@ai-ecoverse/slicc-kernel 1.49.0 → 1.50.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/node.js CHANGED
@@ -715,6 +715,8 @@ var KernelSocket = class _KernelSocket {
715
715
  reset = false;
716
716
  local;
717
717
  peer;
718
+ peerCred;
719
+ listenerCred;
718
720
  link;
719
721
  readShut = false;
720
722
  writeShut = false;
@@ -790,7 +792,7 @@ var KernelSocket = class _KernelSocket {
790
792
  this.local = this.net.bind(this, addr);
791
793
  this.boundAt = this.local;
792
794
  }
793
- listen(backlog) {
795
+ listen(backlog, cred) {
794
796
  if (this.state === "connected") throw new KernelError("EISCONN");
795
797
  if (this.state !== "open" && this.state !== "listening") throw new KernelError("EINVAL");
796
798
  if (!this.local) {
@@ -799,6 +801,7 @@ var KernelSocket = class _KernelSocket {
799
801
  }
800
802
  this.backlog = Math.min(Math.max(backlog, 1), SOMAXCONN);
801
803
  this.state = "listening";
804
+ this.listenerCred ??= cred;
802
805
  }
803
806
  async accept(signal, nonblock = false) {
804
807
  for (; ; ) {
@@ -815,7 +818,7 @@ var KernelSocket = class _KernelSocket {
815
818
  this.changes.wake();
816
819
  return true;
817
820
  }
818
- connect(addr, viaUplink = true) {
821
+ connect(addr, viaUplink = true, cred) {
819
822
  if (this.state === "connected") throw new KernelError("EISCONN");
820
823
  if (this.state === "connecting") throw new KernelError("EALREADY");
821
824
  if (this.state !== "open") throw new KernelError("EINVAL");
@@ -833,7 +836,9 @@ var KernelSocket = class _KernelSocket {
833
836
  const server = new _KernelSocket(this.net, this.domain);
834
837
  server.local = addr.family === "inet" ? { ...addr, host: canonicalHost(addr.host) } : addr;
835
838
  server.peer = this.local;
839
+ server.peerCred = cred;
836
840
  this.peer = server.local;
841
+ this.peerCred = target.listenerCred;
837
842
  link(this, server);
838
843
  if (!target.offer(server)) {
839
844
  this.unlink();
@@ -952,9 +957,10 @@ var KernelSocket = class _KernelSocket {
952
957
  this.boundAt = void 0;
953
958
  this.changes.wake();
954
959
  }
955
- static pair(net, domain) {
960
+ static pair(net, domain, cred) {
956
961
  const a = new _KernelSocket(net, domain);
957
962
  const b = new _KernelSocket(net, domain);
963
+ a.peerCred = b.peerCred = cred;
958
964
  const name2 = domain === "inet" ? { family: "inet", host: "127.0.0.1", port: 0 } : { family: "unix", path: "" };
959
965
  a.local = b.local = a.peer = b.peer = name2;
960
966
  link(a, b);
@@ -1366,9 +1372,10 @@ function fetchTransport(options = {}) {
1366
1372
  const send = options.fetch ?? globalThis.fetch.bind(globalThis);
1367
1373
  const Socket = options.webSocket === true ? globalThis.WebSocket : options.webSocket || void 0;
1368
1374
  const headers = options.webSocketHeaders === true;
1375
+ const redirect = options.redirect ?? "follow";
1369
1376
  return {
1370
1377
  traits: {
1371
- manualRedirects: false,
1378
+ manualRedirects: redirect === "manual",
1372
1379
  encodedBodies: false,
1373
1380
  maxRequestBody: options.maxRequestBody ?? MAX_REQUEST_BODY,
1374
1381
  crossOrigin: "cors",
@@ -1383,7 +1390,7 @@ function fetchTransport(options = {}) {
1383
1390
  headers: joinHeaders(request.headers),
1384
1391
  ...request.body ? { body: request.body } : {},
1385
1392
  signal: request.signal,
1386
- redirect: "follow",
1393
+ redirect,
1387
1394
  credentials: "omit",
1388
1395
  mode: "cors"
1389
1396
  });
@@ -1449,6 +1456,7 @@ function serveTransport(peer, transport, { closeWaitMs = CLOSE_WAIT_MS } = {}) {
1449
1456
  net: "head",
1450
1457
  nid: call.nid,
1451
1458
  ...response.redirected ? { url: response.url, redirected: true } : {},
1459
+ ...response.redirects ? { redirects: response.redirects } : {},
1452
1460
  status: response.status,
1453
1461
  statusText: response.statusText,
1454
1462
  headers: response.headers
@@ -5712,9 +5720,9 @@ var SAB_I_KILLED = 13;
5712
5720
  var SAB_I_ASYNC = 14;
5713
5721
  var SAB_I_STOP = 15;
5714
5722
  var U32 = 2 ** 32;
5715
- function storeU53(header3, lo, hi, value) {
5716
- Atomics.store(header3, hi, Math.floor(value / U32));
5717
- Atomics.store(header3, lo, value % U32 | 0);
5723
+ function storeU53(header4, lo, hi, value) {
5724
+ Atomics.store(header4, hi, Math.floor(value / U32));
5725
+ Atomics.store(header4, lo, value % U32 | 0);
5718
5726
  }
5719
5727
  var SAB_STATE_READY = 2;
5720
5728
  var SAB_STATUS_BYTES = 1;
@@ -5757,7 +5765,7 @@ function sabViews(sab) {
5757
5765
  var PENDING_TTL_MS = SYNC_FS_REQUEST_TIMEOUT_MS + 5e3;
5758
5766
  function attachSyncSabResponder(port, sab, token, opts) {
5759
5767
  const views = sabViews(sab);
5760
- const { header: header3, window } = views;
5768
+ const { header: header4, window } = views;
5761
5769
  const pending = /* @__PURE__ */ new Map();
5762
5770
  let disposed = false;
5763
5771
  const { dispatch: dispatch2 } = opts;
@@ -5768,20 +5776,20 @@ function attachSyncSabResponder(port, sab, token, opts) {
5768
5776
  pending.delete(id2);
5769
5777
  }
5770
5778
  function publish(id2, entry, offset) {
5771
- if (Atomics.load(header3, SAB_I_REQ) !== id2) {
5779
+ if (Atomics.load(header4, SAB_I_REQ) !== id2) {
5772
5780
  drop(id2);
5773
5781
  return;
5774
5782
  }
5775
5783
  const chunk2 = Math.min(window.byteLength, entry.payload.byteLength - offset);
5776
5784
  if (chunk2 > 0) window.set(entry.payload.subarray(offset, offset + chunk2));
5777
- Atomics.store(header3, SAB_I_STATUS, entry.status);
5778
- storeU53(header3, SAB_I_TOTAL, SAB_I_TOTAL_HI, entry.payload.byteLength);
5779
- Atomics.store(header3, SAB_I_CHUNK, chunk2);
5780
- storeU53(header3, SAB_I_OFFSET, SAB_I_OFFSET_HI, offset);
5781
- Atomics.store(header3, SAB_I_SEQ, id2);
5782
- Atomics.store(header3, SAB_I_STATE, SAB_STATE_READY);
5783
- Atomics.add(header3, SAB_I_PUB, 1);
5784
- Atomics.notify(header3, SAB_I_PUB);
5785
+ Atomics.store(header4, SAB_I_STATUS, entry.status);
5786
+ storeU53(header4, SAB_I_TOTAL, SAB_I_TOTAL_HI, entry.payload.byteLength);
5787
+ Atomics.store(header4, SAB_I_CHUNK, chunk2);
5788
+ storeU53(header4, SAB_I_OFFSET, SAB_I_OFFSET_HI, offset);
5789
+ Atomics.store(header4, SAB_I_SEQ, id2);
5790
+ Atomics.store(header4, SAB_I_STATE, SAB_STATE_READY);
5791
+ Atomics.add(header4, SAB_I_PUB, 1);
5792
+ Atomics.notify(header4, SAB_I_PUB);
5785
5793
  if (offset + chunk2 >= entry.payload.byteLength) drop(id2);
5786
5794
  }
5787
5795
  function settle(id2, result) {
@@ -6378,7 +6386,7 @@ var RealmProxy = class {
6378
6386
  const waiting = new AbortController();
6379
6387
  void watchHangup(ctx.socket, abort, waiting.signal);
6380
6388
  try {
6381
- response = await this.upstream(req, url, body, abort.signal);
6389
+ response = await this.upstream(req, url, body, abort.signal, ctx.socket.peerCred?.pid);
6382
6390
  } finally {
6383
6391
  waiting.abort();
6384
6392
  }
@@ -6404,15 +6412,18 @@ var RealmProxy = class {
6404
6412
  await conn.write(latin1Bytes("HTTP/1.1 100 Continue\r\n\r\n"), this.stop.signal);
6405
6413
  }
6406
6414
  }
6407
- async upstream(req, url, body, signal) {
6415
+ async upstream(req, url, body, signal, owner) {
6408
6416
  try {
6409
- const fetching = this.options.transport.fetch({
6410
- url: toHostLoopback(url),
6411
- method: req.method.toUpperCase(),
6412
- headers: forwardRequestHeaders(req.headers),
6413
- body: req.method === "GET" || req.method === "HEAD" ? void 0 : body,
6414
- signal
6415
- });
6417
+ const fetching = this.options.transport.fetch(
6418
+ {
6419
+ url: toHostLoopback(url),
6420
+ method: req.method.toUpperCase(),
6421
+ headers: forwardRequestHeaders(req.headers),
6422
+ body: req.method === "GET" || req.method === "HEAD" ? void 0 : body,
6423
+ signal
6424
+ },
6425
+ owner
6426
+ );
6416
6427
  return await untilAborted(fetching, signal);
6417
6428
  } catch (e) {
6418
6429
  if (signal.aborted) throw e;
@@ -6424,13 +6435,13 @@ var RealmProxy = class {
6424
6435
  }
6425
6436
  async relay(conn, method, minor, response, keepAlive) {
6426
6437
  const status = response.status;
6427
- const bodiless = method === "HEAD" || status === 204 || status === 304 || status < 200;
6438
+ const bodiless2 = method === "HEAD" || status === 204 || status === 304 || status < 200;
6428
6439
  const headers = forwardResponseHeaders(response.headers, {
6429
6440
  encodedBodies: this.options.transport.traits.encodedBodies,
6430
- bodiless
6441
+ bodiless: bodiless2
6431
6442
  });
6432
- const chunked2 = !bodiless && minor >= 1;
6433
- const keep = keepAlive && (bodiless || chunked2);
6443
+ const chunked2 = !bodiless2 && minor >= 1;
6444
+ const keep = keepAlive && (bodiless2 || chunked2);
6434
6445
  headers.push(["Connection", keep ? "keep-alive" : "close"]);
6435
6446
  if (chunked2) headers.push(["Transfer-Encoding", "chunked"]);
6436
6447
  const signal = this.stop.signal;
@@ -6438,7 +6449,7 @@ var RealmProxy = class {
6438
6449
  responseHead(status, response.statusText || REASON[status] || "", headers),
6439
6450
  signal
6440
6451
  );
6441
- if (bodiless) {
6452
+ if (bodiless2) {
6442
6453
  await response.cancel();
6443
6454
  return keep;
6444
6455
  }
@@ -7401,8 +7412,10 @@ var HttpHandles = class {
7401
7412
  open = /* @__PURE__ */ new Map();
7402
7413
  nextHandle = 0;
7403
7414
  ws;
7404
- constructor(transport) {
7415
+ owner;
7416
+ constructor(transport, owner) {
7405
7417
  this.transport = transport;
7418
+ this.owner = owner;
7406
7419
  this.ws = new WsHandles(transport, () => ++this.nextHandle);
7407
7420
  }
7408
7421
  syscall(req) {
@@ -7446,13 +7459,16 @@ var HttpHandles = class {
7446
7459
  }
7447
7460
  async respond(handle, req, entry) {
7448
7461
  try {
7449
- const response = await this.transport.fetch({
7450
- url: req.url,
7451
- method: req.method,
7452
- headers: req.headers,
7453
- ...req.body ? { body: req.body } : {},
7454
- signal: entry.abort.signal
7455
- });
7462
+ const response = await this.transport.fetch(
7463
+ {
7464
+ url: req.url,
7465
+ method: req.method,
7466
+ headers: req.headers,
7467
+ ...req.body ? { body: req.body } : {},
7468
+ signal: entry.abort.signal
7469
+ },
7470
+ this.owner
7471
+ );
7456
7472
  entry.body = response.body[Symbol.asyncIterator]();
7457
7473
  entry.cancel = () => response.cancel();
7458
7474
  if (!this.open.has(handle)) await entry.cancel().catch(() => void 0);
@@ -8278,7 +8294,7 @@ async function socketSyscall(req, proc) {
8278
8294
  case "sock-open":
8279
8295
  return ok(fds.install(new OpenFile(net.socket(req.domain)), 3));
8280
8296
  case "sock-pair": {
8281
- const [a, b] = KernelSocket.pair(net, req.domain);
8297
+ const [a, b] = KernelSocket.pair(net, req.domain, proc.cred?.());
8282
8298
  const first = fds.install(new OpenFile(a), 3);
8283
8299
  try {
8284
8300
  return ok([first, fds.install(new OpenFile(b), 3)]);
@@ -8291,12 +8307,12 @@ async function socketSyscall(req, proc) {
8291
8307
  socketAt(fds, req.fd).bind(req.addr);
8292
8308
  return ok();
8293
8309
  case "sock-listen":
8294
- socketAt(fds, req.fd).listen(req.backlog);
8310
+ socketAt(fds, req.fd).listen(req.backlog, proc.cred?.());
8295
8311
  return ok();
8296
8312
  case "sock-accept":
8297
8313
  return ok(await accept(proc, req.fd, req.nonblock));
8298
8314
  case "sock-connect": {
8299
- const dialing = socketAt(fds, req.fd).connect(req.addr);
8315
+ const dialing = socketAt(fds, req.fd).connect(req.addr, true, proc.cred?.());
8300
8316
  if (req.nonblock) {
8301
8317
  dialing?.catch(() => void 0);
8302
8318
  throw new KernelError("EINPROGRESS");
@@ -9167,6 +9183,7 @@ var SYSCALL_OPS = /* @__PURE__ */ new Set([
9167
9183
  "dl-log",
9168
9184
  "dl-closure",
9169
9185
  "dl-closure-free",
9186
+ "dl-reserve",
9170
9187
  "fd-renumber",
9171
9188
  "fd-promote",
9172
9189
  "fd-open-tty",
@@ -9711,7 +9728,8 @@ var WasmProcess = class {
9711
9728
  fds: this.fds,
9712
9729
  net: this.net,
9713
9730
  blocking: () => this.blockingSignal(cancelled),
9714
- resolve: (name2, family, blocking) => resolver.resolve(name2, family, blocking)
9731
+ resolve: (name2, family, blocking) => resolver.resolve(name2, family, blocking),
9732
+ cred: () => ({ pid: this.pid, uid: this.cred.euid, gid: this.cred.egid })
9715
9733
  });
9716
9734
  }
9717
9735
  tty(fd) {
@@ -9788,6 +9806,8 @@ var WasmProcess = class {
9788
9806
  case "dl-closure-free":
9789
9807
  this.freeClosure(req.index);
9790
9808
  return { ok: true, kind: "void" };
9809
+ case "dl-reserve":
9810
+ return { ok: true, kind: "json", json: this.reserveSlots(req.floor, req.size) };
9791
9811
  case "sig-mask":
9792
9812
  this.caught = req.caught;
9793
9813
  this.ignored = req.ignored;
@@ -9835,6 +9855,12 @@ var WasmProcess = class {
9835
9855
  freeClosure(index) {
9836
9856
  if (!this.closures.live.delete(index)) throw new KernelError("EINVAL");
9837
9857
  this.closures.free.push(index);
9858
+ this.dlLog.push({ kind: "closure-free", index });
9859
+ }
9860
+ reserveSlots(floor, size) {
9861
+ const base = Math.max(floor, this.closures.next);
9862
+ this.closures.next = base + size;
9863
+ return base;
9838
9864
  }
9839
9865
  asyncOps = new AsyncOps(
9840
9866
  (req, cancelled) => this.syscall(req, cancelled),
@@ -9919,12 +9945,12 @@ var Dying = class {
9919
9945
  timer;
9920
9946
  busy = 0;
9921
9947
  finish;
9922
- start(code, sig, header3, process2, finish) {
9948
+ start(code, sig, header4, process2, finish) {
9923
9949
  if (this.ending) return;
9924
9950
  this.ending = { code, sig };
9925
9951
  this.finish = finish;
9926
9952
  this.arm();
9927
- Atomics.store(header3, SAB_I_KILLED, code);
9953
+ Atomics.store(header4, SAB_I_KILLED, code);
9928
9954
  process2.die();
9929
9955
  }
9930
9956
  async serving(work) {
@@ -9961,22 +9987,22 @@ function killThread(thread, caught, sig) {
9961
9987
  function threadCancel(thread) {
9962
9988
  return Atomics.load(thread.header, SAB_I_SIGNALS) !== 0 ? AbortSignal.abort() : thread.interrupt.signal;
9963
9989
  }
9964
- function notifiers(header3) {
9990
+ function notifiers(header4) {
9965
9991
  return {
9966
9992
  onPending(sig) {
9967
- Atomics.or(header3, SAB_I_SIGNALS, sigbit(sig));
9968
- Atomics.notify(header3, SAB_I_SIGNALS);
9993
+ Atomics.or(header4, SAB_I_SIGNALS, sigbit(sig));
9994
+ Atomics.notify(header4, SAB_I_SIGNALS);
9969
9995
  },
9970
9996
  onAsync() {
9971
- Atomics.add(header3, SAB_I_ASYNC, 1);
9972
- Atomics.notify(header3, SAB_I_ASYNC);
9997
+ Atomics.add(header4, SAB_I_ASYNC, 1);
9998
+ Atomics.notify(header4, SAB_I_ASYNC);
9973
9999
  }
9974
10000
  };
9975
10001
  }
9976
- function publishStops(process2, header3) {
10002
+ function publishStops(process2, header4) {
9977
10003
  process2.onState((state) => {
9978
- Atomics.store(header3, SAB_I_STOP, state === "stopped" ? 1 : 0);
9979
- Atomics.notify(header3, SAB_I_STOP);
10004
+ Atomics.store(header4, SAB_I_STOP, state === "stopped" ? 1 : 0);
10005
+ Atomics.notify(header4, SAB_I_STOP);
9980
10006
  });
9981
10007
  }
9982
10008
  function transferOf(opts, lane) {
@@ -10044,7 +10070,7 @@ function holdingToken(opts, holds) {
10044
10070
  renamed: (from, to) => holds.renamed(from, to)
10045
10071
  });
10046
10072
  }
10047
- function processOptions(opts, header3, hooks) {
10073
+ function processOptions(opts, header4, hooks) {
10048
10074
  return {
10049
10075
  ...inherited(opts),
10050
10076
  ...opts.identity ? { identity: opts.identity } : {},
@@ -10068,27 +10094,27 @@ function processOptions(opts, header3, hooks) {
10068
10094
  locks: opts.locks,
10069
10095
  onReap: opts.onReap,
10070
10096
  resolver: opts.resolver,
10071
- ...notifiers(header3),
10072
- onTimer: (which) => void Atomics.or(header3, SAB_I_TIMERS, 1 << which),
10073
- hasPending: () => Atomics.load(header3, SAB_I_SIGNALS) !== 0 || Atomics.load(header3, SAB_I_TIMERS) !== 0,
10097
+ ...notifiers(header4),
10098
+ onTimer: (which) => void Atomics.or(header4, SAB_I_TIMERS, 1 << which),
10099
+ hasPending: () => Atomics.load(header4, SAB_I_SIGNALS) !== 0 || Atomics.load(header4, SAB_I_TIMERS) !== 0,
10074
10100
  raise: hooks.raise,
10075
10101
  threadKill: hooks.threadKill,
10076
- ...opts.program.abi === "wasi" || opts.program.abi === "js" ? { pendingBits: () => Atomics.load(header3, SAB_I_SIGNALS) } : {}
10102
+ ...opts.program.abi === "wasi" || opts.program.abi === "js" ? { pendingBits: () => Atomics.load(header4, SAB_I_SIGNALS) } : {}
10077
10103
  };
10078
10104
  }
10079
10105
  function spawnWasmProcess(opts) {
10080
10106
  const sab = new SharedArrayBuffer(SAB_HEADER_BYTES + SAB_DEFAULT_WINDOW_BYTES);
10081
- const header3 = new Int32Array(sab, 0, SAB_HEADER_I32);
10107
+ const header4 = new Int32Array(sab, 0, SAB_HEADER_I32);
10082
10108
  const threadSignals = /* @__PURE__ */ new Map();
10083
10109
  const process2 = new WasmProcess(
10084
10110
  opts.pid,
10085
10111
  opts.fds,
10086
- processOptions(opts, header3, {
10112
+ processOptions(opts, header4, {
10087
10113
  raise: (sig) => signal(sig),
10088
10114
  threadKill: (tid, sig) => killThread(threadSignals.get(tid), process2.signalMasks().caught, sig)
10089
10115
  })
10090
10116
  );
10091
- publishStops(process2, header3);
10117
+ publishStops(process2, header4);
10092
10118
  const holds = new HeldPaths();
10093
10119
  opts.held?.add(holds);
10094
10120
  const token = holdingToken(opts, holds);
@@ -10200,7 +10226,7 @@ function spawnWasmProcess(opts) {
10200
10226
  if (process2.signal(sig) !== "terminate") return;
10201
10227
  const code = sig === SIG.KILL ? 137 : 128 + sig;
10202
10228
  if (opts.program.abi !== "wasi" || done4) finish(code, sig);
10203
- else dying.start(code, sig, header3, process2, () => finish(code, sig));
10229
+ else dying.start(code, sig, header4, process2, () => finish(code, sig));
10204
10230
  };
10205
10231
  const kill = (code = 137) => finish(code, code > 128 && code < 160 ? code - 128 : void 0);
10206
10232
  return {
@@ -10210,7 +10236,7 @@ function spawnWasmProcess(opts) {
10210
10236
  signal,
10211
10237
  termsig: () => endedBy,
10212
10238
  onState: (listener) => process2.onState(listener),
10213
- memory: () => Atomics.load(header3, SAB_I_MEMORY) * 65536,
10239
+ memory: () => Atomics.load(header4, SAB_I_MEMORY) * 65536,
10214
10240
  ignoredSignals: (fork) => process2.inheritable(fork),
10215
10241
  signalMasks: () => process2.signalMasks(),
10216
10242
  umask: () => process2.umask,
@@ -10405,6 +10431,89 @@ var JobTable = class {
10405
10431
  }
10406
10432
  };
10407
10433
 
10434
+ // src/kernel/net/redirect-hops.ts
10435
+ var HELD_MS = 3e4;
10436
+ var HELD_MAX = 64;
10437
+ var HELD_PER_OWNER = 8;
10438
+ var MATCHED = ["range", "if-none-match", "if-modified-since", "if-range", "accept-encoding"];
10439
+ var KERNEL = -1;
10440
+ function header2(headers, name2) {
10441
+ const values = headers.filter(([n]) => n.toLowerCase() === name2).map(([, v]) => v);
10442
+ return values.length > 0 ? values.join(", ") : void 0;
10443
+ }
10444
+ function fingerprint(headers) {
10445
+ const fields = MATCHED.map((name2) => header2(headers, name2) ?? "");
10446
+ return JSON.stringify([...fields, header2(headers, "authorization") !== void 0]);
10447
+ }
10448
+ function followedWith(status, method) {
10449
+ if (status === 303 && method !== "GET" && method !== "HEAD") return "GET";
10450
+ if ((status === 301 || status === 302) && method === "POST") return "GET";
10451
+ return method;
10452
+ }
10453
+ function bodiless(hop) {
10454
+ return {
10455
+ url: hop.url,
10456
+ status: hop.status,
10457
+ statusText: "",
10458
+ headers: hop.headers,
10459
+ body: (async function* () {
10460
+ })(),
10461
+ cancel: async () => void 0
10462
+ };
10463
+ }
10464
+ function replayingHops(inner, heldMs = HELD_MS) {
10465
+ if (!inner.traits.redirectHops || inner.traits.manualRedirects) return inner;
10466
+ const held = /* @__PURE__ */ new Map();
10467
+ const drop = (key) => {
10468
+ const entry = held.get(key);
10469
+ if (!entry) return;
10470
+ held.delete(key);
10471
+ clearTimeout(entry.timer);
10472
+ void entry.final.cancel().catch(() => void 0);
10473
+ };
10474
+ const keyOf2 = (owner, method, url) => `${owner} ${method} ${url}`;
10475
+ const hold = (key, chain) => {
10476
+ drop(key);
10477
+ const mine = [...held.keys()].filter((k) => held.get(k)?.owner === chain.owner);
10478
+ if (mine.length >= HELD_PER_OWNER) drop(mine[0]);
10479
+ if (held.size >= HELD_MAX) drop(held.keys().next().value);
10480
+ const timer = setTimeout(() => drop(key), heldMs);
10481
+ timer.unref?.();
10482
+ held.set(key, { ...chain, timer });
10483
+ };
10484
+ const replay = (chain) => {
10485
+ const [hop, ...rest2] = chain.hops;
10486
+ const location = hop && header2(hop.headers, "location");
10487
+ if (!hop || location === void 0) return chain.final;
10488
+ const next2 = new URL(location, hop.url).href;
10489
+ const method = followedWith(hop.status, chain.method);
10490
+ hold(keyOf2(chain.owner, method, next2), { ...chain, method, hops: rest2 });
10491
+ return bodiless(hop);
10492
+ };
10493
+ return {
10494
+ traits: { ...inner.traits, manualRedirects: true },
10495
+ ...inner.websocket ? { websocket: inner.websocket.bind(inner) } : {},
10496
+ async fetch(request, owner = KERNEL) {
10497
+ const key = keyOf2(owner, request.method, request.url);
10498
+ const waiting = held.get(key);
10499
+ if (waiting && waiting.fingerprint === fingerprint(request.headers)) {
10500
+ held.delete(key);
10501
+ clearTimeout(waiting.timer);
10502
+ return replay(waiting);
10503
+ }
10504
+ const response = await inner.fetch(request);
10505
+ if (!response.redirects?.length) return response;
10506
+ return replay({
10507
+ owner,
10508
+ method: request.method,
10509
+ fingerprint: fingerprint(request.headers),
10510
+ hops: response.redirects,
10511
+ final: response
10512
+ });
10513
+ }
10514
+ };
10515
+ }
10516
+
10408
10517
  // src/kernel/settling.ts
10409
10518
  var SETUP_OPS = /* @__PURE__ */ new Set([
10410
10519
  "proc-list",
@@ -11830,7 +11939,7 @@ var Launcher = class {
11830
11939
  this.hostname = hostname;
11831
11940
  this.env = { ...networkEnv(hostname), SLICC_CDP_URL: this.cdp.url, ...options.env };
11832
11941
  this.ca = kernelCa(options.caStore ?? memoryCaStore());
11833
- this.transport = options.transport ?? missingTransport();
11942
+ this.transport = replayingHops(options.transport ?? missingTransport());
11834
11943
  this.uplink = options.uplink;
11835
11944
  this.routes = new Routes(options.uplink?.traits.ipv6 === true);
11836
11945
  if (options.uplink?.routes) this.routes.set(options.uplink.routes);
@@ -12087,7 +12196,7 @@ var Launcher = class {
12087
12196
  jobs: this.jobs,
12088
12197
  ptys: this.ptys,
12089
12198
  net: this.net,
12090
- http: new HttpHandles(this.transport),
12199
+ http: new HttpHandles(this.transport, pid),
12091
12200
  locks: this.locks,
12092
12201
  resolver: this.resolver,
12093
12202
  onReap: (child) => this.reaped(child),
@@ -12857,7 +12966,7 @@ var RemoteTransport = class {
12857
12966
  finish();
12858
12967
  throw e;
12859
12968
  }
12860
- const { status, statusText, headers, url, redirected } = head;
12969
+ const { status, statusText, headers, url, redirected, redirects } = head;
12861
12970
  const read = async () => {
12862
12971
  if (finished) return void 0;
12863
12972
  const reply = await this.ask({ net: "read", nid }).catch((e) => {
@@ -12875,6 +12984,7 @@ var RemoteTransport = class {
12875
12984
  };
12876
12985
  return {
12877
12986
  ...redirected ? { url, redirected } : {},
12987
+ ...redirects ? { redirects } : {},
12878
12988
  status,
12879
12989
  statusText,
12880
12990
  headers,
@@ -13074,7 +13184,7 @@ function parseHead(bytes) {
13074
13184
  }
13075
13185
  return { status: Number(match[1]), statusText: match[2] ?? "", headers };
13076
13186
  }
13077
- function header2(head, name2) {
13187
+ function header3(head, name2) {
13078
13188
  return head.headers.find(([key]) => key.toLowerCase() === name2)?.[1];
13079
13189
  }
13080
13190
  async function* chunked(source) {
@@ -13103,8 +13213,8 @@ async function* untilClose(source) {
13103
13213
  }
13104
13214
  function bodyOf(source, head, method) {
13105
13215
  if (method === "HEAD" || head.status === 204 || head.status === 304) return void 0;
13106
- if (header2(head, "transfer-encoding")?.toLowerCase().includes("chunked")) return chunked(source);
13107
- const length2 = header2(head, "content-length");
13216
+ if (header3(head, "transfer-encoding")?.toLowerCase().includes("chunked")) return chunked(source);
13217
+ const length2 = header3(head, "content-length");
13108
13218
  if (length2 === void 0) return untilClose(source);
13109
13219
  const size = Number(length2);
13110
13220
  if (!/^\d+$/.test(length2.trim()) || !Number.isSafeInteger(size)) {
@@ -13558,7 +13668,7 @@ function hostfsFetchOf(options) {
13558
13668
  };
13559
13669
  }
13560
13670
  function nodeTransport() {
13561
- const transport = fetchTransport({ webSocket: true, webSocketHeaders: true });
13671
+ const transport = fetchTransport({ webSocket: true, webSocketHeaders: true, redirect: "manual" });
13562
13672
  return { ...transport, traits: { ...transport.traits, crossOrigin: "any" } };
13563
13673
  }
13564
13674
  var TERMINATED = "the kernel is terminated";