@ai-ecoverse/slicc-kernel 1.49.1 → 1.51.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");
@@ -9712,7 +9728,8 @@ var WasmProcess = class {
9712
9728
  fds: this.fds,
9713
9729
  net: this.net,
9714
9730
  blocking: () => this.blockingSignal(cancelled),
9715
- 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 })
9716
9733
  });
9717
9734
  }
9718
9735
  tty(fd) {
@@ -9928,12 +9945,12 @@ var Dying = class {
9928
9945
  timer;
9929
9946
  busy = 0;
9930
9947
  finish;
9931
- start(code, sig, header3, process2, finish) {
9948
+ start(code, sig, header4, process2, finish) {
9932
9949
  if (this.ending) return;
9933
9950
  this.ending = { code, sig };
9934
9951
  this.finish = finish;
9935
9952
  this.arm();
9936
- Atomics.store(header3, SAB_I_KILLED, code);
9953
+ Atomics.store(header4, SAB_I_KILLED, code);
9937
9954
  process2.die();
9938
9955
  }
9939
9956
  async serving(work) {
@@ -9970,22 +9987,22 @@ function killThread(thread, caught, sig) {
9970
9987
  function threadCancel(thread) {
9971
9988
  return Atomics.load(thread.header, SAB_I_SIGNALS) !== 0 ? AbortSignal.abort() : thread.interrupt.signal;
9972
9989
  }
9973
- function notifiers(header3) {
9990
+ function notifiers(header4) {
9974
9991
  return {
9975
9992
  onPending(sig) {
9976
- Atomics.or(header3, SAB_I_SIGNALS, sigbit(sig));
9977
- Atomics.notify(header3, SAB_I_SIGNALS);
9993
+ Atomics.or(header4, SAB_I_SIGNALS, sigbit(sig));
9994
+ Atomics.notify(header4, SAB_I_SIGNALS);
9978
9995
  },
9979
9996
  onAsync() {
9980
- Atomics.add(header3, SAB_I_ASYNC, 1);
9981
- Atomics.notify(header3, SAB_I_ASYNC);
9997
+ Atomics.add(header4, SAB_I_ASYNC, 1);
9998
+ Atomics.notify(header4, SAB_I_ASYNC);
9982
9999
  }
9983
10000
  };
9984
10001
  }
9985
- function publishStops(process2, header3) {
10002
+ function publishStops(process2, header4) {
9986
10003
  process2.onState((state) => {
9987
- Atomics.store(header3, SAB_I_STOP, state === "stopped" ? 1 : 0);
9988
- Atomics.notify(header3, SAB_I_STOP);
10004
+ Atomics.store(header4, SAB_I_STOP, state === "stopped" ? 1 : 0);
10005
+ Atomics.notify(header4, SAB_I_STOP);
9989
10006
  });
9990
10007
  }
9991
10008
  function transferOf(opts, lane) {
@@ -10053,7 +10070,7 @@ function holdingToken(opts, holds) {
10053
10070
  renamed: (from, to) => holds.renamed(from, to)
10054
10071
  });
10055
10072
  }
10056
- function processOptions(opts, header3, hooks) {
10073
+ function processOptions(opts, header4, hooks) {
10057
10074
  return {
10058
10075
  ...inherited(opts),
10059
10076
  ...opts.identity ? { identity: opts.identity } : {},
@@ -10077,27 +10094,27 @@ function processOptions(opts, header3, hooks) {
10077
10094
  locks: opts.locks,
10078
10095
  onReap: opts.onReap,
10079
10096
  resolver: opts.resolver,
10080
- ...notifiers(header3),
10081
- onTimer: (which) => void Atomics.or(header3, SAB_I_TIMERS, 1 << which),
10082
- 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,
10083
10100
  raise: hooks.raise,
10084
10101
  threadKill: hooks.threadKill,
10085
- ...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) } : {}
10086
10103
  };
10087
10104
  }
10088
10105
  function spawnWasmProcess(opts) {
10089
10106
  const sab = new SharedArrayBuffer(SAB_HEADER_BYTES + SAB_DEFAULT_WINDOW_BYTES);
10090
- const header3 = new Int32Array(sab, 0, SAB_HEADER_I32);
10107
+ const header4 = new Int32Array(sab, 0, SAB_HEADER_I32);
10091
10108
  const threadSignals = /* @__PURE__ */ new Map();
10092
10109
  const process2 = new WasmProcess(
10093
10110
  opts.pid,
10094
10111
  opts.fds,
10095
- processOptions(opts, header3, {
10112
+ processOptions(opts, header4, {
10096
10113
  raise: (sig) => signal(sig),
10097
10114
  threadKill: (tid, sig) => killThread(threadSignals.get(tid), process2.signalMasks().caught, sig)
10098
10115
  })
10099
10116
  );
10100
- publishStops(process2, header3);
10117
+ publishStops(process2, header4);
10101
10118
  const holds = new HeldPaths();
10102
10119
  opts.held?.add(holds);
10103
10120
  const token = holdingToken(opts, holds);
@@ -10209,7 +10226,7 @@ function spawnWasmProcess(opts) {
10209
10226
  if (process2.signal(sig) !== "terminate") return;
10210
10227
  const code = sig === SIG.KILL ? 137 : 128 + sig;
10211
10228
  if (opts.program.abi !== "wasi" || done4) finish(code, sig);
10212
- else dying.start(code, sig, header3, process2, () => finish(code, sig));
10229
+ else dying.start(code, sig, header4, process2, () => finish(code, sig));
10213
10230
  };
10214
10231
  const kill = (code = 137) => finish(code, code > 128 && code < 160 ? code - 128 : void 0);
10215
10232
  return {
@@ -10219,7 +10236,7 @@ function spawnWasmProcess(opts) {
10219
10236
  signal,
10220
10237
  termsig: () => endedBy,
10221
10238
  onState: (listener) => process2.onState(listener),
10222
- memory: () => Atomics.load(header3, SAB_I_MEMORY) * 65536,
10239
+ memory: () => Atomics.load(header4, SAB_I_MEMORY) * 65536,
10223
10240
  ignoredSignals: (fork) => process2.inheritable(fork),
10224
10241
  signalMasks: () => process2.signalMasks(),
10225
10242
  umask: () => process2.umask,
@@ -10414,6 +10431,89 @@ var JobTable = class {
10414
10431
  }
10415
10432
  };
10416
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
+
10417
10517
  // src/kernel/settling.ts
10418
10518
  var SETUP_OPS = /* @__PURE__ */ new Set([
10419
10519
  "proc-list",
@@ -11839,7 +11939,7 @@ var Launcher = class {
11839
11939
  this.hostname = hostname;
11840
11940
  this.env = { ...networkEnv(hostname), SLICC_CDP_URL: this.cdp.url, ...options.env };
11841
11941
  this.ca = kernelCa(options.caStore ?? memoryCaStore());
11842
- this.transport = options.transport ?? missingTransport();
11942
+ this.transport = replayingHops(options.transport ?? missingTransport());
11843
11943
  this.uplink = options.uplink;
11844
11944
  this.routes = new Routes(options.uplink?.traits.ipv6 === true);
11845
11945
  if (options.uplink?.routes) this.routes.set(options.uplink.routes);
@@ -12096,7 +12196,7 @@ var Launcher = class {
12096
12196
  jobs: this.jobs,
12097
12197
  ptys: this.ptys,
12098
12198
  net: this.net,
12099
- http: new HttpHandles(this.transport),
12199
+ http: new HttpHandles(this.transport, pid),
12100
12200
  locks: this.locks,
12101
12201
  resolver: this.resolver,
12102
12202
  onReap: (child) => this.reaped(child),
@@ -12866,7 +12966,7 @@ var RemoteTransport = class {
12866
12966
  finish();
12867
12967
  throw e;
12868
12968
  }
12869
- const { status, statusText, headers, url, redirected } = head;
12969
+ const { status, statusText, headers, url, redirected, redirects } = head;
12870
12970
  const read = async () => {
12871
12971
  if (finished) return void 0;
12872
12972
  const reply = await this.ask({ net: "read", nid }).catch((e) => {
@@ -12884,6 +12984,7 @@ var RemoteTransport = class {
12884
12984
  };
12885
12985
  return {
12886
12986
  ...redirected ? { url, redirected } : {},
12987
+ ...redirects ? { redirects } : {},
12887
12988
  status,
12888
12989
  statusText,
12889
12990
  headers,
@@ -13083,7 +13184,7 @@ function parseHead(bytes) {
13083
13184
  }
13084
13185
  return { status: Number(match[1]), statusText: match[2] ?? "", headers };
13085
13186
  }
13086
- function header2(head, name2) {
13187
+ function header3(head, name2) {
13087
13188
  return head.headers.find(([key]) => key.toLowerCase() === name2)?.[1];
13088
13189
  }
13089
13190
  async function* chunked(source) {
@@ -13112,8 +13213,8 @@ async function* untilClose(source) {
13112
13213
  }
13113
13214
  function bodyOf(source, head, method) {
13114
13215
  if (method === "HEAD" || head.status === 204 || head.status === 304) return void 0;
13115
- if (header2(head, "transfer-encoding")?.toLowerCase().includes("chunked")) return chunked(source);
13116
- 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");
13117
13218
  if (length2 === void 0) return untilClose(source);
13118
13219
  const size = Number(length2);
13119
13220
  if (!/^\d+$/.test(length2.trim()) || !Number.isSafeInteger(size)) {
@@ -13567,7 +13668,7 @@ function hostfsFetchOf(options) {
13567
13668
  };
13568
13669
  }
13569
13670
  function nodeTransport() {
13570
- const transport = fetchTransport({ webSocket: true, webSocketHeaders: true });
13671
+ const transport = fetchTransport({ webSocket: true, webSocketHeaders: true, redirect: "manual" });
13571
13672
  return { ...transport, traits: { ...transport.traits, crossOrigin: "any" } };
13572
13673
  }
13573
13674
  var TERMINATED = "the kernel is terminated";