infinitecampus-mcp 3.1.0 → 3.1.2

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/bundle.js CHANGED
@@ -25548,7 +25548,7 @@ var require_websocket = __commonJS({
25548
25548
  var http = __require("http");
25549
25549
  var net = __require("net");
25550
25550
  var tls = __require("tls");
25551
- var { randomBytes: randomBytes3, createHash } = __require("crypto");
25551
+ var { randomBytes: randomBytes4, createHash } = __require("crypto");
25552
25552
  var { Duplex, Readable } = __require("stream");
25553
25553
  var { URL: URL2 } = __require("url");
25554
25554
  var PerMessageDeflate2 = require_permessage_deflate();
@@ -26086,7 +26086,7 @@ var require_websocket = __commonJS({
26086
26086
  }
26087
26087
  }
26088
26088
  const defaultPort = isSecure ? 443 : 80;
26089
- const key = randomBytes3(16).toString("base64");
26089
+ const key = randomBytes4(16).toString("base64");
26090
26090
  const request = isSecure ? https.request : http.request;
26091
26091
  const protocolSet = /* @__PURE__ */ new Set();
26092
26092
  let perMessageDeflate;
@@ -26983,6 +26983,20 @@ var require_websocket_server = __commonJS({
26983
26983
  import { dirname as dirname3, join as join4 } from "path";
26984
26984
  import { fileURLToPath } from "url";
26985
26985
 
26986
+ // node_modules/@chrischall/mcp-utils/dist/cancel/index.js
26987
+ import { AsyncLocalStorage } from "node:async_hooks";
26988
+ var storage = new AsyncLocalStorage();
26989
+ function withCallSignal(signal, fn, request) {
26990
+ return signal ? storage.run({ signal, ...request ? { request } : {} }, fn) : fn();
26991
+ }
26992
+
26993
+ // node_modules/@chrischall/mcp-utils/dist/caller/index.js
26994
+ import { AsyncLocalStorage as AsyncLocalStorage2 } from "node:async_hooks";
26995
+ var storage2 = new AsyncLocalStorage2();
26996
+ function withCallerCapabilities(capabilities, fn) {
26997
+ return capabilities ? storage2.run(capabilities, fn) : fn();
26998
+ }
26999
+
26986
27000
  // node_modules/@modelcontextprotocol/server/dist/chunk-Br0eD_fh.mjs
26987
27001
  var __create2 = Object.create;
26988
27002
  var __defProp2 = Object.defineProperty;
@@ -41659,13 +41673,28 @@ var API_KEY_RE = new RegExp([
41659
41673
  "whsec_[A-Za-z0-9]{16,}\\b"
41660
41674
  // webhook signing secret (Stripe-style)
41661
41675
  ].map((p) => `\\b${p}`).join("|"), "g");
41662
- var QUERY_SECRET_RE = /([?&](?:access_token|refresh_token|client_secret|api_?key|signature|token|key|sig)=)[^&#\s"'<>`]+/gi;
41676
+ var QUERY_SECRET_RE = /([?&](?:(?:access|refresh|id|auth|session|csrf|xsrf)[_-]?token|client[_-]?secret|api[_-]?secret|api[_-]?key|password|passwd|signature|token|key|sig)=)[^&#\s"'<>`]+/gi;
41677
+ var OAUTH_CODE_RE = /([?&]code=)(?=[^&#\s"'<>`]{16,})[^&#\s"'<>`]+/gi;
41678
+ var FORM_PASSWORD_RE = /((?:^|[\s,;:"'(\[{])(?:password|passwd)=)[^&#\s"'<>`]+/gim;
41679
+ var HEADER_SECRET_RE = /(\b(?:x[-_][a-z0-9_-]*?(?:api[-_]?key|token|secret|auth)[a-z0-9_-]*|api[-_]?key)\s*:\s*)[^\s,;"'<>`]+/gi;
41663
41680
  var AWS_SIGV4_RE = /([?&]X-Amz-(?:Signature|Security-Token|Credential)=)[^&#\s"'<>`]+/gi;
41664
- var JSON_SECRET_KEYS = "access_token|refresh_token|client_secret|api_?key|password|passwd|secret|token";
41665
- var JSON_SECRET_DQ_RE = new RegExp(`("(?:${JSON_SECRET_KEYS})"\\s*:\\s*")[^"]*(")`, "gi");
41666
- var JSON_SECRET_SQ_RE = new RegExp(`('(?:${JSON_SECRET_KEYS})'\\s*:\\s*')[^']*(')`, "gi");
41681
+ var JSON_SECRET_KEYS = [
41682
+ "(?:access|refresh|id|auth|session|bearer|csrf|xsrf)[_-]?token",
41683
+ "client[_-]?secret",
41684
+ "api[_-]?secret",
41685
+ "(?:x[_-])?api[_-]?key",
41686
+ "private[_-]?key",
41687
+ "x[_-][a-z0-9_-]*?(?:api[_-]?key|token|secret|auth)[a-z0-9_-]*",
41688
+ "(?:proxy-)?authorization",
41689
+ "password",
41690
+ "passwd",
41691
+ "secret",
41692
+ "token"
41693
+ ].join("|");
41694
+ var JSON_SECRET_DQ_RE = new RegExp(`("(?:${JSON_SECRET_KEYS})"\\s*:\\s*")(?:[^"\\\\]|\\\\.)*(")`, "gi");
41695
+ var JSON_SECRET_SQ_RE = new RegExp(`('(?:${JSON_SECRET_KEYS})'\\s*:\\s*')(?:[^'\\\\]|\\\\.)*(')`, "gi");
41667
41696
  function redactSecrets(text) {
41668
- return text.replace(BEARER_RE, "$1[REDACTED]").replace(BASIC_AUTH_RE, "$1[REDACTED]").replace(SET_COOKIE_RE, "$1$2=[REDACTED]").replace(COOKIE_HEADER_RE, (_m, prefix, pairs) => `${prefix}${pairs.replace(/=[^;,\s]*/g, "=[REDACTED]")}`).replace(API_KEY_RE, "[REDACTED]").replace(QUERY_SECRET_RE, "$1[REDACTED]").replace(AWS_SIGV4_RE, "$1[REDACTED]").replace(JSON_SECRET_DQ_RE, "$1[REDACTED]$2").replace(JSON_SECRET_SQ_RE, "$1[REDACTED]$2").replace(JWT_RE, "[REDACTED]");
41697
+ return text.replace(BEARER_RE, "$1[REDACTED]").replace(BASIC_AUTH_RE, "$1[REDACTED]").replace(SET_COOKIE_RE, "$1$2=[REDACTED]").replace(COOKIE_HEADER_RE, (_m, prefix, pairs) => `${prefix}${pairs.replace(/=[^;,\s]*/g, "=[REDACTED]")}`).replace(API_KEY_RE, "[REDACTED]").replace(HEADER_SECRET_RE, "$1[REDACTED]").replace(QUERY_SECRET_RE, "$1[REDACTED]").replace(OAUTH_CODE_RE, "$1[REDACTED]").replace(FORM_PASSWORD_RE, "$1[REDACTED]").replace(AWS_SIGV4_RE, "$1[REDACTED]").replace(JSON_SECRET_DQ_RE, "$1[REDACTED]$2").replace(JSON_SECRET_SQ_RE, "$1[REDACTED]$2").replace(JWT_RE, "[REDACTED]");
41669
41698
  }
41670
41699
  function truncateErrorMessage(text, max = DEFAULT_ERROR_MESSAGE_MAX) {
41671
41700
  const str = text === null || text === void 0 ? "" : String(text);
@@ -61444,17 +61473,73 @@ Hint: ${err.hint}`);
61444
61473
  }
61445
61474
  throw err;
61446
61475
  }
61476
+ function mcpRequestFrom(args) {
61477
+ const ctx = args.at(-1);
61478
+ if (typeof ctx !== "object" || ctx === null)
61479
+ return void 0;
61480
+ const req = ctx.mcpReq;
61481
+ if (typeof req !== "object" || req === null)
61482
+ return void 0;
61483
+ return typeof req.notify === "function" ? req : void 0;
61484
+ }
61485
+ function callSignalFrom(args) {
61486
+ const ctx = args.at(-1);
61487
+ if (typeof ctx !== "object" || ctx === null)
61488
+ return void 0;
61489
+ const req = ctx.mcpReq;
61490
+ if (typeof req !== "object" || req === null)
61491
+ return void 0;
61492
+ const signal = req.signal;
61493
+ return signal instanceof AbortSignal ? signal : void 0;
61494
+ }
61495
+ function declaredCapabilitiesFrom(server) {
61496
+ const inner = server.server;
61497
+ if (typeof inner !== "object" || inner === null)
61498
+ return void 0;
61499
+ const read = inner.getClientCapabilities;
61500
+ if (typeof read !== "function")
61501
+ return void 0;
61502
+ try {
61503
+ const declared = read.call(inner);
61504
+ return typeof declared === "object" && declared !== null ? declared : void 0;
61505
+ } catch {
61506
+ return void 0;
61507
+ }
61508
+ }
61447
61509
  function surfaceToolHints(server) {
61448
61510
  const register2 = server.registerTool.bind(server);
61449
- server.registerTool = (name, config2, cb) => register2(name, config2, (...args) => {
61450
- let result;
61451
- try {
61452
- result = cb(...args);
61453
- } catch (err) {
61454
- return hintResultOrRethrow(err);
61455
- }
61456
- return result instanceof Promise ? result.catch(hintResultOrRethrow) : result;
61457
- });
61511
+ server.registerTool = (name, config2, cb) => register2(name, config2, (...args) => (
61512
+ // THE CALLER'S CANCELLATION, made ambient for the whole handler
61513
+ // (`cancel/index.ts`). The SDK delivers it and the fleet ignored it:
61514
+ // measured against @modelcontextprotocol/server 2.0.0, a cancelled
61515
+ // call aborts `ctx.mcpReq.signal` with the caller's reason and the
61516
+ // handler runs to completion regardless — so the HTTP request stays
61517
+ // in flight, the child keeps burning metered CPU, and the upstream
61518
+ // keeps being hit for somebody who has gone. claude.ai sent 101
61519
+ // cancellations in the week to 2026-09-20.
61520
+ //
61521
+ // Here because this is the one wrapper every tool already passes
61522
+ // through: threading the signal by hand would mean editing several
61523
+ // hundred handlers and missing exactly the ones nobody edits.
61524
+ withCallSignal(callSignalFrom(args), () => (
61525
+ // WHAT THE CALLER CAN DO, made ambient for the same reason and in
61526
+ // the same place (`caller/index.ts`). Only the 2025-era half needs
61527
+ // the wrapper — a 2026-07-28 request answers for itself off its own
61528
+ // envelope — but a guarded tool that cannot tell whether the caller
61529
+ // can be shown a prompt returns one the SDK then refuses to deliver,
61530
+ // and that refusal reaches the caller as an unexplained protocol
61531
+ // error it had no chance to catch.
61532
+ withCallerCapabilities(declaredCapabilitiesFrom(server), () => {
61533
+ let result;
61534
+ try {
61535
+ result = cb(...args);
61536
+ } catch (err) {
61537
+ return hintResultOrRethrow(err);
61538
+ }
61539
+ return result instanceof Promise ? result.catch(hintResultOrRethrow) : result;
61540
+ })
61541
+ ), mcpRequestFrom(args))
61542
+ ));
61458
61543
  }
61459
61544
  async function createMcpServer(opts) {
61460
61545
  const server = new McpServer({ name: opts.name, version: opts.version }, { supportedProtocolVersions: [...SERVER_PROTOCOL_VERSIONS] });
@@ -61469,13 +61554,47 @@ async function createMcpServer(opts) {
61469
61554
  }
61470
61555
  return server;
61471
61556
  }
61557
+ var DEFAULT_SHUTDOWN_TIMEOUT_MS = 5e3;
61558
+ var DEFAULT_REPEAT_SIGNAL_GRACE_MS = 500;
61472
61559
  function withGracefulShutdown(target, opts = {}) {
61473
61560
  const shouldExit = opts.exit ?? true;
61561
+ if (opts.timeoutMs !== void 0 && !(Number.isFinite(opts.timeoutMs) && opts.timeoutMs > 0)) {
61562
+ throw new RangeError("withGracefulShutdown: timeoutMs must be a finite number greater than 0.");
61563
+ }
61564
+ const timeoutMs = opts.timeoutMs ?? DEFAULT_SHUTDOWN_TIMEOUT_MS;
61565
+ if (opts.repeatSignalGraceMs !== void 0 && !(Number.isFinite(opts.repeatSignalGraceMs) && opts.repeatSignalGraceMs >= 0)) {
61566
+ throw new RangeError("withGracefulShutdown: repeatSignalGraceMs must be a finite number >= 0.");
61567
+ }
61568
+ const repeatGraceMs = opts.repeatSignalGraceMs ?? DEFAULT_REPEAT_SIGNAL_GRACE_MS;
61474
61569
  let shuttingDown = false;
61570
+ let firstSignalAt = 0;
61571
+ let exited = false;
61572
+ const exitOnce = () => {
61573
+ if (exited)
61574
+ return;
61575
+ exited = true;
61576
+ process.exit(0);
61577
+ };
61475
61578
  const handler = (signal) => {
61476
- if (shuttingDown)
61579
+ if (shuttingDown) {
61580
+ if (Date.now() - firstSignalAt < repeatGraceMs)
61581
+ return;
61582
+ if (shouldExit) {
61583
+ console.error(`[mcp-utils] second ${signal} during shutdown \u2014 exiting now`);
61584
+ exitOnce();
61585
+ }
61477
61586
  return;
61587
+ }
61478
61588
  shuttingDown = true;
61589
+ firstSignalAt = Date.now();
61590
+ let timer;
61591
+ if (shouldExit) {
61592
+ timer = setTimeout(() => {
61593
+ console.error(`[mcp-utils] graceful shutdown on ${signal} did not finish in ${timeoutMs}ms \u2014 exiting`);
61594
+ exitOnce();
61595
+ }, timeoutMs);
61596
+ timer.unref?.();
61597
+ }
61479
61598
  void (async () => {
61480
61599
  try {
61481
61600
  if (opts.onSignal)
@@ -61484,8 +61603,10 @@ function withGracefulShutdown(target, opts = {}) {
61484
61603
  } catch (err) {
61485
61604
  console.error(`[mcp-utils] error during graceful shutdown on ${signal}: ${err instanceof Error ? err.message : String(err)}`);
61486
61605
  } finally {
61606
+ if (timer !== void 0)
61607
+ clearTimeout(timer);
61487
61608
  if (shouldExit)
61488
- process.exit(0);
61609
+ exitOnce();
61489
61610
  }
61490
61611
  })();
61491
61612
  };
@@ -61633,6 +61754,11 @@ var pageSchema = {
61633
61754
  page_size: external_exports.number().int().min(1).max(200).default(50).describe("Number of items per page (1-200).")
61634
61755
  };
61635
61756
 
61757
+ // node_modules/@chrischall/mcp-utils/dist/internal/ascii.js
61758
+ function asciiLower(s) {
61759
+ return s.replace(/[A-Z]+/g, (m) => m.toLowerCase());
61760
+ }
61761
+
61636
61762
  // node_modules/@fetchproxy/protocol/dist/crypto.js
61637
61763
  var subtle = globalThis.crypto.subtle;
61638
61764
  async function exportRaw(key, format) {
@@ -61830,6 +61956,7 @@ var KNOWN_CAPABILITIES = /* @__PURE__ */ new Set([
61830
61956
  "capture_redirect",
61831
61957
  "read_indexed_db",
61832
61958
  "read_dom",
61959
+ "read_dom_list",
61833
61960
  "download",
61834
61961
  "graphql",
61835
61962
  "write_cookies"
@@ -62330,6 +62457,79 @@ function assertDomSelectorsArray(value, label) {
62330
62457
  }
62331
62458
  }
62332
62459
  }
62460
+ function assertDomListSelectorsArray(value, label) {
62461
+ if (!Array.isArray(value)) {
62462
+ throw new ProtocolError3(`${label}: expected array, got ${typeof value}`);
62463
+ }
62464
+ const seen = /* @__PURE__ */ new Set();
62465
+ for (let i = 0; i < value.length; i++) {
62466
+ const entry = value[i];
62467
+ assertObject(entry, `${label}[${i}]`);
62468
+ if (entry.name === void 0) {
62469
+ throw new ProtocolError3(`${label}[${i}].name: missing`);
62470
+ }
62471
+ if (entry.itemSelector === void 0) {
62472
+ throw new ProtocolError3(`${label}[${i}].itemSelector: missing`);
62473
+ }
62474
+ if (entry.fields === void 0) {
62475
+ throw new ProtocolError3(`${label}[${i}].fields: missing`);
62476
+ }
62477
+ if (typeof entry.name !== "string" || !SCOPE_KEY_RE.test(entry.name)) {
62478
+ throw new ProtocolError3(`${label}[${i}].name: invalid ${JSON.stringify(entry.name)}`);
62479
+ }
62480
+ if (typeof entry.itemSelector !== "string" || !DOM_SELECTOR_RE.test(entry.itemSelector)) {
62481
+ throw new ProtocolError3(`${label}[${i}].itemSelector: invalid ${JSON.stringify(entry.itemSelector)}`);
62482
+ }
62483
+ if (!Array.isArray(entry.fields) || entry.fields.length === 0) {
62484
+ throw new ProtocolError3(`${label}[${i}].fields: expected non-empty array`);
62485
+ }
62486
+ const fieldNames = /* @__PURE__ */ new Set();
62487
+ for (let j = 0; j < entry.fields.length; j++) {
62488
+ const field = entry.fields[j];
62489
+ const fieldLabel = `${label}[${i}].fields[${j}]`;
62490
+ assertObject(field, fieldLabel);
62491
+ if (field.name === void 0) {
62492
+ throw new ProtocolError3(`${fieldLabel}.name: missing`);
62493
+ }
62494
+ if (typeof field.name !== "string" || !SCOPE_KEY_RE.test(field.name)) {
62495
+ throw new ProtocolError3(`${fieldLabel}.name: invalid ${JSON.stringify(field.name)}`);
62496
+ }
62497
+ if (field.selector !== void 0) {
62498
+ if (typeof field.selector !== "string" || !DOM_SELECTOR_RE.test(field.selector)) {
62499
+ throw new ProtocolError3(`${fieldLabel}.selector: invalid ${JSON.stringify(field.selector)}`);
62500
+ }
62501
+ }
62502
+ if (field.attribute !== void 0) {
62503
+ if (typeof field.attribute !== "string" || !DOM_ATTRIBUTE_RE.test(field.attribute)) {
62504
+ throw new ProtocolError3(`${fieldLabel}.attribute: invalid ${JSON.stringify(field.attribute)}`);
62505
+ }
62506
+ }
62507
+ if (fieldNames.has(field.name)) {
62508
+ throw new ProtocolError3(`${label}[${i}].fields: duplicate name ${JSON.stringify(field.name)}`);
62509
+ }
62510
+ fieldNames.add(field.name);
62511
+ for (const k of Object.keys(field)) {
62512
+ if (k !== "name" && k !== "selector" && k !== "attribute") {
62513
+ throw new ProtocolError3(`${fieldLabel}: unexpected field ${JSON.stringify(k)}`);
62514
+ }
62515
+ }
62516
+ }
62517
+ if (entry.maxItems !== void 0) {
62518
+ if (typeof entry.maxItems !== "number" || !Number.isInteger(entry.maxItems) || entry.maxItems < 1 || entry.maxItems > 1e3) {
62519
+ throw new ProtocolError3(`${label}[${i}].maxItems: invalid ${JSON.stringify(entry.maxItems)}`);
62520
+ }
62521
+ }
62522
+ if (seen.has(entry.name)) {
62523
+ throw new ProtocolError3(`${label}: duplicate name ${JSON.stringify(entry.name)}`);
62524
+ }
62525
+ seen.add(entry.name);
62526
+ for (const k of Object.keys(entry)) {
62527
+ if (k !== "name" && k !== "itemSelector" && k !== "fields" && k !== "maxItems") {
62528
+ throw new ProtocolError3(`${label}[${i}]: unexpected field ${JSON.stringify(k)}`);
62529
+ }
62530
+ }
62531
+ }
62532
+ }
62333
62533
  function assertGraphqlOpsArray(value, label) {
62334
62534
  if (!Array.isArray(value)) {
62335
62535
  throw new ProtocolError3(`${label}: expected array, got ${typeof value}`);
@@ -62376,6 +62576,8 @@ function validateFrame(raw) {
62376
62576
  return validateHelloRejected(raw);
62377
62577
  if (t === "extension-disconnected")
62378
62578
  return { type: "extension-disconnected" };
62579
+ if (t === "peer-gone")
62580
+ return validatePeerGone(raw);
62379
62581
  throw new ProtocolError3(`unknown frame type: ${String(t)}`);
62380
62582
  }
62381
62583
  function validateHello(raw) {
@@ -62446,16 +62648,11 @@ function validateHello(raw) {
62446
62648
  if (raw.domSelectors !== void 0) {
62447
62649
  assertDomSelectorsArray(raw.domSelectors, "hello.domSelectors");
62448
62650
  }
62449
- if (raw.accepts !== void 0) {
62450
- if (!Array.isArray(raw.accepts)) {
62451
- throw new ProtocolError3("hello.accepts: expected array");
62452
- }
62453
- for (const a of raw.accepts) {
62454
- if (typeof a !== "string") {
62455
- throw new ProtocolError3(`hello.accepts: entry must be string, got ${typeof a}`);
62456
- }
62457
- }
62651
+ if (raw.domListSelectors !== void 0) {
62652
+ assertDomListSelectorsArray(raw.domListSelectors, "hello.domListSelectors");
62458
62653
  }
62654
+ if (raw.accepts !== void 0)
62655
+ assertAcceptsList(raw.accepts);
62459
62656
  if (raw.graphqlOps !== void 0) {
62460
62657
  assertGraphqlOpsArray(raw.graphqlOps, "hello.graphqlOps");
62461
62658
  }
@@ -62477,6 +62674,8 @@ function validateHello(raw) {
62477
62674
  assertBase64(raw.identityX25519Pub, "hello.identityX25519Pub");
62478
62675
  assertBase64(raw.identityEd25519Pub, "hello.identityEd25519Pub");
62479
62676
  assertBase64(raw.sessionNonce, "hello.sessionNonce");
62677
+ if (raw.accepts !== void 0)
62678
+ assertAcceptsList(raw.accepts);
62480
62679
  return raw;
62481
62680
  }
62482
62681
  throw new ProtocolError3(`hello.role: must be 'server' or 'extension', got ${String(role)}`);
@@ -62532,6 +62731,27 @@ function validatePairPending(raw) {
62532
62731
  }
62533
62732
  return { type: "pair-pending", mcpId: raw.mcpId, pairCode: raw.pairCode };
62534
62733
  }
62734
+ function assertAcceptsList(accepts) {
62735
+ if (!Array.isArray(accepts)) {
62736
+ throw new ProtocolError3("hello.accepts: expected array");
62737
+ }
62738
+ for (const a of accepts) {
62739
+ if (typeof a !== "string") {
62740
+ throw new ProtocolError3(`hello.accepts: entry must be string, got ${typeof a}`);
62741
+ }
62742
+ }
62743
+ }
62744
+ function validatePeerGone(raw) {
62745
+ assertString(raw.mcpId, "peer-gone.mcpId");
62746
+ if (!isValidMcpId(raw.mcpId))
62747
+ throw new ProtocolError3("peer-gone.mcpId: invalid format");
62748
+ for (const k of Object.keys(raw)) {
62749
+ if (k !== "type" && k !== "mcpId") {
62750
+ throw new ProtocolError3(`peer-gone: unexpected field ${JSON.stringify(k)}`);
62751
+ }
62752
+ }
62753
+ return { type: "peer-gone", mcpId: raw.mcpId };
62754
+ }
62535
62755
  function validateHelloRejected(raw) {
62536
62756
  assertString(raw.mcpId, "hello-rejected.mcpId");
62537
62757
  if (!isValidMcpId(raw.mcpId))
@@ -62781,6 +63001,24 @@ function validateInnerRequest(raw) {
62781
63001
  }
62782
63002
  return raw;
62783
63003
  }
63004
+ if (raw.op === "read_dom_list") {
63005
+ assertObject(raw.init, "inner.init");
63006
+ if (raw.init.origin === void 0)
63007
+ throw new ProtocolError3("inner.init.origin: missing");
63008
+ if (raw.init.name === void 0)
63009
+ throw new ProtocolError3("inner.init.name: missing");
63010
+ assertHttpsOriginOnly(raw.init.origin, "inner.init.origin");
63011
+ assertString(raw.init.name, "inner.init.name");
63012
+ if (raw.init.name.length === 0) {
63013
+ throw new ProtocolError3("inner.init.name: must be non-empty");
63014
+ }
63015
+ for (const k of Object.keys(raw.init)) {
63016
+ if (k !== "origin" && k !== "name") {
63017
+ throw new ProtocolError3(`inner.init: unexpected field ${JSON.stringify(k)} on read_dom_list`);
63018
+ }
63019
+ }
63020
+ return raw;
63021
+ }
62784
63022
  if (raw.op === "graphql_query") {
62785
63023
  assertObject(raw.init, "inner.init");
62786
63024
  if (raw.init.name === void 0)
@@ -62828,7 +63066,7 @@ function validateInnerRequest(raw) {
62828
63066
  }
62829
63067
  return raw;
62830
63068
  }
62831
- throw new ProtocolError3(`inner.op: must be one of "fetch", "read_cookies", "read_local_storage", "read_session_storage", "capture_request_header", "capture_redirect", "read_indexed_db", "read_dom", "download", "graphql_query", "write_cookies"; got ${JSON.stringify(raw.op)}`);
63069
+ throw new ProtocolError3(`inner.op: must be one of "fetch", "read_cookies", "read_local_storage", "read_session_storage", "capture_request_header", "capture_redirect", "read_indexed_db", "read_dom", "read_dom_list", "download", "graphql_query", "write_cookies"; got ${JSON.stringify(raw.op)}`);
62832
63070
  }
62833
63071
  function assertNonEmptyKeyArray(value, label) {
62834
63072
  if (!Array.isArray(value)) {
@@ -62936,6 +63174,18 @@ function validateInnerResponse(raw) {
62936
63174
  assertStringMap(raw.values, "inner.values");
62937
63175
  return raw;
62938
63176
  }
63177
+ if (op === "read_dom_list") {
63178
+ if (raw.rows === void 0) {
63179
+ throw new ProtocolError3("inner.rows: missing on read_dom_list response");
63180
+ }
63181
+ if (!Array.isArray(raw.rows)) {
63182
+ throw new ProtocolError3("inner.rows: expected array, got " + typeof raw.rows);
63183
+ }
63184
+ for (const [i, row] of raw.rows.entries()) {
63185
+ assertStringMap(row, `inner.rows[${i}]`);
63186
+ }
63187
+ return raw;
63188
+ }
62939
63189
  if (op === "graphql_query") {
62940
63190
  if (raw.data === void 0) {
62941
63191
  throw new ProtocolError3("inner.data: missing on graphql_query response");
@@ -63037,12 +63287,6 @@ async function sealInnerFrame(sessionKey, mcpId, seq, inner, direction) {
63037
63287
  ciphertext: toB64(ct)
63038
63288
  };
63039
63289
  }
63040
- async function openEncryptedFrame(sessionKey, frame, direction) {
63041
- const result = await openEncryptedFrameDetailed(sessionKey, frame, direction);
63042
- if (result.stage === "ok")
63043
- return result.inner;
63044
- throw result.error instanceof Error ? result.error : new Error(String(result.error));
63045
- }
63046
63290
  async function openEncryptedFrameDetailed(sessionKey, frame, direction) {
63047
63291
  let pt;
63048
63292
  try {
@@ -63194,6 +63438,18 @@ async function buildServerHello(opts) {
63194
63438
  ...d.attribute !== void 0 ? { attribute: d.attribute } : {}
63195
63439
  }));
63196
63440
  }
63441
+ if (opts.domListSelectors && opts.domListSelectors.length > 0) {
63442
+ hello.domListSelectors = opts.domListSelectors.map((d) => ({
63443
+ name: d.name,
63444
+ itemSelector: d.itemSelector,
63445
+ fields: d.fields.map((f) => ({
63446
+ name: f.name,
63447
+ ...f.selector !== void 0 ? { selector: f.selector } : {},
63448
+ ...f.attribute !== void 0 ? { attribute: f.attribute } : {}
63449
+ })),
63450
+ ...d.maxItems !== void 0 ? { maxItems: d.maxItems } : {}
63451
+ }));
63452
+ }
63197
63453
  if (opts.graphqlOps && opts.graphqlOps.length > 0) {
63198
63454
  hello.graphqlOps = opts.graphqlOps.map((d) => ({
63199
63455
  name: d.name,
@@ -63362,11 +63618,12 @@ async function awaitSessionReady(ready, opts) {
63362
63618
  }
63363
63619
 
63364
63620
  // node_modules/@fetchproxy/server/dist/extension-trust.js
63365
- import { readFile as readFile2, writeFile as writeFile2, rename as rename2, unlink as unlink2, mkdir as mkdir2, chmod as chmod2 } from "node:fs/promises";
63621
+ import { readFile as readFile2, writeFile as writeFile2, rename as rename2, unlink as unlink2, mkdir as mkdir2, chmod as chmod2, rm as rm2 } from "node:fs/promises";
63622
+ import { randomBytes as randomBytes2 } from "node:crypto";
63366
63623
  import { isAbsolute as isAbsolute2, join as join2 } from "node:path";
63367
63624
 
63368
63625
  // node_modules/@fetchproxy/server/dist/identity.js
63369
- import { readFile, readdir, writeFile, mkdir, chmod, rename, rm, unlink } from "node:fs/promises";
63626
+ import { readFile, readdir, writeFile, mkdir, chmod, rename, rm, unlink, link, lstat } from "node:fs/promises";
63370
63627
  import { randomBytes } from "node:crypto";
63371
63628
  import { isAbsolute, join } from "node:path";
63372
63629
  import { homedir } from "node:os";
@@ -63434,7 +63691,7 @@ async function openIdentityDir(dir) {
63434
63691
  }
63435
63692
  }
63436
63693
  var STAGING_BYTES = 6;
63437
- async function sweepStagingFiles(dir, serverName) {
63694
+ async function sweepStagingFiles(dir, serverName, opts = {}) {
63438
63695
  const base = safeIdentityFileBase(serverName).replace(/[.]/g, "\\.");
63439
63696
  const staging = new RegExp(`^${base}\\.json\\.[0-9a-f]{${STAGING_BYTES * 2}}\\.tmp$`);
63440
63697
  let names;
@@ -63443,35 +63700,80 @@ async function sweepStagingFiles(dir, serverName) {
63443
63700
  } catch {
63444
63701
  return;
63445
63702
  }
63446
- await Promise.all(names.filter((name) => staging.test(name)).map((name) => unlink(join(dir, name)).catch(() => void 0)));
63703
+ const cutoff = opts.olderThanMs === void 0 ? null : Date.now() - opts.olderThanMs;
63704
+ await Promise.all(names.filter((name) => staging.test(name)).map(async (name) => {
63705
+ const path = join(dir, name);
63706
+ if (cutoff !== null) {
63707
+ const st = await lstat(path).catch(() => null);
63708
+ if (st === null || st.mtimeMs > cutoff)
63709
+ return;
63710
+ }
63711
+ await unlink(path).catch(() => void 0);
63712
+ }));
63447
63713
  }
63448
- async function writeIdentityFile(dir, serverName, id) {
63714
+ async function loadOrCreateIdentity(serverName, dir = defaultIdentityDir()) {
63449
63715
  const path = identityFilePath(dir, serverName);
63450
63716
  await openIdentityDir(dir);
63451
- await sweepStagingFiles(dir, serverName);
63452
- const tmp = `${path}.${randomBytes(STAGING_BYTES).toString("hex")}.tmp`;
63453
63717
  try {
63454
- await writeFile(tmp, serializeIdentity(id), { mode: 384, flag: "wx" });
63455
- await chmod(tmp, 384);
63456
- await rename(tmp, path);
63718
+ return parseIdentity(await readFile(path, "utf8"));
63457
63719
  } catch (e) {
63458
- await rm(tmp, { force: true });
63459
- throw e;
63720
+ if (e.code !== "ENOENT" && !(e instanceof SyntaxError))
63721
+ throw e;
63722
+ }
63723
+ let unreadable = null;
63724
+ for (let attempt = 0; attempt < 5; attempt++) {
63725
+ const id = await generateIdentity();
63726
+ if (await publishIdentityIfAbsent(dir, serverName, id))
63727
+ return id;
63728
+ try {
63729
+ return parseIdentity(await readFile(path, "utf8"));
63730
+ } catch (e) {
63731
+ if (e.code === "ENOENT") {
63732
+ continue;
63733
+ }
63734
+ if (!(e instanceof SyntaxError))
63735
+ throw e;
63736
+ unreadable = e;
63737
+ await new Promise((r) => setTimeout(r, 20 * (attempt + 1)));
63738
+ }
63460
63739
  }
63461
- return path;
63740
+ if (unreadable !== null)
63741
+ throw unreadable;
63742
+ throw new Error(`fetchproxy: could not create an identity for ${JSON.stringify(serverName)} in ${dir}`);
63462
63743
  }
63463
- async function loadOrCreateIdentity(serverName, dir = defaultIdentityDir()) {
63744
+ var NO_HARD_LINKS = /* @__PURE__ */ new Set(["EPERM", "ENOTSUP", "EOPNOTSUPP", "ENOSYS"]);
63745
+ async function publishIdentityIfAbsent(dir, serverName, id) {
63464
63746
  const path = identityFilePath(dir, serverName);
63465
- await openIdentityDir(dir);
63747
+ await sweepStagingFiles(dir, serverName, { olderThanMs: 6e4 });
63748
+ const tmp = `${path}.${randomBytes(STAGING_BYTES).toString("hex")}.tmp`;
63466
63749
  try {
63467
- return parseIdentity(await readFile(path, "utf8"));
63750
+ await writeFile(tmp, serializeIdentity(id), { mode: 384, flag: "wx" });
63751
+ await chmod(tmp, 384);
63752
+ try {
63753
+ await link(tmp, path);
63754
+ } catch (e) {
63755
+ const code = e.code;
63756
+ if (code === void 0 || !NO_HARD_LINKS.has(code))
63757
+ throw e;
63758
+ try {
63759
+ await writeFile(path, serializeIdentity(id), { mode: 384, flag: "wx" });
63760
+ } catch (writeErr) {
63761
+ if (writeErr.code !== "EEXIST") {
63762
+ await rm(path, { force: true }).catch(() => void 0);
63763
+ }
63764
+ throw writeErr;
63765
+ }
63766
+ await chmod(path, 384);
63767
+ }
63768
+ return true;
63468
63769
  } catch (e) {
63469
- if (e.code !== "ENOENT")
63470
- throw e;
63770
+ const code = e.code;
63771
+ if (code === "EEXIST" || code === "ENOENT")
63772
+ return false;
63773
+ throw e;
63774
+ } finally {
63775
+ await rm(tmp, { force: true });
63471
63776
  }
63472
- const id = await generateIdentity();
63473
- await writeIdentityFile(dir, serverName, id);
63474
- return id;
63475
63777
  }
63476
63778
 
63477
63779
  // node_modules/@fetchproxy/server/dist/extension-trust.js
@@ -63572,10 +63874,15 @@ async function readExtensionPin(serverName, dir = defaultTrustDir()) {
63572
63874
  async function writeExtensionPin(serverName, pin, dir = defaultTrustDir()) {
63573
63875
  const path = extensionTrustPath(serverName, dir);
63574
63876
  await mkdir2(dir, { recursive: true, mode: 448 });
63575
- const tmp = `${path}.tmp`;
63576
- await writeFile2(tmp, JSON.stringify(pin, null, 2), { mode: 384 });
63577
- await chmod2(tmp, 384);
63578
- await rename2(tmp, path);
63877
+ const tmp = `${path}.${randomBytes2(6).toString("hex")}.tmp`;
63878
+ try {
63879
+ await writeFile2(tmp, JSON.stringify(pin, null, 2), { mode: 384, flag: "wx" });
63880
+ await chmod2(tmp, 384);
63881
+ await rename2(tmp, path);
63882
+ } catch (e) {
63883
+ await rm2(tmp, { force: true });
63884
+ throw e;
63885
+ }
63579
63886
  }
63580
63887
 
63581
63888
  // node_modules/@fetchproxy/server/dist/host.js
@@ -63612,6 +63919,7 @@ function originVerdict(origin, allowLocal) {
63612
63919
  var HANDSHAKE_TIMEOUT_MS = 15e3;
63613
63920
  var MAX_PAYLOAD_BYTES = MAX_FRAME_BYTES;
63614
63921
  var enc4 = new TextEncoder();
63922
+ var OWN_DECRYPT_FAILURES_BEFORE_REHANDSHAKE = 3;
63615
63923
  async function startHost(opts) {
63616
63924
  const allowLocalOrigins = envAllowsLocalOrigins();
63617
63925
  if (allowLocalOrigins) {
@@ -63655,6 +63963,7 @@ async function startHost(opts) {
63655
63963
  localStoragePointers: opts.ownLocalStoragePointers,
63656
63964
  sessionStoragePointers: opts.ownSessionStoragePointers,
63657
63965
  domSelectors: opts.ownDomSelectors,
63966
+ domListSelectors: opts.ownDomListSelectors,
63658
63967
  graphqlOps: opts.ownGraphqlOps
63659
63968
  };
63660
63969
  const generateSessionKeypair = opts.generateSessionKeypair ?? generateX25519;
@@ -63725,6 +64034,31 @@ async function startHost(opts) {
63725
64034
  return null;
63726
64035
  }
63727
64036
  };
64037
+ let ownDecryptFailures = 0;
64038
+ const rehandshakeOwnSession = async (ws) => {
64039
+ const hello = extensionHello;
64040
+ if (extensionWs !== ws || !hello)
64041
+ return;
64042
+ console.warn(`[fetchproxy] ${opts.ownServerName}: ${OWN_DECRYPT_FAILURES_BEFORE_REHANDSHAKE} frames in a row from the extension failed authentication under this MCP's session key \u2014 re-handshaking this MCP's session (the extension stays connected).`);
64043
+ ownSession = dropOwnSessionAndEphemeral();
64044
+ resetSessionPromise();
64045
+ const mintedKeypair = await generateSessionKeypair();
64046
+ const mintedHello = await buildServerHello({
64047
+ ...ownHelloBase,
64048
+ sessionPub: mintedKeypair.publicKey,
64049
+ answersExtNonce: fromB64(hello.sessionNonce)
64050
+ });
64051
+ if (extensionWs !== ws || extensionHello !== hello || ownSession !== null) {
64052
+ mintedKeypair.privateKey.fill(0);
64053
+ return;
64054
+ }
64055
+ installOwnEphemeral({
64056
+ nonce: fromB64(mintedHello.sessionNonce),
64057
+ pub: mintedKeypair.publicKey,
64058
+ priv: mintedKeypair.privateKey
64059
+ });
64060
+ ws.send(JSON.stringify(mintedHello));
64061
+ };
63728
64062
  wss.on("connection", (ws) => {
63729
64063
  let identified = null;
63730
64064
  let peerMcpId = null;
@@ -63915,6 +64249,7 @@ async function startHost(opts) {
63915
64249
  if (extensionWs !== ws)
63916
64250
  return;
63917
64251
  ownSession = new SessionState(key);
64252
+ ownDecryptFailures = 0;
63918
64253
  ownPendingPairCode = null;
63919
64254
  resolveOwnSession(ownSession);
63920
64255
  } else {
@@ -63936,15 +64271,42 @@ async function startHost(opts) {
63936
64271
  }
63937
64272
  if (claim2 !== "ok")
63938
64273
  return;
63939
- let inner;
64274
+ let result;
63940
64275
  try {
63941
- inner = await openEncryptedFrame(session.sessionKey, frame, "e2s");
64276
+ result = await openEncryptedFrameDetailed(session.sessionKey, frame, "e2s");
63942
64277
  } catch (e) {
63943
64278
  session.releaseInboundSeq(frame.seq);
63944
64279
  throw e;
63945
64280
  }
64281
+ if (result.stage === "decrypt-failed") {
64282
+ session.releaseInboundSeq(frame.seq);
64283
+ console.warn(`[fetchproxy] ${opts.ownServerName}: dropped an inbound frame (seq ${frame.seq}) that failed authentication \u2014 most likely a straggler from a previous session.`);
64284
+ if (session === ownSession) {
64285
+ ownDecryptFailures += 1;
64286
+ if (ownDecryptFailures >= OWN_DECRYPT_FAILURES_BEFORE_REHANDSHAKE) {
64287
+ ownDecryptFailures = 0;
64288
+ void rehandshakeOwnSession(ws).catch((e) => console.error(`[fetchproxy] ${opts.ownServerName}: re-handshake failed:`, e));
64289
+ }
64290
+ }
64291
+ return;
64292
+ }
64293
+ if (session === ownSession)
64294
+ ownDecryptFailures = 0;
63946
64295
  session.commitInboundSeq(frame.seq);
63947
- ownInnerListeners.forEach((cb) => cb(inner));
64296
+ if (result.stage === "ok") {
64297
+ ownInnerListeners.forEach((cb) => cb(result.inner));
64298
+ } else {
64299
+ console.error("[fetchproxy] host: received a frame that decrypted OK but failed validation:", result.error);
64300
+ const recoveredId = result.recoveredId;
64301
+ if (recoveredId !== void 0) {
64302
+ ownInnerListeners.forEach((cb) => cb({
64303
+ type: "response",
64304
+ id: recoveredId,
64305
+ ok: false,
64306
+ error: `malformed response failed protocol validation: ${String(result.error)}`
64307
+ }));
64308
+ }
64309
+ }
63948
64310
  } else {
63949
64311
  const slot = peers.get(frame.mcpId);
63950
64312
  if (slot)
@@ -64003,6 +64365,7 @@ async function startHost(opts) {
64003
64365
  rejectOwnSession(new Error("extension disconnected before ready"));
64004
64366
  }
64005
64367
  ownSession = dropOwnSessionAndEphemeral();
64368
+ ownDecryptFailures = 0;
64006
64369
  ownPendingPairCode = null;
64007
64370
  resetSessionPromise();
64008
64371
  disconnectListeners.forEach((cb) => cb());
@@ -64017,8 +64380,15 @@ async function startHost(opts) {
64017
64380
  }
64018
64381
  }
64019
64382
  if (identified === "peer" && peerMcpId) {
64020
- if (peers.get(peerMcpId)?.ws === ws)
64383
+ if (peers.get(peerMcpId)?.ws === ws) {
64021
64384
  peers.delete(peerMcpId);
64385
+ if (extensionWs && extensionHello?.accepts?.includes("peer-gone")) {
64386
+ try {
64387
+ extensionWs.send(JSON.stringify({ type: "peer-gone", mcpId: peerMcpId }));
64388
+ } catch {
64389
+ }
64390
+ }
64391
+ }
64022
64392
  }
64023
64393
  });
64024
64394
  });
@@ -64061,12 +64431,18 @@ async function startHost(opts) {
64061
64431
  }
64062
64432
 
64063
64433
  // node_modules/@fetchproxy/server/dist/peer.js
64434
+ var PEER_DIAL_TIMEOUT_MS = 5e3;
64064
64435
  var enc5 = new TextEncoder();
64436
+ function notSentError() {
64437
+ return new Error("fetchproxy: request not sent \u2014 the connection to the bridge host closed before it went out, so nothing reached the browser. It is safe to retry.");
64438
+ }
64065
64439
  async function startPeer(opts) {
64066
64440
  const ws = new import_websocket.default(`ws://${opts.host}:${opts.port}`, {
64067
64441
  maxPayload: opts.maxPayloadBytes ?? MAX_PAYLOAD_BYTES
64068
64442
  });
64443
+ const dialTimeoutMs = opts.dialTimeoutMs ?? PEER_DIAL_TIMEOUT_MS;
64069
64444
  await new Promise((resolve2, reject) => {
64445
+ let timer;
64070
64446
  const onOpen = () => {
64071
64447
  cleanup();
64072
64448
  resolve2();
@@ -64076,11 +64452,21 @@ async function startPeer(opts) {
64076
64452
  reject(e);
64077
64453
  };
64078
64454
  const cleanup = () => {
64455
+ if (timer !== void 0)
64456
+ clearTimeout(timer);
64079
64457
  ws.off("open", onOpen);
64080
64458
  ws.off("error", onError);
64081
64459
  };
64082
64460
  ws.once("open", onOpen);
64083
64461
  ws.once("error", onError);
64462
+ if (dialTimeoutMs > 0) {
64463
+ timer = setTimeout(() => {
64464
+ cleanup();
64465
+ ws.on("error", () => void 0);
64466
+ ws.terminate();
64467
+ reject(new Error(`fetchproxy: the process holding ${opts.host}:${opts.port} did not answer the WebSocket upgrade within ${dialTimeoutMs}ms \u2014 it may be wedged or not a fetchproxy host. Restart the MCP that owns the port (or free it) and retry.`));
64468
+ }, dialTimeoutMs);
64469
+ }
64084
64470
  });
64085
64471
  ws.on("error", (e) => {
64086
64472
  console.warn(`[fetchproxy] peer: socket error: ${String(e)}`);
@@ -64099,6 +64485,7 @@ async function startPeer(opts) {
64099
64485
  captureHeaders: opts.captureHeaders,
64100
64486
  indexedDbScopes: opts.indexedDbScopes,
64101
64487
  domSelectors: opts.domSelectors,
64488
+ domListSelectors: opts.domListSelectors,
64102
64489
  localStoragePointers: opts.localStoragePointers,
64103
64490
  sessionStoragePointers: opts.sessionStoragePointers,
64104
64491
  graphqlOps: opts.graphqlOps,
@@ -64122,6 +64509,7 @@ async function startPeer(opts) {
64122
64509
  const renegotiateListeners = [];
64123
64510
  const pendingPairListeners = [];
64124
64511
  const closeListeners = [];
64512
+ const extensionDisconnectListeners = [];
64125
64513
  let session = null;
64126
64514
  let sessionEphemeral = null;
64127
64515
  function dropSessionEphemeral() {
@@ -64141,10 +64529,24 @@ async function startPeer(opts) {
64141
64529
  let warnedUnverifiablePairCode = false;
64142
64530
  let resolveFirstReady;
64143
64531
  let rejectFirstReady;
64144
- const sessionPromise = new Promise((resolve2, reject) => {
64145
- resolveFirstReady = resolve2;
64146
- rejectFirstReady = reject;
64147
- });
64532
+ let sessionPromise;
64533
+ let sessionPromiseSettled = false;
64534
+ function resetSessionPromise() {
64535
+ sessionPromiseSettled = false;
64536
+ sessionPromise = new Promise((resolve2, reject) => {
64537
+ resolveFirstReady = (st) => {
64538
+ sessionPromiseSettled = true;
64539
+ resolve2(st);
64540
+ };
64541
+ rejectFirstReady = (e) => {
64542
+ sessionPromiseSettled = true;
64543
+ reject(e);
64544
+ };
64545
+ });
64546
+ sessionPromise.catch(() => {
64547
+ });
64548
+ }
64549
+ resetSessionPromise();
64148
64550
  let extensionHello = null;
64149
64551
  let extensionGone = false;
64150
64552
  let cachedPin = void 0;
@@ -64241,6 +64643,12 @@ async function startPeer(opts) {
64241
64643
  extensionGone = true;
64242
64644
  dropSessionEphemeral();
64243
64645
  pendingPairCode = null;
64646
+ if (session !== null) {
64647
+ session = null;
64648
+ if (sessionPromiseSettled)
64649
+ resetSessionPromise();
64650
+ }
64651
+ extensionDisconnectListeners.forEach((cb) => cb());
64244
64652
  return;
64245
64653
  }
64246
64654
  if (frame.type === "ready" && frame.mcpId === opts.mcpId) {
@@ -64357,19 +64765,30 @@ async function startPeer(opts) {
64357
64765
  }
64358
64766
  closeListeners.forEach((cb) => cb());
64359
64767
  });
64360
- sessionPromise.catch(() => {
64361
- });
64362
64768
  const handle = {
64363
64769
  ws,
64364
- session: sessionPromise,
64770
+ // A getter: `extension-disconnected` replaces the promise (B-BUG-5).
64771
+ get session() {
64772
+ return sessionPromise;
64773
+ },
64365
64774
  sendInner: async (inner) => {
64366
- await awaitSessionReady(sessionPromise, {
64367
- mcpId: opts.mcpId,
64368
- pendingPairCode: () => pendingPairCode
64369
- });
64775
+ try {
64776
+ await awaitSessionReady(sessionPromise, {
64777
+ mcpId: opts.mcpId,
64778
+ pendingPairCode: () => pendingPairCode
64779
+ });
64780
+ } catch (e) {
64781
+ if (ws.readyState !== import_websocket.default.OPEN)
64782
+ throw notSentError();
64783
+ throw e;
64784
+ }
64370
64785
  const s = session;
64786
+ if (s === null)
64787
+ throw new Error("peer: extension disconnected");
64371
64788
  const plaintext = encodeOutboundInnerFrame(opts.mcpId, inner);
64372
64789
  const sealed = await sealInnerFrame(s.sessionKey, opts.mcpId, s.nextOutboundSeq(), plaintext, "s2e");
64790
+ if (ws.readyState !== import_websocket.default.OPEN)
64791
+ throw notSentError();
64373
64792
  ws.send(JSON.stringify(sealed));
64374
64793
  },
64375
64794
  onInner: (cb) => {
@@ -64387,6 +64806,9 @@ async function startPeer(opts) {
64387
64806
  onClose: (cb) => {
64388
64807
  closeListeners.push(cb);
64389
64808
  },
64809
+ onExtensionDisconnect: (cb) => {
64810
+ extensionDisconnectListeners.push(cb);
64811
+ },
64390
64812
  close: () => ws.close()
64391
64813
  };
64392
64814
  return handle;
@@ -64394,15 +64816,15 @@ async function startPeer(opts) {
64394
64816
 
64395
64817
  // node_modules/@fetchproxy/server/dist/error-kind.js
64396
64818
  function classifyFetchError(error62) {
64819
+ if (/^(in-page )?fetch threw:/.test(error62)) {
64820
+ return "tab_fetch_failed";
64821
+ }
64397
64822
  if (/Could not establish connection/i.test(error62) || /Receiving end does not exist/i.test(error62)) {
64398
64823
  return "content_script_unreachable";
64399
64824
  }
64400
64825
  if (/^tab fetch failed:/.test(error62)) {
64401
64826
  return "tab_fetch_failed";
64402
64827
  }
64403
- if (/^(in-page )?fetch threw:/.test(error62)) {
64404
- return "tab_fetch_failed";
64405
- }
64406
64828
  if (/^no tab matching /.test(error62)) {
64407
64829
  return "no_tab";
64408
64830
  }
@@ -64594,6 +65016,17 @@ var FetchproxyTimeoutError = class extends FetchproxyProtocolError {
64594
65016
  * the message says which number that was and where it came from.
64595
65017
  */
64596
65018
  requestedTimeoutMs;
65019
+ /**
65020
+ * B-BUG-1: whether re-sending this request is safe. A timeout only means
65021
+ * the reply did not arrive in time — the extension may already have run the
65022
+ * request in the tab — so this is `false` for a non-idempotent `fetch`
65023
+ * (anything but GET/HEAD/OPTIONS) the caller did not mark `retryOnTimeout`,
65024
+ * for `download` and `writeCookies`, and for `graphqlQuery` unless the
65025
+ * caller marked it `retryOnTimeout` (a declared operation may be a
65026
+ * mutation). `retryOnceOnTimeout` honours it. Defaults to `true` (the read
65027
+ * verbs).
65028
+ */
65029
+ retrySafe;
64597
65030
  constructor(args) {
64598
65031
  const graceMs = args.graceMs ?? VERB_DEADLINE_GRACE_MS;
64599
65032
  const callerChose = args.requestedTimeoutMs !== void 0 && args.timeoutMs === args.requestedTimeoutMs + graceMs;
@@ -64606,6 +65039,7 @@ var FetchproxyTimeoutError = class extends FetchproxyProtocolError {
64606
65039
  this.elapsedMs = args.elapsedMs ?? args.timeoutMs;
64607
65040
  this.retryAttempted = args.retryAttempted ?? false;
64608
65041
  this.requestedTimeoutMs = args.requestedTimeoutMs ?? null;
65042
+ this.retrySafe = args.retrySafe ?? true;
64609
65043
  }
64610
65044
  };
64611
65045
  var SUBDOMAIN_LABEL_RE = /^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$/i;
@@ -64645,6 +65079,32 @@ function assertUrlInDomains(field, url4, domains) {
64645
65079
  throw new Error(`FetchproxyServer: ${field} host "${host}" is outside declared domains [${declared}] \u2014 must be one of them or a subdomain`);
64646
65080
  }
64647
65081
  var DEFAULT_JSON_OK_STATUSES = [200, 201, 202, 204];
65082
+ function parseJsonBody(response) {
65083
+ if (response.status === 204 || response.body.trim() === "")
65084
+ return null;
65085
+ return JSON.parse(response.body);
65086
+ }
65087
+ var TIMEOUT_RETRY_SAFE_METHODS = /* @__PURE__ */ new Set(["GET", "HEAD", "OPTIONS"]);
65088
+ function isRetrySafeOnTimeout(method, retryOnTimeout) {
65089
+ if (retryOnTimeout !== void 0)
65090
+ return retryOnTimeout;
65091
+ return TIMEOUT_RETRY_SAFE_METHODS.has(method.toUpperCase());
65092
+ }
65093
+ function isResendableRequest(inner) {
65094
+ switch (inner.op) {
65095
+ case "fetch":
65096
+ return isRetrySafeOnTimeout(inner.init.method);
65097
+ case "write_cookies":
65098
+ case "download":
65099
+ // A declared GraphQL operation may be a mutation; the server holds only
65100
+ // its name, so it cannot tell. `graphqlQuery({ retryOnTimeout: true })`
65101
+ // passes `resendable` explicitly for a known query.
65102
+ case "graphql_query":
65103
+ return false;
65104
+ default:
65105
+ return true;
65106
+ }
65107
+ }
64648
65108
  var FetchproxyServer = class {
64649
65109
  /**
64650
65110
  * Bridge role. `null` until the first verb call (or an explicit
@@ -64693,12 +65153,22 @@ var FetchproxyServer = class {
64693
65153
  pendingRedirect = /* @__PURE__ */ new Map();
64694
65154
  // 0.4.0+: read_indexed_db awaiters resolve a JSON-typed values map.
64695
65155
  pendingIdb = /* @__PURE__ */ new Map();
65156
+ // 3.1.0+: read_dom_list awaiters resolve an array of field-value rows —
65157
+ // a different shape from `pendingStorage`'s single map, so its own map.
65158
+ pendingDomList = /* @__PURE__ */ new Map();
64696
65159
  // download awaiters resolve the saved-file metadata (path + size + mime).
64697
65160
  pendingDownload = /* @__PURE__ */ new Map();
64698
65161
  // 1.x+: graphql_query awaiters resolve the GraphQL `data` object. Its
64699
65162
  // shape is operation-specific, so the awaiter resolves `unknown` and the
64700
65163
  // caller narrows.
64701
65164
  pendingGraphql = /* @__PURE__ */ new Map();
65165
+ /**
65166
+ * B-BUG-6: the frames of in-flight requests that are safe to send again
65167
+ * if the host that relayed them exits before replying (see
65168
+ * `recoverFromHostLoss`). Keyed by request id; an entry outlives its
65169
+ * request only until the next prune or response.
65170
+ */
65171
+ resendable = /* @__PURE__ */ new Map();
64702
65172
  mcpId = null;
64703
65173
  identity = null;
64704
65174
  // 0.5.3+: in-flight role-election / handle-start promise. Set the
@@ -64744,7 +65214,7 @@ var FetchproxyServer = class {
64744
65214
  }
64745
65215
  for (const c of opts.capabilities) {
64746
65216
  if (!KNOWN_CAPABILITIES.has(c)) {
64747
- throw new Error(`FetchproxyServer: unknown capability ${JSON.stringify(c)} \u2014 known values: ["fetch", "read_cookies"]`);
65217
+ throw new Error(`FetchproxyServer: unknown capability ${JSON.stringify(c)} \u2014 known values: ${JSON.stringify([...KNOWN_CAPABILITIES])}`);
64748
65218
  }
64749
65219
  }
64750
65220
  capabilities = [...opts.capabilities];
@@ -64791,6 +65261,16 @@ var FetchproxyServer = class {
64791
65261
  selector: d.selector,
64792
65262
  ...d.attribute !== void 0 ? { attribute: d.attribute } : {}
64793
65263
  })),
65264
+ domListSelectors: (opts.domListSelectors ?? []).map((d) => ({
65265
+ name: d.name,
65266
+ itemSelector: d.itemSelector,
65267
+ fields: d.fields.map((f) => ({
65268
+ name: f.name,
65269
+ ...f.selector !== void 0 ? { selector: f.selector } : {},
65270
+ ...f.attribute !== void 0 ? { attribute: f.attribute } : {}
65271
+ })),
65272
+ ...d.maxItems !== void 0 ? { maxItems: d.maxItems } : {}
65273
+ })),
64794
65274
  graphqlOps: (opts.graphqlOps ?? []).map((d) => ({
64795
65275
  name: d.name,
64796
65276
  operationName: d.operationName
@@ -64920,6 +65400,7 @@ var FetchproxyServer = class {
64920
65400
  ownLocalStoragePointers: this.opts.localStoragePointers,
64921
65401
  ownSessionStoragePointers: this.opts.sessionStoragePointers,
64922
65402
  ownDomSelectors: this.opts.domSelectors,
65403
+ ownDomListSelectors: this.opts.domListSelectors,
64923
65404
  ownGraphqlOps: this.opts.graphqlOps,
64924
65405
  onPairCode: this.opts.onPairCode,
64925
65406
  extensionTrust: this.extensionTrust()
@@ -64951,6 +65432,7 @@ var FetchproxyServer = class {
64951
65432
  localStoragePointers: this.opts.localStoragePointers,
64952
65433
  sessionStoragePointers: this.opts.sessionStoragePointers,
64953
65434
  domSelectors: this.opts.domSelectors,
65435
+ domListSelectors: this.opts.domListSelectors,
64954
65436
  graphqlOps: this.opts.graphqlOps,
64955
65437
  extensionTrust: this.extensionTrust(),
64956
65438
  requireExtensionIdentity: this.opts.requireExtensionIdentity
@@ -64960,6 +65442,10 @@ var FetchproxyServer = class {
64960
65442
  this.stopKeepalive();
64961
65443
  this.rejectAllPending();
64962
65444
  });
65445
+ this.peerHandle.onExtensionDisconnect(() => {
65446
+ this.stopKeepalive();
65447
+ this.rejectAllPending();
65448
+ });
64963
65449
  this.peerHandle.onPendingPair((code) => {
64964
65450
  this.rejectAllPending(this.pairingErrorMessage(code));
64965
65451
  });
@@ -64971,9 +65457,9 @@ var FetchproxyServer = class {
64971
65457
  if (this.closing || this.peerHandle === null)
64972
65458
  return;
64973
65459
  this.stopKeepalive();
64974
- this.rejectAllPending();
64975
65460
  this.peerHandle = null;
64976
65461
  this.role = null;
65462
+ void this.recoverFromHostLoss();
64977
65463
  });
64978
65464
  }
64979
65465
  }
@@ -64994,7 +65480,7 @@ var FetchproxyServer = class {
64994
65480
  * only when the bridge itself failed (no signed-in tab, extension
64995
65481
  * offline, etc.).
64996
65482
  */
64997
- async fetch(init) {
65483
+ async fetch(init, fetchOpts = {}) {
64998
65484
  await this.ensureConnected();
64999
65485
  const pendingCode = this.currentPendingPairCode();
65000
65486
  if (pendingCode !== null) {
@@ -65006,16 +65492,17 @@ var FetchproxyServer = class {
65006
65492
  retryAttempted: false
65007
65493
  };
65008
65494
  }
65009
- const first = await this._fetchOnceWithTimeout(init);
65495
+ const first = await this._fetchOnceWithTimeout(init, fetchOpts);
65010
65496
  const reviveMs = this.opts.bridgeReviveDelayMs;
65011
65497
  const isColdStartSymptom = !first.ok && (first.kind === "content_script_unreachable" || first.kind === "timeout");
65012
65498
  if (isColdStartSymptom) {
65013
65499
  this.lastEvictionDetectedAt = Date.now();
65014
65500
  }
65015
- if (isColdStartSymptom && reviveMs !== void 0 && reviveMs > 0) {
65501
+ const retryable = isColdStartSymptom && (first.kind === "content_script_unreachable" || isRetrySafeOnTimeout(init.method, fetchOpts.retryOnTimeout));
65502
+ if (retryable && reviveMs !== void 0 && reviveMs > 0) {
65016
65503
  this.lazyReviveAttempts += 1;
65017
65504
  await new Promise((r) => setTimeout(r, reviveMs));
65018
- const second = await this._fetchOnceWithTimeout(init);
65505
+ const second = await this._fetchOnceWithTimeout(init, fetchOpts);
65019
65506
  if (second.ok) {
65020
65507
  this.lazyReviveSuccesses += 1;
65021
65508
  this.recordSuccess();
@@ -65164,13 +65651,16 @@ var FetchproxyServer = class {
65164
65651
  * one of the op maps — request ids are unique) so it doesn't leak until the
65165
65652
  * server closes, then rethrow.
65166
65653
  */
65167
- async sendInnerFrame(inner) {
65654
+ async sendInnerFrame(inner, opts = {}) {
65168
65655
  try {
65169
65656
  if (this.hostHandle) {
65170
65657
  await this.hostHandle.sendOwnInner(inner);
65171
65658
  } else if (this.peerHandle) {
65172
65659
  await this.peerHandle.sendInner(inner);
65173
65660
  }
65661
+ if (inner.type === "request" && (opts.resendable ?? isResendableRequest(inner))) {
65662
+ this.noteResendable(inner);
65663
+ }
65174
65664
  } catch (err) {
65175
65665
  if ("id" in inner && typeof inner.id === "number") {
65176
65666
  const { id } = inner;
@@ -65182,6 +65672,7 @@ var FetchproxyServer = class {
65182
65672
  this.pendingRedirect.delete(id);
65183
65673
  this.pendingDownload.delete(id);
65184
65674
  this.pendingIdb.delete(id);
65675
+ this.pendingDomList.delete(id);
65185
65676
  this.pendingGraphql.delete(id);
65186
65677
  }
65187
65678
  throw err;
@@ -65192,13 +65683,25 @@ var FetchproxyServer = class {
65192
65683
  * On timeout returns the `{ok:false, kind:'timeout'}` envelope —
65193
65684
  * the throwing surface is the convenience methods.
65194
65685
  */
65195
- async _fetchOnceWithTimeout(init) {
65686
+ async _fetchOnceWithTimeout(init, fetchOpts = {}) {
65196
65687
  const id = this.nextRequestId++;
65197
65688
  const inner = { type: "request", id, op: "fetch", init };
65198
65689
  const pending = this.guardPending(new Promise((resolve2) => {
65199
65690
  this.pending.set(id, resolve2);
65200
65691
  }));
65201
- await this.sendInnerFrame(inner);
65692
+ try {
65693
+ await this.sendInnerFrame(inner, {
65694
+ resendable: isRetrySafeOnTimeout(init.method, fetchOpts.retryOnTimeout)
65695
+ });
65696
+ } catch (err) {
65697
+ return {
65698
+ ok: false,
65699
+ error: err instanceof Error ? err.message : String(err),
65700
+ kind: "session_not_ready",
65701
+ retryAttempted: false,
65702
+ cause: err
65703
+ };
65704
+ }
65202
65705
  const timeoutMs = this.opts.fetchTimeoutMs;
65203
65706
  if (timeoutMs === void 0 || timeoutMs <= 0)
65204
65707
  return pending;
@@ -65272,7 +65775,7 @@ var FetchproxyServer = class {
65272
65775
  });
65273
65776
  return pending;
65274
65777
  }
65275
- async _withVerbTimeout(pending, pendingMap, id, url4, requestedTimeoutMs) {
65778
+ async _withVerbTimeout(pending, pendingMap, id, url4, requestedTimeoutMs, retrySafe = true) {
65276
65779
  const transportMs = this.opts.fetchTimeoutMs;
65277
65780
  if (transportMs === void 0 || transportMs <= 0)
65278
65781
  return pending;
@@ -65293,6 +65796,7 @@ var FetchproxyServer = class {
65293
65796
  port: this.opts.port,
65294
65797
  elapsedMs: Date.now() - start,
65295
65798
  retryAttempted: false,
65799
+ retrySafe,
65296
65800
  graceMs,
65297
65801
  ...requestedTimeoutMs !== void 0 ? { requestedTimeoutMs } : {}
65298
65802
  }));
@@ -65310,7 +65814,7 @@ var FetchproxyServer = class {
65310
65814
  * logic re-implemented inline) `captureRequestHeader()` agree on what
65311
65815
  * to throw.
65312
65816
  */
65313
- _typedErrorFor(result, url4, op, retryAttempted) {
65817
+ _typedErrorFor(result, url4, op, retryAttempted, retrySafe = true) {
65314
65818
  if (result.kind === "timeout") {
65315
65819
  return new FetchproxyTimeoutError({
65316
65820
  url: url4,
@@ -65318,7 +65822,8 @@ var FetchproxyServer = class {
65318
65822
  role: this.role,
65319
65823
  port: this.opts.port,
65320
65824
  elapsedMs: result.elapsedMs,
65321
- retryAttempted
65825
+ retryAttempted,
65826
+ retrySafe
65322
65827
  });
65323
65828
  }
65324
65829
  if (result.kind === "content_script_unreachable") {
@@ -65392,9 +65897,12 @@ var FetchproxyServer = class {
65392
65897
  // every version before this option existed.
65393
65898
  ...opts.credentials === "omit" ? { credentials: "omit" } : {}
65394
65899
  };
65395
- const result = await this.fetch(init);
65900
+ const result = await this.fetch(init, opts.retryOnTimeout !== void 0 ? { retryOnTimeout: opts.retryOnTimeout } : {});
65396
65901
  if (!result.ok) {
65397
- throw this._typedErrorFor(result, init.url, "fetch", result.retryAttempted ?? false);
65902
+ if (result.kind === "session_not_ready" && result.cause instanceof Error) {
65903
+ throw result.cause;
65904
+ }
65905
+ throw this._typedErrorFor(result, init.url, "fetch", result.retryAttempted ?? false, isRetrySafeOnTimeout(method, opts.retryOnTimeout));
65398
65906
  }
65399
65907
  const response = {
65400
65908
  status: result.status,
@@ -65434,17 +65942,21 @@ var FetchproxyServer = class {
65434
65942
  * GET a path and parse the response body as JSON. Throws
65435
65943
  * `FetchproxyHttpError` if the status is outside the default 2xx
65436
65944
  * happy-path set (`[200, 201, 202, 204]`); pass a custom
65437
- * `expectStatus` to override.
65945
+ * `expectStatus` to override. A 204 or an empty/whitespace body
65946
+ * resolves to `null` (the declared `T` does not say so, to keep
65947
+ * existing call sites compiling — include `| null` in `T` if the
65948
+ * endpoint can answer empty).
65438
65949
  */
65439
65950
  async getJson(path, opts = {}) {
65440
65951
  const response = await this.get(path, this.applyJsonDefaults(opts));
65441
- return JSON.parse(response.body);
65952
+ return parseJsonBody(response);
65442
65953
  }
65443
65954
  /**
65444
65955
  * POST a JSON body and parse the response body as JSON. The body is
65445
65956
  * `JSON.stringify`'d; `Content-Type: application/json` is set unless
65446
65957
  * the caller already provided one. Defaults `expectStatus` to the 2xx
65447
- * happy-path set.
65958
+ * happy-path set. A 204 or an empty/whitespace body resolves to `null`
65959
+ * rather than throwing after the write already happened (see `getJson`).
65448
65960
  */
65449
65961
  async postJson(path, body, opts = {}) {
65450
65962
  const headers = { ...opts.headers ?? {} };
@@ -65456,7 +65968,7 @@ var FetchproxyServer = class {
65456
65968
  headers,
65457
65969
  body: body === void 0 ? void 0 : JSON.stringify(body)
65458
65970
  });
65459
- return JSON.parse(response.body);
65971
+ return parseJsonBody(response);
65460
65972
  }
65461
65973
  /**
65462
65974
  * GET a path and return the response body as a string. Throws
@@ -65477,7 +65989,8 @@ var FetchproxyServer = class {
65477
65989
  * - adds `Content-Type: application/json` only for a non-GET request
65478
65990
  * that carries a `body` (and only if the caller didn't set one);
65479
65991
  * - `JSON.stringify`s the body (GET / no-body sends nothing);
65480
- * - treats a `204` or an empty body as `data: null` (no parse);
65992
+ * - treats a `204`, an empty body, or a whitespace-only body as
65993
+ * `data: null` (no parse), the same rule as getJson/postJson;
65481
65994
  * - otherwise `JSON.parse`s the body.
65482
65995
  *
65483
65996
  * Scope is serialization + header defaults + 204-handling +
@@ -65491,6 +66004,11 @@ var FetchproxyServer = class {
65491
66004
  * Bridge-level failures (no signed-in tab, SW down, timeout) still
65492
66005
  * throw the typed errors via `request()`, exactly like the verb
65493
66006
  * helpers — only successful round-trips (any HTTP status) return.
66007
+ *
66008
+ * `retryOnTimeout` is `RequestOpts.retryOnTimeout`: a timed-out POST is
66009
+ * NOT re-sent unless the caller says it is safe to repeat. A read-only POST
66010
+ * (a search, a GraphQL query) should pass `true` to keep the cold-start
66011
+ * timeout retry (#90).
65494
66012
  */
65495
66013
  async requestJson(method, path, opts = {}) {
65496
66014
  const isGet = method.toUpperCase() === "GET";
@@ -65504,7 +66022,8 @@ var FetchproxyServer = class {
65504
66022
  headers,
65505
66023
  body: sendBody ? JSON.stringify(opts.body) : void 0,
65506
66024
  ...opts.subdomain !== void 0 ? { subdomain: opts.subdomain } : {},
65507
- ...opts.domain !== void 0 ? { domain: opts.domain } : {}
66025
+ ...opts.domain !== void 0 ? { domain: opts.domain } : {},
66026
+ ...opts.retryOnTimeout !== void 0 ? { retryOnTimeout: opts.retryOnTimeout } : {}
65508
66027
  });
65509
66028
  const result = {
65510
66029
  ok: true,
@@ -65512,12 +66031,9 @@ var FetchproxyServer = class {
65512
66031
  url: response.url,
65513
66032
  body: response.body
65514
66033
  };
65515
- if (response.status === 204 || response.body === "") {
65516
- return { data: null, result };
65517
- }
65518
66034
  let data;
65519
66035
  try {
65520
- data = JSON.parse(response.body);
66036
+ data = parseJsonBody(response);
65521
66037
  } catch (e) {
65522
66038
  throw new Error(`fetchproxy ${method} ${path} \u2014 response was not JSON: ${e instanceof Error ? e.message : String(e)}`);
65523
66039
  }
@@ -65574,18 +66090,19 @@ var FetchproxyServer = class {
65574
66090
  };
65575
66091
  }
65576
66092
  /**
65577
- * Snapshot the user's non-HttpOnly cookies for the chosen domain.
66093
+ * Read the user's cookies for the chosen domain.
65578
66094
  *
65579
66095
  * Requires `'read_cookies'` in `FetchproxyServerOpts.capabilities`.
65580
66096
  * Throws a developer-facing `Error` at the call site if the MCP did
65581
66097
  * not declare the capability — this is a programming mistake, not a
65582
66098
  * runtime condition.
65583
66099
  *
65584
- * The returned string is the raw `document.cookie` value (semicolon-
65585
- * separated `k=v` pairs). HttpOnly cookies are NOT visible to page JS
65586
- * and are therefore not included; the underlying threat model assumes
65587
- * the cookies that matter for the auth bootstrap (session tokens, csrf
65588
- * cookies that the page itself reads) are non-HttpOnly.
66100
+ * The returned string is semicolon-separated `k=v` pairs. With `keys`
66101
+ * (each in the declared `cookieKeys`), the extension reads each name via
66102
+ * `chrome.cookies.get`, which INCLUDES HttpOnly cookies — the login
66103
+ * session cookie among them; that is what cookie-session MCPs use it
66104
+ * for. Without `keys` (legacy), it is the tab's `document.cookie`, which
66105
+ * omits HttpOnly cookies. See docs/SECURITY.md §T-cookie-exfil.
65589
66106
  *
65590
66107
  * Throws `FetchproxyProtocolError` if the bridge could not deliver
65591
66108
  * the request (no signed-in tab, extension offline, etc.).
@@ -65681,7 +66198,15 @@ var FetchproxyServer = class {
65681
66198
  this.pendingWriteCookies.set(id, { resolve: resolve2, reject });
65682
66199
  }));
65683
66200
  await this.sendInnerFrame(inner);
65684
- return this._withVerbTimeout(pending, this.pendingWriteCookies, id, `https://${host}`);
66201
+ return this._withVerbTimeout(
66202
+ pending,
66203
+ this.pendingWriteCookies,
66204
+ id,
66205
+ `https://${host}`,
66206
+ void 0,
66207
+ // A write: never re-sent after an ambiguous timeout.
66208
+ false
66209
+ );
65685
66210
  }
65686
66211
  /**
65687
66212
  * 0.3.0+: read declared localStorage keys from the user's signed-in
@@ -65784,53 +66309,7 @@ var FetchproxyServer = class {
65784
66309
  throw new Error("FetchproxyServer.captureRequestHeader: pass both host AND headerName, or neither (which defaults to the single declared entry)");
65785
66310
  }
65786
66311
  const callOpts = { ...resolved, ...opts?.timeoutMs !== void 0 ? { timeoutMs: opts.timeoutMs } : {} };
65787
- try {
65788
- const result = await this._captureRequestHeaderOnce(callOpts);
65789
- this.recordSuccess();
65790
- return result;
65791
- } catch (err) {
65792
- const swDown = err instanceof FetchproxyProtocolError && classifyFetchError(err.message) === "content_script_unreachable";
65793
- if (!swDown) {
65794
- this.recordFailure(`capture_request_header: ${err.message ?? String(err)}`);
65795
- throw err;
65796
- }
65797
- this.lastEvictionDetectedAt = Date.now();
65798
- const reviveMs = this.opts.bridgeReviveDelayMs ?? 0;
65799
- if (reviveMs > 0) {
65800
- this.lazyReviveAttempts += 1;
65801
- await new Promise((r) => setTimeout(r, reviveMs));
65802
- try {
65803
- const result = await this._captureRequestHeaderOnce(callOpts);
65804
- this.lazyReviveSuccesses += 1;
65805
- this.recordSuccess();
65806
- return result;
65807
- } catch (retryErr) {
65808
- const stillDown = retryErr instanceof FetchproxyProtocolError && classifyFetchError(retryErr.message) === "content_script_unreachable";
65809
- if (!stillDown) {
65810
- this.recordFailure(`capture_request_header: ${retryErr.message ?? String(retryErr)}`);
65811
- throw retryErr;
65812
- }
65813
- this.recordFailure(`capture_request_header bridge-down: ${retryErr.message}`);
65814
- throw new FetchproxyBridgeDownError({
65815
- originalError: retryErr.message,
65816
- retryAttempted: true,
65817
- op: "capture_request_header",
65818
- url: `https://${resolved.host}${resolved.path ?? "/*"}`,
65819
- role: this.role,
65820
- port: this.opts.port
65821
- });
65822
- }
65823
- }
65824
- this.recordFailure(`capture_request_header bridge-down: ${err.message}`);
65825
- throw new FetchproxyBridgeDownError({
65826
- originalError: err.message,
65827
- retryAttempted: false,
65828
- op: "capture_request_header",
65829
- url: `https://${resolved.host}${resolved.path ?? "/*"}`,
65830
- role: this.role,
65831
- port: this.opts.port
65832
- });
65833
- }
66312
+ return this.withLazyRevive("capture_request_header", `https://${resolved.host}${resolved.path ?? "/*"}`, () => this._captureRequestHeaderOnce(callOpts));
65834
66313
  }
65835
66314
  async _captureRequestHeaderOnce(opts) {
65836
66315
  const id = this.nextRequestId++;
@@ -65879,53 +66358,7 @@ var FetchproxyServer = class {
65879
66358
  }
65880
66359
  await this.ensureConnected();
65881
66360
  this.throwIfPendingPair();
65882
- try {
65883
- const result = await this._captureRedirectOnce(opts);
65884
- this.recordSuccess();
65885
- return result;
65886
- } catch (err) {
65887
- const swDown = err instanceof FetchproxyProtocolError && classifyFetchError(err.message) === "content_script_unreachable";
65888
- if (!swDown) {
65889
- this.recordFailure(`capture_redirect: ${err.message ?? String(err)}`);
65890
- throw err;
65891
- }
65892
- this.lastEvictionDetectedAt = Date.now();
65893
- const reviveMs = this.opts.bridgeReviveDelayMs ?? 0;
65894
- if (reviveMs > 0) {
65895
- this.lazyReviveAttempts += 1;
65896
- await new Promise((r) => setTimeout(r, reviveMs));
65897
- try {
65898
- const result = await this._captureRedirectOnce(opts);
65899
- this.lazyReviveSuccesses += 1;
65900
- this.recordSuccess();
65901
- return result;
65902
- } catch (retryErr) {
65903
- const stillDown = retryErr instanceof FetchproxyProtocolError && classifyFetchError(retryErr.message) === "content_script_unreachable";
65904
- if (!stillDown) {
65905
- this.recordFailure(`capture_redirect: ${retryErr.message ?? String(retryErr)}`);
65906
- throw retryErr;
65907
- }
65908
- this.recordFailure(`capture_redirect bridge-down: ${retryErr.message}`);
65909
- throw new FetchproxyBridgeDownError({
65910
- originalError: retryErr.message,
65911
- retryAttempted: true,
65912
- op: "capture_redirect",
65913
- url: `https://${opts.host}${opts.path ?? "/*"}`,
65914
- role: this.role,
65915
- port: this.opts.port
65916
- });
65917
- }
65918
- }
65919
- this.recordFailure(`capture_redirect bridge-down: ${err.message}`);
65920
- throw new FetchproxyBridgeDownError({
65921
- originalError: err.message,
65922
- retryAttempted: false,
65923
- op: "capture_redirect",
65924
- url: `https://${opts.host}${opts.path ?? "/*"}`,
65925
- role: this.role,
65926
- port: this.opts.port
65927
- });
65928
- }
66361
+ return this.withLazyRevive("capture_redirect", `https://${opts.host}${opts.path ?? "/*"}`, () => this._captureRedirectOnce(opts));
65929
66362
  }
65930
66363
  async _captureRedirectOnce(opts) {
65931
66364
  const id = this.nextRequestId++;
@@ -65970,52 +66403,57 @@ var FetchproxyServer = class {
65970
66403
  assertUrlInDomains("download url", opts.url, this.opts.domains);
65971
66404
  await this.ensureConnected();
65972
66405
  this.throwIfPendingPair();
66406
+ return this.withLazyRevive("download", opts.url, () => this._downloadOnce(opts));
66407
+ }
66408
+ /**
66409
+ * B-QUAL-1: the lazy-revive policy for the throwing verbs
66410
+ * (`capture_request_header`, `capture_redirect`, `download`), in one place.
66411
+ * Runs `once`; on the SW-eviction symptom (`content_script_unreachable` —
66412
+ * the request provably never reached a tab, so re-sending is safe) stamps
66413
+ * the eviction counter, waits `bridgeReviveDelayMs` and retries once, then
66414
+ * surfaces a `FetchproxyBridgeDownError`. Every other error is recorded and
66415
+ * rethrown untouched. `fetch()` keeps its own envelope-shaped variant.
66416
+ */
66417
+ async withLazyRevive(op, url4, once) {
66418
+ const isSwDown = (e) => e instanceof FetchproxyProtocolError && classifyFetchError(e.message) === "content_script_unreachable";
66419
+ const bridgeDown = (e, retryAttempted) => {
66420
+ this.recordFailure(`${op} bridge-down: ${e.message}`);
66421
+ return new FetchproxyBridgeDownError({
66422
+ originalError: e.message,
66423
+ retryAttempted,
66424
+ op,
66425
+ url: url4,
66426
+ role: this.role,
66427
+ port: this.opts.port
66428
+ });
66429
+ };
65973
66430
  try {
65974
- const result = await this._downloadOnce(opts);
66431
+ const result = await once();
65975
66432
  this.recordSuccess();
65976
66433
  return result;
65977
66434
  } catch (err) {
65978
- const swDown = err instanceof FetchproxyProtocolError && classifyFetchError(err.message) === "content_script_unreachable";
65979
- if (!swDown) {
65980
- this.recordFailure(`download: ${err.message ?? String(err)}`);
66435
+ if (!isSwDown(err)) {
66436
+ this.recordFailure(`${op}: ${err.message ?? String(err)}`);
65981
66437
  throw err;
65982
66438
  }
65983
66439
  this.lastEvictionDetectedAt = Date.now();
65984
66440
  const reviveMs = this.opts.bridgeReviveDelayMs ?? 0;
65985
- if (reviveMs > 0) {
65986
- this.lazyReviveAttempts += 1;
65987
- await new Promise((r) => setTimeout(r, reviveMs));
65988
- try {
65989
- const result = await this._downloadOnce(opts);
65990
- this.lazyReviveSuccesses += 1;
65991
- this.recordSuccess();
65992
- return result;
65993
- } catch (retryErr) {
65994
- const stillDown = retryErr instanceof FetchproxyProtocolError && classifyFetchError(retryErr.message) === "content_script_unreachable";
65995
- if (!stillDown) {
65996
- this.recordFailure(`download: ${retryErr.message ?? String(retryErr)}`);
65997
- throw retryErr;
65998
- }
65999
- this.recordFailure(`download bridge-down: ${retryErr.message}`);
66000
- throw new FetchproxyBridgeDownError({
66001
- originalError: retryErr.message,
66002
- retryAttempted: true,
66003
- op: "download",
66004
- url: opts.url,
66005
- role: this.role,
66006
- port: this.opts.port
66007
- });
66441
+ if (reviveMs <= 0)
66442
+ throw bridgeDown(err, false);
66443
+ this.lazyReviveAttempts += 1;
66444
+ await new Promise((r) => setTimeout(r, reviveMs));
66445
+ try {
66446
+ const result = await once();
66447
+ this.lazyReviveSuccesses += 1;
66448
+ this.recordSuccess();
66449
+ return result;
66450
+ } catch (retryErr) {
66451
+ if (!isSwDown(retryErr)) {
66452
+ this.recordFailure(`${op}: ${retryErr.message ?? String(retryErr)}`);
66453
+ throw retryErr;
66008
66454
  }
66455
+ throw bridgeDown(retryErr, true);
66009
66456
  }
66010
- this.recordFailure(`download bridge-down: ${err.message}`);
66011
- throw new FetchproxyBridgeDownError({
66012
- originalError: err.message,
66013
- retryAttempted: false,
66014
- op: "download",
66015
- url: opts.url,
66016
- role: this.role,
66017
- port: this.opts.port
66018
- });
66019
66457
  }
66020
66458
  }
66021
66459
  async _downloadOnce(opts) {
@@ -66044,7 +66482,9 @@ var FetchproxyServer = class {
66044
66482
  // CAP and need the identical explanation; missing it then was the point
66045
66483
  // of #279 — "both verbs" was counted off the two call sites in view
66046
66484
  // rather than off the type. Three is still the number.
66047
- opts.timeoutMs
66485
+ opts.timeoutMs,
66486
+ // A second download saves a duplicate "file (1)".
66487
+ false
66048
66488
  );
66049
66489
  }
66050
66490
  /**
@@ -66135,6 +66575,52 @@ var FetchproxyServer = class {
66135
66575
  await this.sendInnerFrame(inner);
66136
66576
  return this._withVerbTimeout(pending, this.pendingStorage, id, origin);
66137
66577
  }
66578
+ /**
66579
+ * 3.1.0+: read a declared REPEATED DOM structure from the user's
66580
+ * signed-in tab. Requires `'read_dom_list'` in capabilities AND `name` to
66581
+ * match a declared `domListSelectors` entry. The extension resolves the
66582
+ * entry's `itemSelector` via `document.querySelectorAll` in the matched
66583
+ * tab's DOM (isolated world), then resolves each declared field against
66584
+ * every matched item in turn — no page-JS execution, same as `readDom`.
66585
+ *
66586
+ * Returns one row per matched item, in document order, truncated to the
66587
+ * entry's declared `maxItems` when set. A field absent on a given item is
66588
+ * omitted from that item's row (the row itself is still returned). Throws
66589
+ * `FetchproxyProtocolError` on bridge failures and a plain `Error` on
66590
+ * developer mistakes (undeclared capability, undeclared name).
66591
+ *
66592
+ * Reads only what is CURRENTLY rendered — a virtualized or lazily-loaded
66593
+ * list returns only its visible rows. A caller that needs more scrolls
66594
+ * the list (out of band) and calls again.
66595
+ */
66596
+ async readDomList(opts) {
66597
+ if (!this.opts.capabilities.includes("read_dom_list")) {
66598
+ throw new Error('FetchproxyServer.readDomList(): MCP did not declare "read_dom_list" in capabilities');
66599
+ }
66600
+ await this.ensureConnected();
66601
+ this.throwIfPendingPair();
66602
+ if (typeof opts.name !== "string" || opts.name.length === 0) {
66603
+ throw new Error("FetchproxyServer.readDomList: opts.name must be a non-empty string");
66604
+ }
66605
+ this.assertScopeSubset([opts.name], this.opts.domListSelectors.map((d) => d.name), "domListSelectors");
66606
+ if (opts.subdomain !== void 0)
66607
+ assertSubdomainLabel(opts.subdomain);
66608
+ const baseDomain = this.resolveBaseDomain(opts.domain);
66609
+ const host = opts.subdomain ? `${opts.subdomain}.${baseDomain}` : baseDomain;
66610
+ const origin = `https://${host}`;
66611
+ const id = this.nextRequestId++;
66612
+ const inner = {
66613
+ type: "request",
66614
+ id,
66615
+ op: "read_dom_list",
66616
+ init: { origin, name: opts.name }
66617
+ };
66618
+ const pending = this.guardPending(new Promise((resolve2, reject) => {
66619
+ this.pendingDomList.set(id, { resolve: resolve2, reject });
66620
+ }));
66621
+ await this.sendInnerFrame(inner);
66622
+ return this._withVerbTimeout(pending, this.pendingDomList, id, origin);
66623
+ }
66138
66624
  /**
66139
66625
  * 1.x+: run a declared GraphQL operation through the page's own Apollo
66140
66626
  * client (`window.__APOLLO_CLIENT__`) in the signed-in tab's MAIN world.
@@ -66178,8 +66664,9 @@ var FetchproxyServer = class {
66178
66664
  const pending = this.guardPending(new Promise((resolve2, reject) => {
66179
66665
  this.pendingGraphql.set(id, { resolve: resolve2, reject });
66180
66666
  }));
66181
- await this.sendInnerFrame(inner);
66182
- return this._withVerbTimeout(pending, this.pendingGraphql, id, opts.name);
66667
+ const retrySafe = opts.retryOnTimeout === true;
66668
+ await this.sendInnerFrame(inner, { resendable: retrySafe });
66669
+ return this._withVerbTimeout(pending, this.pendingGraphql, id, opts.name, void 0, retrySafe);
66183
66670
  }
66184
66671
  assertScopeSubset(requested, declared, label) {
66185
66672
  const undeclared = undeclaredKeys(requested, declared);
@@ -66216,6 +66703,7 @@ var FetchproxyServer = class {
66216
66703
  onInner(inner) {
66217
66704
  if (inner.type !== "response")
66218
66705
  return;
66706
+ this.resendable.delete(inner.id);
66219
66707
  this.lastExtensionMessageAt = Date.now();
66220
66708
  const fetchCb = this.pending.get(inner.id);
66221
66709
  if (fetchCb) {
@@ -66304,6 +66792,20 @@ var FetchproxyServer = class {
66304
66792
  }
66305
66793
  return;
66306
66794
  }
66795
+ const domListCb = this.pendingDomList.get(inner.id);
66796
+ if (domListCb) {
66797
+ this.pendingDomList.delete(inner.id);
66798
+ if (inner.ok) {
66799
+ if (inner.op === "read_dom_list" && inner.rows) {
66800
+ domListCb.resolve(inner.rows.map((row) => ({ ...row })));
66801
+ } else {
66802
+ domListCb.reject(new FetchproxyProtocolError(`unexpected ${String(inner.op)} response on read_dom_list awaiter`));
66803
+ }
66804
+ } else {
66805
+ domListCb.reject(protocolErrorFrom(inner.error));
66806
+ }
66807
+ return;
66808
+ }
66307
66809
  const downloadCb = this.pendingDownload.get(inner.id);
66308
66810
  if (downloadCb) {
66309
66811
  this.pendingDownload.delete(inner.id);
@@ -66363,42 +66865,133 @@ var FetchproxyServer = class {
66363
66865
  }
66364
66866
  }
66365
66867
  }
66366
- rejectAllPending(reason = "extension disconnected") {
66868
+ rejectAllPending(reason = "extension disconnected", keep = /* @__PURE__ */ new Set()) {
66869
+ for (const id of this.allPendingIds()) {
66870
+ if (!keep.has(id))
66871
+ this.rejectPendingId(id, reason);
66872
+ }
66873
+ if (keep.size === 0)
66874
+ this.resendable.clear();
66875
+ }
66876
+ /** Every request id with a live awaiter, across all op maps. */
66877
+ allPendingIds() {
66878
+ const ids = [];
66879
+ for (const map2 of this.pendingMaps())
66880
+ for (const id of map2.keys())
66881
+ ids.push(id);
66882
+ return ids;
66883
+ }
66884
+ pendingMaps() {
66885
+ return [
66886
+ this.pending,
66887
+ this.pendingReadCookies,
66888
+ this.pendingStorage,
66889
+ this.pendingWriteCookies,
66890
+ this.pendingCapture,
66891
+ this.pendingRedirect,
66892
+ this.pendingIdb,
66893
+ this.pendingDomList,
66894
+ this.pendingDownload,
66895
+ this.pendingGraphql
66896
+ ];
66897
+ }
66898
+ isPendingId(id) {
66899
+ return this.pendingMaps().some((m) => m.has(id));
66900
+ }
66901
+ /** Fail the one awaiter registered under `id`, in its own map's shape. */
66902
+ rejectPendingId(id, reason) {
66367
66903
  const err = new FetchproxyProtocolError(reason);
66368
- for (const cb of this.pending.values()) {
66369
- cb({
66904
+ this.resendable.delete(id);
66905
+ const fetchCb = this.pending.get(id);
66906
+ if (fetchCb) {
66907
+ this.pending.delete(id);
66908
+ fetchCb({
66370
66909
  ok: false,
66371
66910
  error: err.message,
66372
66911
  kind: classifyFetchError(err.message),
66373
66912
  retryAttempted: false
66374
66913
  });
66914
+ return;
66915
+ }
66916
+ const cookieCb = this.pendingReadCookies.get(id);
66917
+ if (cookieCb) {
66918
+ this.pendingReadCookies.delete(id);
66919
+ cookieCb({ ok: false, error: err.message });
66920
+ return;
66921
+ }
66922
+ for (const map2 of [
66923
+ this.pendingStorage,
66924
+ this.pendingWriteCookies,
66925
+ this.pendingCapture,
66926
+ this.pendingRedirect,
66927
+ this.pendingIdb,
66928
+ this.pendingDomList,
66929
+ this.pendingDownload,
66930
+ this.pendingGraphql
66931
+ ]) {
66932
+ const entry = map2.get(id);
66933
+ if (entry) {
66934
+ map2.delete(id);
66935
+ entry.reject(err);
66936
+ return;
66937
+ }
66938
+ }
66939
+ }
66940
+ noteResendable(inner) {
66941
+ this.resendable.set(inner.id, inner);
66942
+ if (this.resendable.size > 256) {
66943
+ for (const id of [...this.resendable.keys()]) {
66944
+ if (!this.isPendingId(id))
66945
+ this.resendable.delete(id);
66946
+ }
66375
66947
  }
66376
- this.pending.clear();
66377
- for (const cb of this.pendingReadCookies.values()) {
66378
- cb({ ok: false, error: err.message });
66379
- }
66380
- this.pendingReadCookies.clear();
66381
- for (const { reject } of this.pendingStorage.values())
66382
- reject(err);
66383
- this.pendingStorage.clear();
66384
- for (const { reject } of this.pendingWriteCookies.values())
66385
- reject(err);
66386
- this.pendingWriteCookies.clear();
66387
- for (const { reject } of this.pendingCapture.values())
66388
- reject(err);
66389
- this.pendingCapture.clear();
66390
- for (const { reject } of this.pendingRedirect.values())
66391
- reject(err);
66392
- this.pendingRedirect.clear();
66393
- for (const { reject } of this.pendingIdb.values())
66394
- reject(err);
66395
- this.pendingIdb.clear();
66396
- for (const { reject } of this.pendingDownload.values())
66397
- reject(err);
66398
- this.pendingDownload.clear();
66399
- for (const { reject } of this.pendingGraphql.values())
66400
- reject(err);
66401
- this.pendingGraphql.clear();
66948
+ }
66949
+ /**
66950
+ * B-BUG-6: the host this peer relayed through has gone — most often a
66951
+ * short-lived process (a bootstrap lift, an `fpx` call) that won the
66952
+ * election and then closed. Fail only the in-flight requests that may
66953
+ * already have run (a non-idempotent fetch, a cookie write, a download);
66954
+ * re-elect and send the rest again. The replies to the originals died with
66955
+ * the old host, and each re-sent request keeps its awaiter and its
66956
+ * original deadline.
66957
+ */
66958
+ async recoverFromHostLoss() {
66959
+ const toResend = /* @__PURE__ */ new Map();
66960
+ for (const [id, inner] of this.resendable) {
66961
+ if (this.isPendingId(id))
66962
+ toResend.set(id, inner);
66963
+ }
66964
+ this.resendable.clear();
66965
+ this.rejectAllPending("fetchproxy: the MCP process holding the bridge port exited while this request was in flight. It was not re-sent because it may already have run in the browser \u2014 check before retrying.", new Set(toResend.keys()));
66966
+ if (toResend.size === 0)
66967
+ return;
66968
+ const lost = (id, e) => this.rejectPendingId(id, `fetchproxy: the bridge host exited and reconnecting failed: ${e instanceof Error ? e.message : String(e)}`);
66969
+ try {
66970
+ await this.ensureConnected();
66971
+ } catch (e) {
66972
+ for (const id of toResend.keys())
66973
+ lost(id, e);
66974
+ return;
66975
+ }
66976
+ for (const [id, inner] of toResend) {
66977
+ if (!this.isPendingId(id))
66978
+ continue;
66979
+ try {
66980
+ await this.resendInnerFrame(inner);
66981
+ } catch (e) {
66982
+ lost(id, e);
66983
+ }
66984
+ }
66985
+ }
66986
+ /** Send an already-registered request again, keeping its awaiter on failure. */
66987
+ async resendInnerFrame(inner) {
66988
+ if (this.hostHandle)
66989
+ await this.hostHandle.sendOwnInner(inner);
66990
+ else if (this.peerHandle)
66991
+ await this.peerHandle.sendInner(inner);
66992
+ else
66993
+ throw new Error("no bridge handle after re-election");
66994
+ this.noteResendable(inner);
66402
66995
  }
66403
66996
  /**
66404
66997
  * 0.5.2+: read the current pair-pending pair code from whichever handle
@@ -66501,6 +67094,12 @@ async function runOneLift(opts) {
66501
67094
  if (envVal !== void 0 && envVal !== "" && envVal !== "0" && envVal !== "false") {
66502
67095
  throw new BootstrapDisabledError(opts.serverName, envVar);
66503
67096
  }
67097
+ for (const bucket of ["cookies", "localStorage", "sessionStorage"]) {
67098
+ const glob = opts.declare[bucket].find((k) => k.endsWith("*"));
67099
+ if (glob !== void 0) {
67100
+ throw new Error(`fetchproxy bootstrap: declare.${bucket} contains ${JSON.stringify(glob)}, a glob. A lift reads each key by its exact name, so a glob can never match \u2014 list the concrete key names instead.`);
67101
+ }
67102
+ }
66504
67103
  const factory = opts._serverFactory ?? defaultFactory;
66505
67104
  const indexedDb = opts.declare.indexedDb ?? [];
66506
67105
  const localStoragePointers = opts.declare.localStoragePointers ?? [];
@@ -66863,7 +67462,7 @@ function loadAccount(env = process.env) {
66863
67462
  // package.json
66864
67463
  var package_default = {
66865
67464
  name: "infinitecampus-mcp",
66866
- version: "3.1.0",
67465
+ version: "3.1.2",
66867
67466
  mcpName: "io.github.chrischall/infinitecampus-mcp",
66868
67467
  description: "Infinite Campus (Campus Parent) MCP server \u2014 multi-district read + message/document write",
66869
67468
  author: "Claude Code (AI) <https://www.anthropic.com/claude>",
@@ -66908,13 +67507,13 @@ var package_default = {
66908
67507
  typecheck: "tsc -p tsconfig.json --noEmit"
66909
67508
  },
66910
67509
  dependencies: {
66911
- "@chrischall/mcp-utils": "^1.0.0",
66912
- "@fetchproxy/bootstrap": "^3.0.1",
66913
- "@fetchproxy/server": "^3.0.1",
67510
+ "@chrischall/mcp-utils": "^2.4.0",
67511
+ "@fetchproxy/bootstrap": "^3.2.0",
67512
+ "@fetchproxy/server": "^3.2.0",
66914
67513
  "@modelcontextprotocol/server": "^2.0.0",
66915
67514
  dotenv: "^17.4.2",
66916
67515
  "node-html-parser": "^9.0.0",
66917
- zod: "^4.6.2"
67516
+ zod: "^4.6.5"
66918
67517
  },
66919
67518
  devDependencies: {
66920
67519
  "@modelcontextprotocol/client": "^2.0.0",
@@ -67026,7 +67625,7 @@ import { dirname as dirname2 } from "path";
67026
67625
  import { existsSync, readFileSync, writeFileSync, mkdirSync, chmodSync, renameSync, unlinkSync } from "node:fs";
67027
67626
  import { dirname, join as join3, resolve } from "node:path";
67028
67627
  import { homedir as homedir2 } from "node:os";
67029
- import { randomBytes as randomBytes2, createHmac } from "node:crypto";
67628
+ import { randomBytes as randomBytes3, createHmac } from "node:crypto";
67030
67629
  function bindingDigest(secret, salt) {
67031
67630
  return createHmac("sha256", salt).update(secret).digest("hex").slice(0, 32);
67032
67631
  }
@@ -67078,7 +67677,7 @@ function createFileStatePersistence(opts) {
67078
67677
  },
67079
67678
  save(state) {
67080
67679
  const dir = dirname(filePath);
67081
- const tmp = `${filePath}.tmp-${randomBytes2(6).toString("hex")}`;
67680
+ const tmp = `${filePath}.tmp-${randomBytes3(6).toString("hex")}`;
67082
67681
  const dirExisted = existsSync(dir);
67083
67682
  try {
67084
67683
  mkdirSync(dir, { recursive: true, mode: 448 });
@@ -67086,7 +67685,7 @@ function createFileStatePersistence(opts) {
67086
67685
  chmodSync(dir, 448);
67087
67686
  let boundTo;
67088
67687
  if (secret !== void 0) {
67089
- const salt = randomBytes2(16).toString("hex");
67688
+ const salt = randomBytes3(16).toString("hex");
67090
67689
  boundTo = { salt, digest: bindingDigest(secret, salt) };
67091
67690
  }
67092
67691
  const envelope = { v: 1, ...boundTo !== void 0 ? { boundTo } : {}, state };
@@ -68280,7 +68879,7 @@ function extractPlainTextFromHtml(html) {
68280
68879
  return text.replace(/\s+/g, " ").trim();
68281
68880
  }
68282
68881
  function stripElementContent(html, tag) {
68283
- const lower = html.toLowerCase();
68882
+ const lower = asciiLower(html);
68284
68883
  const open2 = `<${tag}`;
68285
68884
  const close = `</${tag}>`;
68286
68885
  let out = "";
@@ -68882,7 +69481,7 @@ try {
68882
69481
  }
68883
69482
  var COMMON = {
68884
69483
  name: "infinitecampus",
68885
- version: "3.1.0"
69484
+ version: "3.1.2"
68886
69485
  // x-release-please-version
68887
69486
  };
68888
69487
  var healthState = { account, source, configError, client: null };