@ai-ecoverse/slicc-kernel 1.89.0 → 1.91.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.
@@ -2052,7 +2052,7 @@ function portCdpHook(ask, gone) {
2052
2052
  }
2053
2053
 
2054
2054
  // src/client/protocol.ts
2055
- var PROTOCOL = [1, 10];
2055
+ var PROTOCOL = [1, 11];
2056
2056
  var FS_METHODS = [
2057
2057
  "readFile",
2058
2058
  "writeFile",
@@ -2083,6 +2083,52 @@ function fresh(bytes, start2 = 0, end = bytes.length) {
2083
2083
  return new Uint8Array(bytes.subarray(start2, end));
2084
2084
  }
2085
2085
 
2086
+ // src/facade/port.ts
2087
+ var FACADE_GONE = "the facade host is gone";
2088
+ function portFacadeHook(ask, gone) {
2089
+ return (stamp, { name: name2 }) => new Promise((resolve2, reject) => {
2090
+ const { port1, port2 } = new MessageChannel();
2091
+ let closed = false;
2092
+ const shut = () => {
2093
+ closed = true;
2094
+ port1.close();
2095
+ };
2096
+ const conn = {
2097
+ send: (bytes) => {
2098
+ if (closed) return;
2099
+ const copy = bytes.slice();
2100
+ port1.postMessage({ b: copy }, [copy.buffer]);
2101
+ },
2102
+ onmessage: null,
2103
+ onclose: null,
2104
+ close: (reason) => {
2105
+ if (closed) return;
2106
+ port1.postMessage({ close: reason ?? true });
2107
+ shut();
2108
+ }
2109
+ };
2110
+ port1.addEventListener("message", ({ data }) => {
2111
+ if ("b" in data) conn.onmessage?.(data.b);
2112
+ else if ("open" in data) resolve2(conn);
2113
+ else if ("error" in data) {
2114
+ shut();
2115
+ reject(new Error(data.error));
2116
+ } else if (!closed) {
2117
+ shut();
2118
+ conn.onclose?.();
2119
+ }
2120
+ });
2121
+ port1.start();
2122
+ void gone?.then(() => {
2123
+ if (closed) return;
2124
+ shut();
2125
+ reject(new Error(FACADE_GONE));
2126
+ conn.onclose?.(FACADE_GONE);
2127
+ });
2128
+ ask({ name: name2, stamp }, port2);
2129
+ });
2130
+ }
2131
+
2086
2132
  // src/fs/walk.ts
2087
2133
  var MAX_LINKS3 = 40;
2088
2134
  function parentOf4(path) {
@@ -2253,8 +2299,8 @@ async function access(fs, cred, path, want, options = {}) {
2253
2299
  if (await present(fs, walked.path, false)) return { ...walked, denied: walked.path };
2254
2300
  return { ...walked, missing: true };
2255
2301
  }
2256
- function refused(path, errno = "EACCES") {
2257
- return fsError(errno, path);
2302
+ function refused(path, errno2 = "EACCES") {
2303
+ return fsError(errno2, path);
2258
2304
  }
2259
2305
 
2260
2306
  // src/fs/dac.ts
@@ -2314,9 +2360,9 @@ var Created = class {
2314
2360
  function linked(g, options) {
2315
2361
  return g.links === void 0 ? options : { ...options, links: g.links };
2316
2362
  }
2317
- function deny(g, op, path, errno = "EACCES") {
2363
+ function deny(g, op, path, errno2 = "EACCES") {
2318
2364
  const tag = g.tag === void 0 ? {} : { tag: g.tag };
2319
- g.dac.denied({ pid: g.pid, uid: g.cred.euid, op, path, errno, ...tag });
2365
+ g.dac.denied({ pid: g.pid, uid: g.cred.euid, op, path, errno: errno2, ...tag });
2320
2366
  }
2321
2367
  var TTY_MODE = 400;
2322
2368
  function ttyOpenable(g, tty) {
@@ -2983,7 +3029,7 @@ var WASI_SIGNAL_TO_POSIX = Object.fromEntries(
2983
3029
 
2984
3030
  // src/process/wasi-errno.ts
2985
3031
  var WASI_ERRNO = {
2986
- ...Object.fromEntries(Object.entries(E).map(([name2, errno]) => [`E${name2}`, errno])),
3032
+ ...Object.fromEntries(Object.entries(E).map(([name2, errno2]) => [`E${name2}`, errno2])),
2987
3033
  EWOULDBLOCK: E.AGAIN,
2988
3034
  ENOTSUP: 138,
2989
3035
  EOPNOTSUPP: 138,
@@ -3080,7 +3126,7 @@ function dialError(e, timedOut, closed) {
3080
3126
  if (timedOut) return new KernelError("ETIMEDOUT");
3081
3127
  if (closed) return new KernelError("ECONNABORTED");
3082
3128
  const code = e?.code;
3083
- const known = DIAL_ERRORS.find((errno) => errno === code);
3129
+ const known = DIAL_ERRORS.find((errno2) => errno2 === code);
3084
3130
  return new KernelError(known ?? "EHOSTUNREACH");
3085
3131
  }
3086
3132
  function isLoopback(host) {
@@ -3137,6 +3183,7 @@ var KernelSocket = class _KernelSocket {
3137
3183
  local;
3138
3184
  peer;
3139
3185
  peerCred;
3186
+ peerStamp;
3140
3187
  listenerCred;
3141
3188
  link;
3142
3189
  readShut = false;
@@ -3207,10 +3254,10 @@ var KernelSocket = class _KernelSocket {
3207
3254
  for (const wait2 of waits) wait2.catch(() => void 0);
3208
3255
  return pipeCall(() => Promise.race(waits));
3209
3256
  }
3210
- bind(addr) {
3257
+ bind(addr, kernel = false) {
3211
3258
  if (this.state !== "open" || this.local) throw new KernelError("EINVAL");
3212
3259
  if (addr.family !== this.domain) throw new KernelError("EAFNOSUPPORT");
3213
- this.local = this.net.bind(this, addr);
3260
+ this.local = this.net.bind(this, addr, kernel);
3214
3261
  this.boundAt = this.local;
3215
3262
  }
3216
3263
  listen(backlog, cred) {
@@ -3239,7 +3286,7 @@ var KernelSocket = class _KernelSocket {
3239
3286
  this.changes.wake();
3240
3287
  return true;
3241
3288
  }
3242
- connect(addr, viaUplink = true, cred) {
3289
+ connect(addr, viaUplink = true, cred, stamp) {
3243
3290
  if (this.state === "connected") throw new KernelError("EISCONN");
3244
3291
  if (this.state === "connecting") throw new KernelError("EALREADY");
3245
3292
  if (this.state !== "open") throw new KernelError("EINVAL");
@@ -3258,6 +3305,7 @@ var KernelSocket = class _KernelSocket {
3258
3305
  server.local = addr.family === "inet" ? { ...addr, host: canonicalHost(addr.host) } : addr;
3259
3306
  server.peer = this.local;
3260
3307
  server.peerCred = cred;
3308
+ server.peerStamp = stamp;
3261
3309
  this.peer = server.local;
3262
3310
  this.peerCred = target.listenerCred;
3263
3311
  link(this, server);
@@ -3398,6 +3446,8 @@ var LoopbackNet = class {
3398
3446
  bound = /* @__PURE__ */ new Map();
3399
3447
  activators = /* @__PURE__ */ new Map();
3400
3448
  internal = /* @__PURE__ */ new Set();
3449
+ reserved = /* @__PURE__ */ new Set();
3450
+ facades = /* @__PURE__ */ new Map();
3401
3451
  nextEphemeral = EPHEMERAL_FIRST;
3402
3452
  route;
3403
3453
  useUplink(route) {
@@ -3412,6 +3462,18 @@ var LoopbackNet = class {
3412
3462
  if (reserve) this.internal.add(port);
3413
3463
  return this.internal.has(port);
3414
3464
  }
3465
+ reserve(port) {
3466
+ this.reserved.add(port);
3467
+ }
3468
+ facade(port, name2) {
3469
+ this.reserve(port);
3470
+ this.kernelOnly(port, true);
3471
+ this.facades.set(port, name2);
3472
+ }
3473
+ facadeAt(addr) {
3474
+ if (addr.family !== "inet" || !isLoopback(canonicalHost(addr.host))) return void 0;
3475
+ return this.facades.get(addr.port);
3476
+ }
3415
3477
  activate(addr, start2) {
3416
3478
  this.activators.set(keyOf(addr), start2);
3417
3479
  }
@@ -3420,7 +3482,7 @@ var LoopbackNet = class {
3420
3482
  }
3421
3483
  listen(addr, backlog = 128) {
3422
3484
  const socket = this.socket(addr.family);
3423
- socket.bind(addr);
3485
+ socket.bind(addr, true);
3424
3486
  socket.listen(backlog);
3425
3487
  return socket;
3426
3488
  }
@@ -3429,11 +3491,13 @@ var LoopbackNet = class {
3429
3491
  void socket.connect(addr, false);
3430
3492
  return socket;
3431
3493
  }
3432
- bind(socket, addr) {
3494
+ bind(socket, addr, kernel = false) {
3433
3495
  if (addr.family === "unix") {
3434
3496
  if (addr.path === "") throw new KernelError("EINVAL");
3435
3497
  } else if (addr.host !== "0.0.0.0" && !isLoopback(addr.host)) {
3436
3498
  throw new KernelError("EADDRNOTAVAIL");
3499
+ } else if (!kernel && this.reserved.has(addr.port)) {
3500
+ throw new KernelError("EADDRINUSE");
3437
3501
  }
3438
3502
  const named = addr.family === "inet" && addr.port === 0 ? { ...addr, port: this.ephemeral() } : addr;
3439
3503
  const key = keyOf(named);
@@ -3460,7 +3524,7 @@ var LoopbackNet = class {
3460
3524
  for (let i = 0; i < span; i++) {
3461
3525
  const port = this.nextEphemeral;
3462
3526
  this.nextEphemeral = port === EPHEMERAL_LAST ? EPHEMERAL_FIRST : port + 1;
3463
- if (!this.bound.has(`inet:${port}`)) return port;
3527
+ if (!this.bound.has(`inet:${port}`) && !this.reserved.has(port)) return port;
3464
3528
  }
3465
3529
  throw new KernelError("EADDRINUSE");
3466
3530
  }
@@ -3729,11 +3793,11 @@ function serveTransport(peer, transport, { closeWaitMs = CLOSE_WAIT_MS } = {}) {
3729
3793
  };
3730
3794
  const sockets = /* @__PURE__ */ new Map();
3731
3795
  const credit = /* @__PURE__ */ new Map();
3732
- const refill = (nid, n, closing = false) => {
3796
+ const refill = (nid, n, closing2 = false) => {
3733
3797
  const flow = credit.get(nid);
3734
3798
  if (!flow) return;
3735
3799
  flow.inflight -= n;
3736
- if (closing && !flow.closing) {
3800
+ if (closing2 && !flow.closing) {
3737
3801
  flow.closing = true;
3738
3802
  flow.deadline();
3739
3803
  }
@@ -3756,12 +3820,12 @@ function serveTransport(peer, transport, { closeWaitMs = CLOSE_WAIT_MS } = {}) {
3756
3820
  }
3757
3821
  sockets.set(call.nid, socket);
3758
3822
  peer.postMessage({ net: "ws-opened", nid: call.nid, protocol: socket.protocol });
3759
- void relay(call.nid, socket);
3823
+ void relay2(call.nid, socket);
3760
3824
  } catch (e) {
3761
3825
  fail2(call.nid, e);
3762
3826
  }
3763
3827
  };
3764
- const relay = async (nid, socket) => {
3828
+ const relay2 = async (nid, socket) => {
3765
3829
  let ending;
3766
3830
  let late;
3767
3831
  const unanswered = new Promise((resolve2) => {
@@ -4395,6 +4459,45 @@ function cdpRegistry(send, gone) {
4395
4459
  }
4396
4460
  };
4397
4461
  }
4462
+ function facadeRegistry(send, gone) {
4463
+ const registered = /* @__PURE__ */ new Map();
4464
+ const drop = (id2) => {
4465
+ registered.get(id2)?.();
4466
+ return registered.delete(id2);
4467
+ };
4468
+ return {
4469
+ serve(l, req) {
4470
+ const hook = portFacadeHook(
4471
+ (request2, facade) => send({ facadeOpen: { ...request2, registration: req.id }, port: facade }, [
4472
+ facade
4473
+ ]),
4474
+ gone
4475
+ );
4476
+ registered.set(req.id, l.facades.register(req.name, hook));
4477
+ return req.id;
4478
+ },
4479
+ drop,
4480
+ close: () => {
4481
+ for (const id2 of [...registered.keys()]) drop(id2);
4482
+ }
4483
+ };
4484
+ }
4485
+ function hostRegistries(send, gone) {
4486
+ const cdps = cdpRegistry(send, gone);
4487
+ const facades = facadeRegistry(send, gone);
4488
+ return {
4489
+ answer(l, req) {
4490
+ if (req.op === "serve-cdp") return cdps.serve(l, req);
4491
+ if (req.op === "unserve-cdp") return cdps.drop(req.registration);
4492
+ if (req.op === "serve-facade") return facades.serve(l, req);
4493
+ return facades.drop(req.registration);
4494
+ },
4495
+ close() {
4496
+ cdps.close();
4497
+ facades.close();
4498
+ }
4499
+ };
4500
+ }
4398
4501
  function visibleTo(g, notify) {
4399
4502
  if (!g?.dac.enforcing) return notify;
4400
4503
  let queue = Promise.resolve();
@@ -4438,7 +4541,7 @@ function serveClient(port, host) {
4438
4541
  if (state !== "closed") port.postMessage(message3, transfer);
4439
4542
  };
4440
4543
  const reply = (id2, body, transfer) => send({ id: id2, ...body }, transfer);
4441
- const cdps = cdpRegistry(send, ended.promise);
4544
+ const hosts = hostRegistries(send, ended.promise);
4442
4545
  function detach(kill = false, bye = "the slicc-kernel detached this client") {
4443
4546
  if (state === "closed") return;
4444
4547
  send({ bye });
@@ -4446,7 +4549,7 @@ function serveClient(port, host) {
4446
4549
  net?.close();
4447
4550
  for (const unwatch of watches.values()) unwatch();
4448
4551
  watches.clear();
4449
- cdps.close();
4552
+ hosts.close();
4450
4553
  for (const session of terminals.values()) session.close();
4451
4554
  terminals.clear();
4452
4555
  if (kill) killGroups(host, [...groups]);
@@ -4543,16 +4646,16 @@ function serveClient(port, host) {
4543
4646
  case "mount":
4544
4647
  return { result: await l.mountAs(clientCred(self), req.spec) };
4545
4648
  case "umount":
4546
- await l.umountAs(clientCred(self), req.target);
4547
- return { result: true };
4649
+ return { result: await l.umountAs(clientCred(self), req.target).then(() => true) };
4548
4650
  case "mounts":
4549
4651
  return { result: l.mounts.list() };
4550
4652
  case "dial":
4551
4653
  return dialFor(l, req, watches);
4552
4654
  case "serve-cdp":
4553
- return { result: cdps.serve(l, req) };
4554
4655
  case "unserve-cdp":
4555
- return { result: cdps.drop(req.registration) };
4656
+ case "serve-facade":
4657
+ case "unserve-facade":
4658
+ return { result: hosts.answer(l, req) };
4556
4659
  case "detach":
4557
4660
  return { result: true };
4558
4661
  default:
@@ -4825,6 +4928,33 @@ function toHostLoopback(href) {
4825
4928
  url.hostname = "127.0.0.1";
4826
4929
  return url.href;
4827
4930
  }
4931
+ var LOOPBACK_V4 = /^127\.\d{1,3}\.\d{1,3}\.\d{1,3}$/;
4932
+ var LOOPBACK_V6 = /* @__PURE__ */ new Set(["::1", "::", "0:0:0:0:0:0:0:1"]);
4933
+ var MAPPED_V4 = /^::ffff:7f[0-9a-f]{2}:[0-9a-f]{1,4}$/;
4934
+ var KERNEL_PORT = /^(\d{1,5})\.kernel\.localhost$/;
4935
+ var SCHEMES = {
4936
+ "http:": 80,
4937
+ "ws:": 80,
4938
+ "https:": 443,
4939
+ "wss:": 443
4940
+ };
4941
+ function isKernelLoopbackHost(hostname) {
4942
+ const host = hostname.replace(/^\[(.*)\]$/, "$1").replace(/\.$/, "").toLowerCase();
4943
+ return host === "localhost" || host.endsWith(".localhost") || host === "0.0.0.0" || LOOPBACK_V4.test(host) || LOOPBACK_V6.has(host) || MAPPED_V4.test(host);
4944
+ }
4945
+ function kernelLoopbackPort(href) {
4946
+ let url;
4947
+ try {
4948
+ url = new URL(href);
4949
+ } catch {
4950
+ return void 0;
4951
+ }
4952
+ const fallback = SCHEMES[url.protocol];
4953
+ if (fallback === void 0 || !isKernelLoopbackHost(url.hostname)) return void 0;
4954
+ const named = KERNEL_PORT.exec(url.hostname.replace(/\.$/, "").toLowerCase())?.[1];
4955
+ const port = Number(named ?? (url.port || fallback));
4956
+ return port >= 1 && port <= 65535 ? port : void 0;
4957
+ }
4828
4958
  var DEFAULT_HOSTNAME = "slicc";
4829
4959
  var HOSTNAME_ADDRESS = "127.0.1.1";
4830
4960
  var LIBC_HOSTNAMES = ["emscripten", "wasmer.sh"];
@@ -5130,6 +5260,9 @@ async function acceptKey(headers) {
5130
5260
  }
5131
5261
  const key = field(headers, "sec-websocket-key") ?? "";
5132
5262
  if (!/^[A-Za-z0-9+/]{22}==$/.test(key)) throw new HttpError(400, "bad Sec-WebSocket-Key");
5263
+ return acceptFor(key);
5264
+ }
5265
+ async function acceptFor(key) {
5133
5266
  const digest = await crypto.subtle.digest("SHA-1", new TextEncoder().encode(key + GUID));
5134
5267
  return btoa(String.fromCharCode(...new Uint8Array(digest)));
5135
5268
  }
@@ -5153,6 +5286,19 @@ function frame(opcode, payload) {
5153
5286
  out.set(payload, size);
5154
5287
  return out;
5155
5288
  }
5289
+ function clientFrame(opcode, payload) {
5290
+ const plain = frame(opcode, payload);
5291
+ const head = plain.length - payload.length;
5292
+ const mask = crypto.getRandomValues(new Uint8Array(4));
5293
+ const out = new Uint8Array(plain.length + 4);
5294
+ out.set(plain.subarray(0, head));
5295
+ out[1] = out[1] | 128;
5296
+ out.set(mask, head);
5297
+ for (let i = 0; i < payload.length; i++) {
5298
+ out[head + 4 + i] = payload[i] ^ mask[i & 3];
5299
+ }
5300
+ return out;
5301
+ }
5156
5302
  function closeFrame(code, reason = "") {
5157
5303
  let bytes = new TextEncoder().encode(reason);
5158
5304
  if (bytes.length > 123) {
@@ -5192,10 +5338,12 @@ var WsReader = class {
5192
5338
  continuing = false;
5193
5339
  binaryMessage = false;
5194
5340
  binary;
5195
- constructor(incoming, max = MAX_MESSAGE, binary) {
5341
+ fromServer;
5342
+ constructor(incoming, max = MAX_MESSAGE, binary, fromServer = false) {
5196
5343
  this.incoming = incoming;
5197
5344
  this.max = max;
5198
5345
  this.binary = binary;
5346
+ this.fromServer = fromServer;
5199
5347
  }
5200
5348
  async next(signal) {
5201
5349
  for (; ; ) {
@@ -5207,7 +5355,7 @@ var WsReader = class {
5207
5355
  const fin = (head[0] & 128) !== 0;
5208
5356
  const opcode = head[0] & 15;
5209
5357
  if ((head[0] & 112) !== 0) throw new WsError(CLOSE.protocol, "reserved bits are set");
5210
- if ((head[1] & 128) === 0) throw new WsError(CLOSE.protocol, "client frames must be masked");
5358
+ const masked = this.masking(head[1]);
5211
5359
  const length2 = await this.length(head[1] & 127, signal);
5212
5360
  const control = opcode >= 8;
5213
5361
  if (control && (length2 > 125 || !fin)) {
@@ -5216,15 +5364,19 @@ var WsReader = class {
5216
5364
  if (!control && this.size + length2 > this.max) {
5217
5365
  throw new WsError(CLOSE.tooBig, `a message is over ${this.max} bytes`);
5218
5366
  }
5219
- const mask = new Uint8Array(4);
5220
- await fill(this.incoming, mask, signal);
5221
- const payload = new Uint8Array(length2);
5222
- await fill(this.incoming, payload, signal);
5223
- for (let i = 0; i < length2; i++) payload[i] ^= mask[i & 3];
5367
+ const payload = await this.payload(length2, masked, signal);
5224
5368
  const event = this.take(opcode, fin, payload);
5225
5369
  if (event) return event;
5226
5370
  }
5227
5371
  }
5372
+ masking(second) {
5373
+ const masked = (second & 128) !== 0;
5374
+ if (masked !== this.fromServer) return masked;
5375
+ throw new WsError(
5376
+ CLOSE.protocol,
5377
+ this.fromServer ? "server frames must not be masked" : "client frames must be masked"
5378
+ );
5379
+ }
5228
5380
  data(opcode, fin, payload) {
5229
5381
  if (opcode === OP.continuation !== this.continuing) {
5230
5382
  throw new WsError(CLOSE.protocol, "unexpected continuation frame");
@@ -5239,6 +5391,14 @@ var WsReader = class {
5239
5391
  this.size = 0;
5240
5392
  return this.binaryMessage ? { kind: "binary", bytes: whole } : { kind: "text", text: decode(whole) };
5241
5393
  }
5394
+ async payload(length2, masked, signal) {
5395
+ const mask = new Uint8Array(4);
5396
+ if (masked) await fill(this.incoming, mask, signal);
5397
+ const payload = new Uint8Array(length2);
5398
+ await fill(this.incoming, payload, signal);
5399
+ if (masked) for (let i = 0; i < length2; i++) payload[i] ^= mask[i & 3];
5400
+ return payload;
5401
+ }
5242
5402
  async length(short, signal) {
5243
5403
  if (short < 126) return short;
5244
5404
  const ext = new Uint8Array(short === 126 ? 2 : 8);
@@ -5674,10 +5834,10 @@ var RealmProxy = class {
5674
5834
  encodedBodies: this.options.transport.traits.encodedBodies,
5675
5835
  bodiless: bodiless2
5676
5836
  });
5677
- const chunked = !bodiless2 && minor >= 1;
5678
- const keep2 = keepAlive && (bodiless2 || chunked);
5837
+ const chunked2 = !bodiless2 && minor >= 1;
5838
+ const keep2 = keepAlive && (bodiless2 || chunked2);
5679
5839
  headers.push(["Connection", keep2 ? "keep-alive" : "close"]);
5680
- if (chunked) headers.push(["Transfer-Encoding", "chunked"]);
5840
+ if (chunked2) headers.push(["Transfer-Encoding", "chunked"]);
5681
5841
  const signal = this.stop.signal;
5682
5842
  await conn.write(
5683
5843
  responseHead(status, response.statusText || REASON[status] || "", headers),
@@ -5689,13 +5849,13 @@ var RealmProxy = class {
5689
5849
  }
5690
5850
  try {
5691
5851
  for await (const piece of response.body) {
5692
- if (piece.length > 0) await conn.write(chunked ? chunk(piece) : piece, signal);
5852
+ if (piece.length > 0) await conn.write(chunked2 ? chunk(piece) : piece, signal);
5693
5853
  }
5694
5854
  } catch {
5695
5855
  await response.cancel().catch(() => void 0);
5696
5856
  return false;
5697
5857
  }
5698
- if (chunked) await conn.write(LAST_CHUNK, signal);
5858
+ if (chunked2) await conn.write(LAST_CHUNK, signal);
5699
5859
  return keep2;
5700
5860
  }
5701
5861
  async connect(conn, incoming, req, idle) {
@@ -7127,6 +7287,7 @@ var Relay = class {
7127
7287
  function enableCdp(net, hosts) {
7128
7288
  let facade;
7129
7289
  net.kernelOnly(CDP_PORT, true);
7290
+ net.reserve(CDP_PORT);
7130
7291
  net.activate({ family: "inet", host: "127.0.0.1", port: CDP_PORT }, () => {
7131
7292
  const started = new CdpFacade({ net, hosts });
7132
7293
  facade = started;
@@ -7441,9 +7602,9 @@ async function scanFilesystems(fs, modules) {
7441
7602
  } catch {
7442
7603
  continue;
7443
7604
  }
7444
- const declared = manifest.slicc?.filesystems;
7445
- if (!declared || typeof declared !== "object") continue;
7446
- for (const [type, entry] of Object.entries(declared)) {
7605
+ const declared2 = manifest.slicc?.filesystems;
7606
+ if (!declared2 || typeof declared2 !== "object") continue;
7607
+ for (const [type, entry] of Object.entries(declared2)) {
7447
7608
  const module = inside(pkg, entry?.module);
7448
7609
  if (NAME2.test(type) && module && !found.has(type)) found.set(type, module);
7449
7610
  }
@@ -7461,9 +7622,9 @@ async function scanBinfmts(fs, modules, warn = () => {
7461
7622
  } catch {
7462
7623
  continue;
7463
7624
  }
7464
- const declared = manifest.slicc?.binfmt;
7465
- if (!declared || typeof declared !== "object") continue;
7466
- for (const [suffix, command] of Object.entries(declared)) {
7625
+ const declared2 = manifest.slicc?.binfmt;
7626
+ if (!declared2 || typeof declared2 !== "object") continue;
7627
+ for (const [suffix, command] of Object.entries(declared2)) {
7467
7628
  if (!SUFFIX.test(suffix) || typeof command !== "string" || !NAME2.test(command)) continue;
7468
7629
  const owner = owners.get(suffix);
7469
7630
  if (owner === void 0) {
@@ -7493,6 +7654,132 @@ function binfmtOf(binfmts, path) {
7493
7654
  return best === void 0 ? void 0 : binfmts.get(best);
7494
7655
  }
7495
7656
 
7657
+ // src/facade/types.ts
7658
+ var FACADE_NAME = /^[a-z][a-z0-9-]*$/;
7659
+ function facadeEnvName(name2) {
7660
+ return `SLICC_${name2.toUpperCase().replaceAll("-", "_")}_URL`;
7661
+ }
7662
+ function facadeUrl(port) {
7663
+ return `tcp://127.0.0.1:${port}`;
7664
+ }
7665
+
7666
+ // src/facade/hosts.ts
7667
+ var TAKEN_PORTS = /* @__PURE__ */ new Set([CDP_PORT, REALM_PROXY_PORT]);
7668
+ var READ_CHUNK = 64 * 1024;
7669
+ var FACADE_MAX_PENDING = 16 * 1024 * 1024;
7670
+ var FACADE_FLOODED = `slicc-kernel: more than ${FACADE_MAX_PENDING} bytes are waiting for the program to read`;
7671
+ function declared(specs) {
7672
+ const out = /* @__PURE__ */ new Map();
7673
+ const ports = /* @__PURE__ */ new Set();
7674
+ for (const [name2, spec] of Object.entries(specs ?? {})) {
7675
+ if (!FACADE_NAME.test(name2)) throw new TypeError(`facades: not a facade name: ${name2}`);
7676
+ const port = spec?.port;
7677
+ if (!Number.isInteger(port) || port < 1 || port > 65535 || TAKEN_PORTS.has(port)) {
7678
+ throw new TypeError(`facades.${name2}: not a free port: ${String(port)}`);
7679
+ }
7680
+ if (ports.has(port)) throw new TypeError(`facades.${name2}: port ${port} is taken twice`);
7681
+ if (spec.hook !== void 0 && typeof spec.hook !== "function") {
7682
+ throw new TypeError(`facades.${name2}: hook must be a function`);
7683
+ }
7684
+ ports.add(port);
7685
+ out.set(name2, { port, embedder: spec.hook, clients: [] });
7686
+ }
7687
+ return out;
7688
+ }
7689
+ var FacadeHosts = class {
7690
+ facades;
7691
+ constructor(specs) {
7692
+ this.facades = declared(specs);
7693
+ }
7694
+ env() {
7695
+ const env = {};
7696
+ for (const [name2, { port }] of this.facades) env[facadeEnvName(name2)] = facadeUrl(port);
7697
+ return env;
7698
+ }
7699
+ enable(net) {
7700
+ for (const [name2, { port }] of this.facades) {
7701
+ net.facade(port, name2);
7702
+ net.activate({ family: "inet", host: "127.0.0.1", port }, () => {
7703
+ const listener = net.listen({ family: "inet", host: "127.0.0.1", port });
7704
+ void acceptAll(listener, (socket) => this.serve(name2, socket));
7705
+ });
7706
+ }
7707
+ }
7708
+ register(name2, hook) {
7709
+ const entry = this.facades.get(name2);
7710
+ if (!entry || typeof hook !== "function") throw new KernelError("EINVAL");
7711
+ entry.clients.push(hook);
7712
+ return () => {
7713
+ const at = entry.clients.indexOf(hook);
7714
+ if (at >= 0) entry.clients.splice(at, 1);
7715
+ };
7716
+ }
7717
+ async serve(name2, socket) {
7718
+ const entry = this.facades.get(name2);
7719
+ const hook = entry.clients.at(-1) ?? entry.embedder;
7720
+ const stamp = socket.peerStamp;
7721
+ if (!hook || !stamp) return socket.close();
7722
+ let conn;
7723
+ try {
7724
+ conn = await hook(structuredClone(stamp), { name: name2 });
7725
+ } catch {
7726
+ return socket.close();
7727
+ }
7728
+ await relay(socket, conn);
7729
+ }
7730
+ };
7731
+ async function acceptAll(listener, serve) {
7732
+ for (; ; ) {
7733
+ const socket = await listener.accept();
7734
+ void serve(socket);
7735
+ }
7736
+ }
7737
+ async function relay(socket, conn) {
7738
+ let ended = false;
7739
+ let pending = 0;
7740
+ let writes = Promise.resolve();
7741
+ const end = () => {
7742
+ if (ended) return;
7743
+ ended = true;
7744
+ socket.close();
7745
+ };
7746
+ conn.onmessage = (bytes) => {
7747
+ if (ended) return;
7748
+ const chunk2 = bytes.slice();
7749
+ pending += chunk2.length;
7750
+ if (pending > FACADE_MAX_PENDING) {
7751
+ end();
7752
+ conn.close(FACADE_FLOODED);
7753
+ return;
7754
+ }
7755
+ writes = writes.then(async () => {
7756
+ if (ended) return;
7757
+ let at = 0;
7758
+ while (at < chunk2.length) at += await socket.write(chunk2.subarray(at));
7759
+ pending -= chunk2.length;
7760
+ });
7761
+ writes.catch(() => {
7762
+ end();
7763
+ conn.close();
7764
+ });
7765
+ };
7766
+ conn.onclose = () => {
7767
+ writes.then(end, end);
7768
+ };
7769
+ try {
7770
+ for (; ; ) {
7771
+ const bytes = await socket.read(READ_CHUNK);
7772
+ if (bytes.length === 0 || ended) break;
7773
+ conn.send(bytes);
7774
+ }
7775
+ } catch {
7776
+ }
7777
+ if (!ended) {
7778
+ end();
7779
+ conn.close();
7780
+ }
7781
+ }
7782
+
7496
7783
  // src/fs/attest.ts
7497
7784
  var ATTEST_MAX = 1024 * 1024;
7498
7785
  var UNOWNED = 1e3;
@@ -7995,15 +8282,15 @@ var UnlinkedKeeper = class {
7995
8282
  wrap(fs = this.fs) {
7996
8283
  const keeper = this;
7997
8284
  const visible = (names) => names.filter((name2) => name2 !== UNLINKED_DIR);
7998
- const counted = (st) => {
8285
+ const counted2 = (st) => {
7999
8286
  const hidden = keeper.links.get(`${st.dev ?? 0}:${st.ino}`) ?? 0;
8000
8287
  return hidden === 0 || st.nlink === void 0 ? st : { ...st, nlink: Math.max(0, st.nlink - hidden) };
8001
8288
  };
8002
8289
  return {
8003
8290
  ...fs,
8004
8291
  links: (path) => mayLink(fs, path),
8005
- stat: async (path) => counted(await fs.stat(path)),
8006
- lstat: async (path) => counted(await fs.lstat(path)),
8292
+ stat: async (path) => counted2(await fs.stat(path)),
8293
+ lstat: async (path) => counted2(await fs.lstat(path)),
8007
8294
  async rm(path, options) {
8008
8295
  const target = fs.resolvePath("/", path);
8009
8296
  const pinned = await keeper.pin(target);
@@ -8017,7 +8304,7 @@ var UnlinkedKeeper = class {
8017
8304
  await keeper.settle(pinned, () => fs.rename(from, to));
8018
8305
  },
8019
8306
  readdir: async (path) => visible(await fs.readdir(path)),
8020
- readdirStat: async (path) => (await fs.readdirStat(path)).filter(([name2]) => name2 !== UNLINKED_DIR).map(([name2, st]) => [name2, st && counted(st)])
8307
+ readdirStat: async (path) => (await fs.readdirStat(path)).filter(([name2]) => name2 !== UNLINKED_DIR).map(([name2, st]) => [name2, st && counted2(st)])
8021
8308
  };
8022
8309
  }
8023
8310
  async settle(pinned, op) {
@@ -10849,7 +11136,7 @@ function mountTable(mounts = []) {
10849
11136
  }
10850
11137
 
10851
11138
  // src/version.ts
10852
- var KERNEL_VERSION = true ? "1.89.0" : "0.0.0-dev";
11139
+ var KERNEL_VERSION = true ? "1.91.0" : "0.0.0-dev";
10853
11140
  var OS_RELEASE = "6.0.0-slicc";
10854
11141
  function procVersion() {
10855
11142
  return `Linux version ${OS_RELEASE} (slicc-kernel ${KERNEL_VERSION}) #1 SMP PREEMPT_DYNAMIC
@@ -11437,6 +11724,339 @@ async function hostSyscall(req, ctx) {
11437
11724
  }
11438
11725
  }
11439
11726
 
11727
+ // src/kernel/net/loopback-transport.ts
11728
+ var LOOPBACK_TRAITS = {
11729
+ manualRedirects: true,
11730
+ encodedBodies: true,
11731
+ maxRequestBody: 64 * 1024 * 1024,
11732
+ crossOrigin: "any",
11733
+ websocket: true
11734
+ };
11735
+ var MAX_HEAD2 = 64 * 1024;
11736
+ var OWN_HEADERS = /* @__PURE__ */ new Set([
11737
+ "host",
11738
+ "connection",
11739
+ "content-length",
11740
+ "transfer-encoding",
11741
+ "upgrade",
11742
+ "sec-websocket-key",
11743
+ "sec-websocket-version",
11744
+ "sec-websocket-protocol",
11745
+ "sec-websocket-extensions"
11746
+ ]);
11747
+ var OP_CLOSE = 8;
11748
+ var OP_PONG = 10;
11749
+ var TOKEN3 = /^[!#$%&'*+.^_`|~0-9A-Za-z-]+$/;
11750
+ var SAFE_VALUE = /^[^\r\n\0]*$/;
11751
+ var CLOSE_WAIT_MS3 = 5e3;
11752
+ var MAX_UNREAD = 16 * 1024 * 1024;
11753
+ function errno(code, message3) {
11754
+ return Object.assign(new Error(`${code}: ${message3}`), { code });
11755
+ }
11756
+ function requestHead(method, url, headers, extra) {
11757
+ if (!TOKEN3.test(method)) throw errno("EINVAL", `method ${method}`);
11758
+ const lines2 = [`${method} ${url.pathname}${url.search} HTTP/1.1`, `Host: ${url.host}`];
11759
+ for (const [name2, value] of headers) {
11760
+ if (OWN_HEADERS.has(name2.toLowerCase())) continue;
11761
+ if (!TOKEN3.test(name2) || !SAFE_VALUE.test(value)) throw errno("EINVAL", `header ${name2}`);
11762
+ lines2.push(`${name2}: ${value}`);
11763
+ }
11764
+ for (const [name2, value] of extra) lines2.push(`${name2}: ${value}`);
11765
+ return latin1Bytes(`${lines2.join("\r\n")}\r
11766
+ \r
11767
+ `);
11768
+ }
11769
+ function parseHead(bytes) {
11770
+ const [statusLine = "", ...lines2] = latin1(bytes).split(/\r?\n/);
11771
+ const match = /^HTTP\/1\.[01] (\d{3})(?: (.*))?$/.exec(statusLine);
11772
+ if (!match) throw errno("EPROTO", `not an HTTP/1.x response: ${statusLine.slice(0, 80)}`);
11773
+ const headers = [];
11774
+ for (const line of lines2) {
11775
+ const colon = line.indexOf(":");
11776
+ if (colon > 0) headers.push([line.slice(0, colon).trim(), line.slice(colon + 1).trim()]);
11777
+ }
11778
+ return { status: Number(match[1]), statusText: match[2] ?? "", headers };
11779
+ }
11780
+ async function readHead(incoming, signal) {
11781
+ for (; ; ) {
11782
+ let raw;
11783
+ try {
11784
+ raw = await incoming.head(MAX_HEAD2, signal);
11785
+ } catch (err) {
11786
+ if (err instanceof KernelError) throw err;
11787
+ throw errno("EPROTO", "the response head is malformed or too large");
11788
+ }
11789
+ if (!raw) throw errno("ECONNRESET", "the connection closed before a response");
11790
+ const head = parseHead(raw);
11791
+ if (head.status < 100 || head.status >= 200 || head.status === 101) return head;
11792
+ }
11793
+ }
11794
+ async function* chunked(incoming, signal) {
11795
+ for (; ; ) {
11796
+ const line = await incoming.line(4096, signal);
11797
+ const size = /^([0-9A-Fa-f]{1,12})[ \t]*(;.*)?$/.exec(line);
11798
+ if (!size) throw errno("EPROTO", "a malformed chunk size");
11799
+ const n = Number.parseInt(size[1], 16);
11800
+ if (n === 0) {
11801
+ while (await incoming.line(MAX_HEAD2, signal) !== "") ;
11802
+ return;
11803
+ }
11804
+ yield await incoming.exactly(n, signal);
11805
+ if (await incoming.line(2, signal) !== "") throw errno("EPROTO", "a malformed chunk");
11806
+ }
11807
+ }
11808
+ async function* counted(incoming, length2, signal) {
11809
+ for (let left = length2; left > 0; ) {
11810
+ const data = await incoming.some(Math.min(left, 64 * 1024), signal);
11811
+ if (data.length === 0) throw errno("EPROTO", "the body ended early");
11812
+ left -= data.length;
11813
+ yield data;
11814
+ }
11815
+ }
11816
+ async function* untilClose(incoming, signal) {
11817
+ for (; ; ) {
11818
+ const data = await incoming.some(64 * 1024, signal);
11819
+ if (data.length === 0) return;
11820
+ yield data;
11821
+ }
11822
+ }
11823
+ function bodyOf(incoming, head, method, signal) {
11824
+ if (method === "HEAD" || head.status === 204 || head.status === 304) return [];
11825
+ if (fieldTokens(head.headers, "transfer-encoding").includes("chunked")) {
11826
+ return chunked(incoming, signal);
11827
+ }
11828
+ const lengths = fieldValues(head.headers, "content-length");
11829
+ if (lengths.length === 0) return untilClose(incoming, signal);
11830
+ const length2 = lengths[0]?.trim() ?? "";
11831
+ if (!/^\d{1,15}$/.test(length2) || lengths.some((l) => l.trim() !== length2)) {
11832
+ throw errno("EPROTO", `a malformed Content-Length: ${lengths.join(", ")}`);
11833
+ }
11834
+ return counted(incoming, Number(length2), signal);
11835
+ }
11836
+ var KernelLoopback = class {
11837
+ net;
11838
+ cred;
11839
+ constructor(net, cred) {
11840
+ this.net = net;
11841
+ this.cred = cred;
11842
+ }
11843
+ port(url) {
11844
+ return kernelLoopbackPort(url);
11845
+ }
11846
+ connect(url, port, signal) {
11847
+ if (url.protocol === "https:" || url.protocol === "wss:") {
11848
+ throw errno("ENOTSUP", `TLS to the kernel's loopback is not supported: ${url.origin}`);
11849
+ }
11850
+ signal.throwIfAborted();
11851
+ const socket = this.net.socket("inet");
11852
+ try {
11853
+ void socket.connect({ family: "inet", host: "127.0.0.1", port }, false, this.cred());
11854
+ } catch (err) {
11855
+ socket.close();
11856
+ const refused2 = err instanceof KernelError && err.code === "ECONNREFUSED";
11857
+ throw refused2 ? errno("ECONNREFUSED", `nothing listens on the kernel's port ${port}`) : err;
11858
+ }
11859
+ signal.addEventListener("abort", () => socket.close(), { once: true });
11860
+ return socket;
11861
+ }
11862
+ async fetch(req, port) {
11863
+ const url = new URL(req.url);
11864
+ const body = req.body ?? (/^(POST|PUT|PATCH)$/i.test(req.method) ? new Uint8Array(0) : void 0);
11865
+ const length2 = body ? [["Content-Length", String(body.length)]] : [];
11866
+ const head = requestHead(req.method, url, req.headers, [...length2, ["Connection", "close"]]);
11867
+ const socket = this.connect(url, port, req.signal);
11868
+ try {
11869
+ await writeAll2(socket, body ? concat([head, body]) : head, req.signal);
11870
+ const incoming = new Incoming(socket);
11871
+ const response = await readHead(incoming, req.signal);
11872
+ const chunks = bodyOf(incoming, response, req.method, req.signal);
11873
+ return {
11874
+ url: req.url,
11875
+ status: response.status,
11876
+ statusText: response.statusText,
11877
+ headers: response.headers,
11878
+ body: closing(chunks, socket),
11879
+ cancel: async () => socket.close()
11880
+ };
11881
+ } catch (err) {
11882
+ socket.close();
11883
+ throw err;
11884
+ }
11885
+ }
11886
+ async websocket(req, port) {
11887
+ const url = new URL(req.url);
11888
+ const socket = this.connect(url, port, req.signal);
11889
+ try {
11890
+ const key = btoa(String.fromCharCode(...crypto.getRandomValues(new Uint8Array(16))));
11891
+ const extra = [
11892
+ ["Upgrade", "websocket"],
11893
+ ["Connection", "Upgrade"],
11894
+ ["Sec-WebSocket-Key", key],
11895
+ ["Sec-WebSocket-Version", "13"]
11896
+ ];
11897
+ if (req.protocols.length > 0)
11898
+ extra.push(["Sec-WebSocket-Protocol", req.protocols.join(", ")]);
11899
+ await writeAll2(socket, requestHead("GET", url, req.headers, extra), req.signal);
11900
+ const incoming = new Incoming(socket);
11901
+ const head = await readHead(incoming, req.signal);
11902
+ if (head.status !== 101) {
11903
+ throw errno(
11904
+ "EPROTO",
11905
+ `the server answered ${head.status} ${head.statusText}, not an upgrade`
11906
+ );
11907
+ }
11908
+ const upgraded = fieldTokens(head.headers, "upgrade").includes("websocket") && fieldTokens(head.headers, "connection").includes("upgrade");
11909
+ const accept2 = fieldValues(head.headers, "sec-websocket-accept")[0];
11910
+ if (!upgraded || accept2 !== await acceptFor(key)) {
11911
+ throw errno("EPROTO", "the server did not accept the WebSocket key");
11912
+ }
11913
+ const protocol = fieldValues(head.headers, "sec-websocket-protocol")[0] ?? "";
11914
+ return new LoopbackWebSocket(socket, incoming, protocol);
11915
+ } catch (err) {
11916
+ socket.close();
11917
+ throw err;
11918
+ }
11919
+ }
11920
+ };
11921
+ async function writeAll2(socket, bytes, signal) {
11922
+ for (let at = 0; at < bytes.length; ) at += await socket.write(bytes.subarray(at), signal);
11923
+ }
11924
+ async function* closing(chunks, socket) {
11925
+ try {
11926
+ yield* chunks;
11927
+ } catch (err) {
11928
+ if (typeof err?.code === "string") throw err;
11929
+ throw errno("EPROTO", err instanceof Error ? err.message : String(err));
11930
+ } finally {
11931
+ socket.close();
11932
+ }
11933
+ }
11934
+ var LoopbackWebSocket = class {
11935
+ protocol;
11936
+ messages;
11937
+ closed;
11938
+ pending = 0;
11939
+ writes = Promise.resolve();
11940
+ sentClose = false;
11941
+ stop = new AbortController();
11942
+ socket;
11943
+ settle;
11944
+ unread = 0;
11945
+ room;
11946
+ constructor(socket, incoming, protocol) {
11947
+ this.socket = socket;
11948
+ this.protocol = protocol;
11949
+ this.closed = new Promise((resolve2) => {
11950
+ this.settle = resolve2;
11951
+ });
11952
+ this.stop.signal.addEventListener("abort", () => this.room?.(), { once: true });
11953
+ const queue = [];
11954
+ let wake;
11955
+ let ended = false;
11956
+ void this.pump(new WsReader(incoming, void 0, true, true), (message3) => {
11957
+ queue.push(message3);
11958
+ this.unread += message3.length;
11959
+ wake?.();
11960
+ }).finally(() => {
11961
+ ended = true;
11962
+ wake?.();
11963
+ });
11964
+ const taken = (message3) => {
11965
+ this.unread -= message3.length;
11966
+ const room = this.room;
11967
+ this.room = void 0;
11968
+ room?.();
11969
+ };
11970
+ this.messages = {
11971
+ [Symbol.asyncIterator]: async function* () {
11972
+ for (; ; ) {
11973
+ const next2 = queue.shift();
11974
+ if (next2 !== void 0) {
11975
+ taken(next2);
11976
+ yield next2;
11977
+ continue;
11978
+ }
11979
+ if (ended) return;
11980
+ await new Promise((resolve2) => {
11981
+ wake = resolve2;
11982
+ });
11983
+ wake = void 0;
11984
+ }
11985
+ }
11986
+ };
11987
+ }
11988
+ async space() {
11989
+ while (this.unread > MAX_UNREAD && !this.stop.signal.aborted) {
11990
+ await new Promise((resolve2) => {
11991
+ this.room = resolve2;
11992
+ });
11993
+ }
11994
+ }
11995
+ get buffered() {
11996
+ return this.pending;
11997
+ }
11998
+ write(bytes) {
11999
+ this.pending += bytes.length;
12000
+ const previous = this.writes;
12001
+ this.writes = (async () => {
12002
+ await previous;
12003
+ try {
12004
+ await writeAll2(this.socket, bytes, this.stop.signal);
12005
+ } catch {
12006
+ this.stop.abort();
12007
+ }
12008
+ this.pending -= bytes.length;
12009
+ })();
12010
+ }
12011
+ async pump(reader, deliver) {
12012
+ let close = { code: 1006, reason: "" };
12013
+ try {
12014
+ for (; ; ) {
12015
+ await this.space();
12016
+ const event = await reader.next(this.stop.signal);
12017
+ if (!event) break;
12018
+ if (event.kind === "text") deliver(event.text);
12019
+ else if (event.kind === "binary") deliver(event.bytes);
12020
+ else if (event.kind === "ping") this.write(clientFrame(OP_PONG, event.payload));
12021
+ else {
12022
+ close = { code: event.code, reason: event.reason };
12023
+ if (!this.sentClose) this.sendClose(event.code === 1005 ? 1e3 : event.code, "");
12024
+ break;
12025
+ }
12026
+ }
12027
+ } catch (err) {
12028
+ if (err instanceof WsError) this.sendClose(err.code, err.message);
12029
+ close = { code: err instanceof WsError ? err.code : 1006, reason: "" };
12030
+ } finally {
12031
+ const late = setTimeout(() => this.stop.abort(), CLOSE_WAIT_MS3);
12032
+ await this.writes;
12033
+ clearTimeout(late);
12034
+ this.socket.close();
12035
+ this.settle(close);
12036
+ }
12037
+ }
12038
+ sendClose(code, reason) {
12039
+ if (this.sentClose) return;
12040
+ this.sentClose = true;
12041
+ const late = setTimeout(() => this.stop.abort(), CLOSE_WAIT_MS3);
12042
+ void this.closed.then(() => clearTimeout(late));
12043
+ const bytes = new TextEncoder().encode(reason).subarray(0, 123);
12044
+ const payload = new Uint8Array(2 + bytes.length);
12045
+ new DataView(payload.buffer).setUint16(0, sendableCode(code) ? code : CLOSE.error);
12046
+ payload.set(bytes, 2);
12047
+ this.write(clientFrame(OP_CLOSE, payload));
12048
+ }
12049
+ send(data) {
12050
+ if (this.sentClose) throw errno("EPIPE", "the websocket is closing");
12051
+ const text = typeof data === "string";
12052
+ const payload = text ? new TextEncoder().encode(data) : data;
12053
+ this.write(clientFrame(text ? OP_TEXT : OP_BINARY, payload));
12054
+ }
12055
+ close(code = 1e3, reason = "") {
12056
+ this.sendClose(code, reason);
12057
+ }
12058
+ };
12059
+
11440
12060
  // src/kernel/net/ws-syscalls.ts
11441
12061
  var WS_OPS = [
11442
12062
  "net-ws-open",
@@ -11446,7 +12066,7 @@ var WS_OPS = [
11446
12066
  "net-ws-send",
11447
12067
  "net-ws-close"
11448
12068
  ];
11449
- var TOKEN3 = /^[!#$%&'*+.^_`|~0-9A-Za-z-]+$/;
12069
+ var TOKEN4 = /^[!#$%&'*+.^_`|~0-9A-Za-z-]+$/;
11450
12070
  function message(e) {
11451
12071
  return e instanceof Error ? e.message : String(e);
11452
12072
  }
@@ -11455,16 +12075,18 @@ function errnoOf(e) {
11455
12075
  const code = e?.code;
11456
12076
  return typeof code === "string" && ERRNO.test(code) ? code : "ECONNREFUSED";
11457
12077
  }
11458
- function fail(errno, text) {
11459
- return { ok: false, errno, message: text };
12078
+ function fail(errno2, text) {
12079
+ return { ok: false, errno: errno2, message: text };
11460
12080
  }
11461
12081
  var WsHandles = class {
11462
12082
  transport;
11463
12083
  open = /* @__PURE__ */ new Map();
11464
12084
  next;
11465
- constructor(transport, next2) {
12085
+ local;
12086
+ constructor(transport, next2, local) {
11466
12087
  this.transport = transport;
11467
12088
  this.next = next2;
12089
+ this.local = local;
11468
12090
  }
11469
12091
  owns(handle) {
11470
12092
  return this.open.has(handle);
@@ -11493,16 +12115,18 @@ var WsHandles = class {
11493
12115
  return { ok: true, kind: "json", json: handle };
11494
12116
  }
11495
12117
  async connect(handle, req, entry) {
11496
- const { transport } = this;
11497
- if (!transport.traits.websocket || !transport.websocket) return fail("ENOTSUP", NO_WEBSOCKET2);
12118
+ const { transport, local } = this;
12119
+ const port = local?.port(req.url);
12120
+ const open = { protocols: req.protocols, headers: req.headers, signal: entry.abort.signal };
12121
+ if (port === void 0 && (!transport.traits.websocket || !transport.websocket)) {
12122
+ return fail("ENOTSUP", NO_WEBSOCKET2);
12123
+ }
11498
12124
  let socket;
11499
12125
  try {
11500
- socket = await transport.websocket({
12126
+ socket = port === void 0 ? await transport.websocket({
11501
12127
  url: toHostLoopback(req.url),
11502
- protocols: req.protocols,
11503
- headers: req.headers,
11504
- signal: entry.abort.signal
11505
- });
12128
+ ...open
12129
+ }) : await local.websocket({ url: req.url, ...open }, port);
11506
12130
  } catch (e) {
11507
12131
  return fail(errnoOf(e), message(e));
11508
12132
  }
@@ -11510,7 +12134,7 @@ var WsHandles = class {
11510
12134
  socket.close(1001, "the program went away");
11511
12135
  return fail("EBADF", `no websocket ${handle}`);
11512
12136
  }
11513
- if (socket.protocol && !(req.protocols.includes(socket.protocol) && TOKEN3.test(socket.protocol))) {
12137
+ if (socket.protocol && !(req.protocols.includes(socket.protocol) && TOKEN4.test(socket.protocol))) {
11514
12138
  socket.close(1002, "subprotocol not offered");
11515
12139
  return fail("EPROTO", "the far end chose a subprotocol the program did not offer");
11516
12140
  }
@@ -11573,6 +12197,10 @@ var EMPTY = new Uint8Array(0);
11573
12197
  function message2(e) {
11574
12198
  return e instanceof Error ? e.message : String(e);
11575
12199
  }
12200
+ function codeOf2(e) {
12201
+ const code = e?.code;
12202
+ return typeof code === "string" && /^E[A-Z]+$/.test(code) ? code : "EIO";
12203
+ }
11576
12204
  async function* once(bytes) {
11577
12205
  yield bytes;
11578
12206
  }
@@ -11582,10 +12210,12 @@ var HttpHandles = class {
11582
12210
  nextHandle = 0;
11583
12211
  ws;
11584
12212
  owner;
11585
- constructor(transport, owner) {
12213
+ local;
12214
+ constructor(transport, owner, local) {
11586
12215
  this.transport = transport;
11587
12216
  this.owner = owner;
11588
- this.ws = new WsHandles(transport, () => ++this.nextHandle);
12217
+ this.local = local;
12218
+ this.ws = new WsHandles(transport, () => ++this.nextHandle, local);
11589
12219
  }
11590
12220
  syscall(req) {
11591
12221
  if (req.op.startsWith("net-ws-")) return this.ws.syscall(req);
@@ -11593,10 +12223,13 @@ var HttpHandles = class {
11593
12223
  if (req.op === "net-open") return Promise.resolve(this.opened(req));
11594
12224
  if (req.op === "net-head") return this.head(req.handle);
11595
12225
  if (req.op === "net-read") return this.read(req.handle, req.max);
11596
- if (req.op === "net-traits") return Promise.resolve(this.traits());
12226
+ if (req.op === "net-traits") return Promise.resolve(this.traits(req.url));
11597
12227
  return this.close(req.handle);
11598
12228
  }
11599
- traits() {
12229
+ traits(url) {
12230
+ if (url !== void 0 && this.local?.port(url) !== void 0) {
12231
+ return { ok: true, kind: "json", json: LOOPBACK_TRAITS };
12232
+ }
11600
12233
  const { unavailable, crossOrigin = "any", ...rest2 } = this.transport.traits;
11601
12234
  if (unavailable) return { ok: false, errno: "ENETUNREACH", message: NO_TRANSPORT };
11602
12235
  return { ok: true, kind: "json", json: { ...rest2, crossOrigin } };
@@ -11627,28 +12260,30 @@ var HttpHandles = class {
11627
12260
  return { handle, head: entry.head };
11628
12261
  }
11629
12262
  async respond(handle, req, entry) {
12263
+ const port = this.local?.port(req.url);
11630
12264
  try {
11631
- const response = await this.transport.fetch(
11632
- {
11633
- url: req.url,
11634
- method: req.method,
11635
- headers: req.headers,
11636
- ...req.body ? { body: req.body } : {},
11637
- signal: entry.abort.signal
11638
- },
11639
- this.owner
11640
- );
12265
+ const target = port === void 0 ? toHostLoopback(req.url) : req.url;
12266
+ const request2 = {
12267
+ url: target,
12268
+ method: req.method,
12269
+ headers: req.headers,
12270
+ ...req.body ? { body: req.body } : {},
12271
+ signal: entry.abort.signal
12272
+ };
12273
+ const response = port === void 0 ? await this.transport.fetch(request2, this.owner) : await this.local.fetch(request2, port);
11641
12274
  entry.body = response.body[Symbol.asyncIterator]();
11642
12275
  entry.cancel = () => response.cancel();
11643
12276
  if (!this.open.has(handle)) await entry.cancel().catch(() => void 0);
11644
- const head = this.headOf(handle, response.url ?? req.url, response);
12277
+ const shown = response.url && response.url !== target ? response.url : req.url;
12278
+ const head = this.headOf(handle, shown, response);
11645
12279
  return response.redirected ? { ...head, json: { ...head.json, redirected: true } } : head;
11646
12280
  } catch (e) {
11647
12281
  const text = message2(e);
12282
+ if (port !== void 0) return { ok: false, errno: codeOf2(e), message: text };
11648
12283
  const status = e.status;
11649
12284
  if (text === NO_TRANSPORT || typeof status !== "number" || status === 502) {
11650
- const errno = text === NO_TRANSPORT ? "ENETUNREACH" : "ECONNREFUSED";
11651
- return { ok: false, errno, message: text };
12285
+ const errno2 = text === NO_TRANSPORT ? "ENETUNREACH" : "ECONNREFUSED";
12286
+ return { ok: false, errno: errno2, message: text };
11652
12287
  }
11653
12288
  entry.body = once(new TextEncoder().encode(text))[Symbol.asyncIterator]();
11654
12289
  return this.headOf(handle, req.url, {
@@ -12489,7 +13124,9 @@ async function socketSyscall(req, proc) {
12489
13124
  case "sock-accept":
12490
13125
  return ok(await accept(proc, req.fd, req.nonblock));
12491
13126
  case "sock-connect": {
12492
- const dialing = socketAt(fds, req.fd).connect(req.addr, true, proc.cred?.());
13127
+ const socket = socketAt(fds, req.fd);
13128
+ const stamp = net.facadeAt(req.addr) && await proc.stamp?.();
13129
+ const dialing = socket.connect(req.addr, true, proc.cred?.(), stamp || void 0);
12493
13130
  if (req.nonblock) {
12494
13131
  dialing?.catch(() => void 0);
12495
13132
  throw new KernelError("EINPROGRESS");
@@ -13668,6 +14305,9 @@ var WasmProcess = class {
13668
14305
  if (channel) channel.stamp = stamp;
13669
14306
  }
13670
14307
  }
14308
+ connectorStamp() {
14309
+ return this.sealedCaller();
14310
+ }
13671
14311
  async sealedCaller() {
13672
14312
  const shown = await this.options.identity?.();
13673
14313
  const broker = this.options.sealed;
@@ -14006,7 +14646,8 @@ var WasmProcess = class {
14006
14646
  net: this.net,
14007
14647
  blocking: () => this.blockingSignal(cancelled),
14008
14648
  resolve: (name2, family, blocking) => resolver.resolve(name2, family, blocking),
14009
- cred: () => ({ pid: this.pid, uid: this.cred.euid, gid: this.cred.egid })
14649
+ cred: () => ({ pid: this.pid, uid: this.cred.euid, gid: this.cred.egid }),
14650
+ stamp: () => this.connectorStamp()
14010
14651
  });
14011
14652
  }
14012
14653
  tty(fd) {
@@ -15378,7 +16019,7 @@ function scrubberOf(secrets) {
15378
16019
  const usable = secrets.filter((s) => encoder5.encode(s.value).length >= MIN_VALUE);
15379
16020
  return usable.length > 0 ? compile(usable, { idleMs: SCRUB_IDLE_MS }) : void 0;
15380
16021
  }
15381
- async function writeAll2(file, bytes) {
16022
+ async function writeAll3(file, bytes) {
15382
16023
  const write = file.write;
15383
16024
  for (let at = 0; at < bytes.length; ) at += await write.call(file, bytes.subarray(at));
15384
16025
  }
@@ -15412,7 +16053,7 @@ function scrubbed(inner, scrubber, idleMs = SCRUB_IDLE_MS) {
15412
16053
  const later = (bytes) => {
15413
16054
  if (bytes.length === 0) return;
15414
16055
  pending += 1;
15415
- tail = tail.then(() => writeAll2(file, bytes)).catch((err) => {
16056
+ tail = tail.then(() => writeAll3(file, bytes)).catch((err) => {
15416
16057
  if (failed3 === void 0) failed3 = err;
15417
16058
  }).finally(() => {
15418
16059
  pending -= 1;
@@ -16043,8 +16684,8 @@ var FSA_CAPABILITIES = {
16043
16684
  ranges: true,
16044
16685
  sessions: true
16045
16686
  };
16046
- var codeOf2 = (err) => err?.code;
16047
- var denied = (err) => codeOf2(err) === "EACCES" || err?.name === "NotAllowedError";
16687
+ var codeOf3 = (err) => err?.code;
16688
+ var denied = (err) => codeOf3(err) === "EACCES" || err?.name === "NotAllowedError";
16048
16689
  function attrOf(st) {
16049
16690
  return {
16050
16691
  kind: st.isDirectory ? "directory" : st.isSymbolicLink ? "symlink" : "file",
@@ -16066,7 +16707,7 @@ function kernelFsHandlers(fs) {
16066
16707
  try {
16067
16708
  return await fs.stat(path);
16068
16709
  } catch (err) {
16069
- if (codeOf2(err) === "ENOENT") return void 0;
16710
+ if (codeOf3(err) === "ENOENT") return void 0;
16070
16711
  throw err;
16071
16712
  }
16072
16713
  }
@@ -16259,7 +16900,7 @@ function parseFstab(text) {
16259
16900
  });
16260
16901
  return lines2;
16261
16902
  }
16262
- function codeOf3(err) {
16903
+ function codeOf4(err) {
16263
16904
  const code = err?.code;
16264
16905
  return typeof code === "string" ? code : "EIO";
16265
16906
  }
@@ -16297,18 +16938,18 @@ async function mountLine({ line, spec, invalid }, mount, signal, retries, discar
16297
16938
  break;
16298
16939
  } catch (err) {
16299
16940
  failed3 = err;
16300
- if (FINAL.has(codeOf3(err)) || attempt === retries.length) break;
16941
+ if (FINAL.has(codeOf4(err)) || attempt === retries.length) break;
16301
16942
  await wait(retries[attempt], signal);
16302
16943
  }
16303
16944
  }
16304
- const code = codeOf3(failed3);
16945
+ const code = codeOf4(failed3);
16305
16946
  return { line, target: spec.target, code, error: String(failed3?.message ?? code) };
16306
16947
  }
16307
16948
 
16308
16949
  // src/mount/hostfs.ts
16309
16950
  var HOSTFS_IO = 16 * 1024 * 1024;
16310
- var codeOf4 = (err) => err?.code;
16311
- var unreachable = (err) => codeOf4(err) === "ENOTCONN";
16951
+ var codeOf5 = (err) => err?.code;
16952
+ var unreachable = (err) => codeOf5(err) === "ENOTCONN";
16312
16953
  function rel(path) {
16313
16954
  return path.replace(/^\/+/, "");
16314
16955
  }
@@ -16368,9 +17009,9 @@ var HostfsClient = class {
16368
17009
  throw fsError2("ESTALE", "the folder was granted again, so its open files are gone");
16369
17010
  return this.send(path, init, false, true);
16370
17011
  }
16371
- const errno = response.headers.get("X-Hostfs-Errno") ?? "EIO";
17012
+ const errno2 = response.headers.get("X-Hostfs-Errno") ?? "EIO";
16372
17013
  const body = await response.json().catch(() => ({}));
16373
- throw fsError2(errno, typeof body.message === "string" ? body.message : errno);
17014
+ throw fsError2(errno2, typeof body.message === "string" ? body.message : errno2);
16374
17015
  }
16375
17016
  async call(op, body = {}, handle = false) {
16376
17017
  const response = await this.send(
@@ -16485,7 +17126,7 @@ function hostfsHandlers(current, againMs = RESTORE_AGAIN_MS) {
16485
17126
  try {
16486
17127
  return { opened: await opening2(), restore: void 0 };
16487
17128
  } catch (err) {
16488
- if (!flags2.write || codeOf4(err) !== "EACCES") throw err;
17129
+ if (!flags2.write || codeOf5(err) !== "EACCES") throw err;
16489
17130
  const was = await client.call("stat", { path: rel(path) });
16490
17131
  const mode = was.mode ?? 0;
16491
17132
  if (mode & 128) throw err;
@@ -17398,8 +18039,8 @@ var RULE = new RegExp(
17398
18039
  var TERM = /^[A-Za-z0-9._+-]+$/;
17399
18040
  var PLAIN = /^[A-Za-z0-9_@%+=:,./-]+$/;
17400
18041
  var USAGE = "usage: sudo [-n] [-u user] [--] command [args...]\n sudo -l";
17401
- function failed2(errno, error) {
17402
- return { error, errno };
18042
+ function failed2(errno2, error) {
18043
+ return { error, errno: errno2 };
17403
18044
  }
17404
18045
  function unescaped(double) {
17405
18046
  return double.replace(/\\(["\\$`])/g, "$1");
@@ -18010,6 +18651,7 @@ var Launcher = class {
18010
18651
  );
18011
18652
  net = new LoopbackNet();
18012
18653
  cdp;
18654
+ facades;
18013
18655
  ca;
18014
18656
  transport;
18015
18657
  uplink;
@@ -18084,10 +18726,16 @@ var Launcher = class {
18084
18726
  this.modulesDir = options.modules ?? "/node_modules";
18085
18727
  this.appended = appendedRoots(options.commands);
18086
18728
  this.cdp = new CdpHosts(options.cdp);
18729
+ this.facades = new FacadeHosts(options.facades);
18087
18730
  const hostname = options.hostname ?? DEFAULT_HOSTNAME;
18088
18731
  if (!isHostname(hostname)) throw new Error(`not a host name: ${String(hostname)}`);
18089
18732
  this.hostname = hostname;
18090
- this.env = { ...networkEnv(hostname), SLICC_CDP_URL: this.cdp.url, ...options.env };
18733
+ this.env = {
18734
+ ...networkEnv(hostname),
18735
+ SLICC_CDP_URL: this.cdp.url,
18736
+ ...this.facades.env(),
18737
+ ...options.env
18738
+ };
18091
18739
  this.ca = kernelCa(options.caStore ?? memoryCaStore());
18092
18740
  this.transport = replayingHops(options.transport ?? missingTransport());
18093
18741
  this.announcedTraits = JSON.stringify(this.transport.traits);
@@ -18102,6 +18750,7 @@ var Launcher = class {
18102
18750
  ca: this.ca
18103
18751
  });
18104
18752
  enableCdp(this.net, this.cdp);
18753
+ this.facades.enable(this.net);
18105
18754
  const fs = withCommandDirs(
18106
18755
  this.base,
18107
18756
  async () => new Set((await this.commands()).keys()),
@@ -18685,6 +19334,12 @@ var Launcher = class {
18685
19334
  groupSession(pgid) {
18686
19335
  return this.jobs.sessionOf(pgid);
18687
19336
  }
19337
+ loopbackFor(pid) {
19338
+ return new KernelLoopback(this.net, () => {
19339
+ const cred = this.credOf(pid);
19340
+ return { pid, uid: cred.euid, gid: cred.egid };
19341
+ });
19342
+ }
18688
19343
  treeScrubs(req) {
18689
19344
  const inherited2 = req.ppid !== void 0 ? this.scrubs.get(req.ppid) : void 0;
18690
19345
  return req.scrub ? [...inherited2 ?? [], req.scrub] : inherited2;
@@ -18762,7 +19417,7 @@ var Launcher = class {
18762
19417
  jobs: this.jobs,
18763
19418
  ptys: this.ptys,
18764
19419
  net: this.net,
18765
- http: new HttpHandles(this.transport, pid),
19420
+ http: new HttpHandles(this.transport, pid, this.loopbackFor(pid)),
18766
19421
  locks: this.locks,
18767
19422
  resolver: this.resolver,
18768
19423
  onReap: (child) => this.reaped(child, pid),
@@ -19035,6 +19690,9 @@ var Launcher = class {
19035
19690
  }
19036
19691
  };
19037
19692
  }
19693
+ connectorStamp(pid) {
19694
+ return this.sealed.stamp(this.identityOf(pid), this.credOf(pid), this.tagOf(pid));
19695
+ }
19038
19696
  identityOf(pid) {
19039
19697
  const parent = this.jobs.shownParent(pid);
19040
19698
  const alive = parent !== void 0 && this.running(parent);
@@ -19706,6 +20364,20 @@ function cdpOption(req, port) {
19706
20364
  )
19707
20365
  };
19708
20366
  }
20367
+ function pageHooks(req, port) {
20368
+ return { ...cdpOption(req, port), ...facadesOption(req, port) };
20369
+ }
20370
+ function facadesOption(req, port) {
20371
+ if (!req.facades) return {};
20372
+ const hook = portFacadeHook(
20373
+ (request2, facade) => port.postMessage({ facadeOpen: request2, port: facade }, [facade])
20374
+ );
20375
+ const facades = {};
20376
+ for (const [name2, spec] of Object.entries(req.facades)) {
20377
+ facades[name2] = { port: spec.port, ...spec.hook ? { hook } : {} };
20378
+ }
20379
+ return { facades };
20380
+ }
19709
20381
  function remotes(port, req) {
19710
20382
  return {
19711
20383
  ...req.transport ? { transport: new RemoteTransport(port, req.transport) } : {},
@@ -19919,7 +20591,7 @@ function serveKernel(port, deps) {
19919
20591
  ...media ? { media } : {},
19920
20592
  onMountPending: (medium) => port.postMessage({ medium }),
19921
20593
  ...req.hostfs ? { hostfs: askGrant } : {},
19922
- ...cdpOption(req, port),
20594
+ ...pageHooks(req, port),
19923
20595
  ...mountPolicy(req.processMounts),
19924
20596
  caStore: caStore(deps.metadata ? caDb(req) : false)
19925
20597
  });