crosscheck-mcp 0.1.9 → 0.1.10

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.
@@ -7449,14 +7449,14 @@ function detectCycle(nodes) {
7449
7449
  indeg[id] = (indeg[id] ?? 0) + 1;
7450
7450
  }
7451
7451
  }
7452
- const queue = Object.keys(indeg).filter((id) => indeg[id] === 0);
7452
+ const queue2 = Object.keys(indeg).filter((id) => indeg[id] === 0);
7453
7453
  let processed = 0;
7454
- while (queue.length > 0) {
7455
- const id = queue.shift();
7454
+ while (queue2.length > 0) {
7455
+ const id = queue2.shift();
7456
7456
  processed++;
7457
7457
  for (const next of consumers[id] ?? []) {
7458
7458
  indeg[next] = (indeg[next] ?? 0) - 1;
7459
- if (indeg[next] === 0) queue.push(next);
7459
+ if (indeg[next] === 0) queue2.push(next);
7460
7460
  }
7461
7461
  }
7462
7462
  return processed === Object.keys(byId).length ? null : "cycle detected (Kahn's algorithm didn't process all nodes)";
@@ -7495,13 +7495,13 @@ function topologicalSort(nodesById) {
7495
7495
  }
7496
7496
  }
7497
7497
  const order = [];
7498
- const queue = Object.keys(indeg).filter((id) => indeg[id] === 0);
7499
- while (queue.length > 0) {
7500
- const id = queue.shift();
7498
+ const queue2 = Object.keys(indeg).filter((id) => indeg[id] === 0);
7499
+ while (queue2.length > 0) {
7500
+ const id = queue2.shift();
7501
7501
  order.push(id);
7502
7502
  for (const next of consumers[id] ?? []) {
7503
7503
  indeg[next] = (indeg[next] ?? 0) - 1;
7504
- if (indeg[next] === 0) queue.push(next);
7504
+ if (indeg[next] === 0) queue2.push(next);
7505
7505
  }
7506
7506
  }
7507
7507
  return order;
@@ -11343,7 +11343,7 @@ var CHECK_INTERVAL_SECONDS = 3 * 24 * 60 * 60;
11343
11343
  var DEFAULT_PACKAGE = "crosscheck-cli";
11344
11344
  var FETCH_TIMEOUT_MS = 3e3;
11345
11345
  function engineVersion() {
11346
- return true ? "0.1.9" : "0.0.0-dev";
11346
+ return true ? "0.1.10" : "0.0.0-dev";
11347
11347
  }
11348
11348
  function defaultUpdateCachePath() {
11349
11349
  const base = process.env["CROSSCHECK_DATA_DIR"] || import_node_path12.default.join(import_node_os2.default.homedir() || import_node_os2.default.tmpdir(), ".crosscheck");
@@ -12023,7 +12023,7 @@ function solveTool(providers, allowlist, bridge, storage, breakers, transcriptsD
12023
12023
  })
12024
12024
  };
12025
12025
  }
12026
- function configPinTool(repoRoot, config, rejectDriftEnv) {
12026
+ function configPinTool(repoRoot, config2, rejectDriftEnv) {
12027
12027
  return {
12028
12028
  name: "config_pin",
12029
12029
  description: "CRUD over the config-pinning ledger. Tracks SHA256s of the canonical config files (config/pricing.json, etc.) and detects drift. Actions: show, set, accept_drift, clear. The actual drift-rejection gate is wired separately in the host.",
@@ -12048,7 +12048,7 @@ function configPinTool(repoRoot, config, rejectDriftEnv) {
12048
12048
  }
12049
12049
  return runConfigPin(args, {
12050
12050
  repoRoot,
12051
- ...config ? { config } : {},
12051
+ ...config2 ? { config: config2 } : {},
12052
12052
  rejectDriftEnv
12053
12053
  });
12054
12054
  }
@@ -12688,9 +12688,194 @@ function attachUpdateNotice(result, toolName, opts) {
12688
12688
  }
12689
12689
  }
12690
12690
 
12691
+ // src/core/telemetry.ts
12692
+ init_cjs_shims();
12693
+ var import_node_crypto7 = __toESM(require("crypto"), 1);
12694
+ var UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
12695
+ var BATCH_MAX = 100;
12696
+ var FLUSH_BATCH_AT = 25;
12697
+ var FLUSH_INTERVAL_MS = 15e3;
12698
+ var MAX_QUEUE = 500;
12699
+ var POST_TIMEOUT_MS = 5e3;
12700
+ var PROVIDER_TO_SCHEMA = {
12701
+ anthropic: "anthropic",
12702
+ openai: "openai",
12703
+ gemini: "gemini",
12704
+ xai: "grok",
12705
+ grok: "grok"
12706
+ };
12707
+ var TOOL_TO_PATTERN = {
12708
+ confer: "confer",
12709
+ debate: "debate",
12710
+ audit: "audit",
12711
+ plan: "plan",
12712
+ pick: "panel_pick",
12713
+ verify: "verify",
12714
+ critique: "critique",
12715
+ solve: "solve"
12716
+ };
12717
+ function providerToSchema(provider) {
12718
+ return PROVIDER_TO_SCHEMA[String(provider).toLowerCase()] ?? null;
12719
+ }
12720
+ function toolToPattern(toolName) {
12721
+ return TOOL_TO_PATTERN[String(toolName).toLowerCase()] ?? "other";
12722
+ }
12723
+ function canonicalEventBody(e) {
12724
+ return [
12725
+ e.eventId,
12726
+ e.seatId,
12727
+ e.orgId,
12728
+ e.ts,
12729
+ e.provider,
12730
+ e.model,
12731
+ e.pattern,
12732
+ String(e.promptTokens),
12733
+ String(e.completionTokens),
12734
+ e.costUsdEstimate.toFixed(6),
12735
+ String(e.latencyMs),
12736
+ e.status,
12737
+ e.errorClass ?? ""
12738
+ ].join("|");
12739
+ }
12740
+ function signEvent(seatSigningKey, e) {
12741
+ return import_node_crypto7.default.createHmac("sha256", seatSigningKey).update(canonicalEventBody(e)).digest("hex");
12742
+ }
12743
+ var configResolved = false;
12744
+ var config = null;
12745
+ function getConfig() {
12746
+ if (configResolved) return config;
12747
+ configResolved = true;
12748
+ if (process.env["CROSSCHECK_TELEMETRY"] === "off") return config = null;
12749
+ const url = process.env["CROSSCHECK_TELEMETRY_URL"];
12750
+ const seatId = process.env["CROSSCHECK_SEAT_ID"];
12751
+ const orgId = process.env["CROSSCHECK_ORG_ID"];
12752
+ const seatKey = process.env["CROSSCHECK_SEAT_HMAC_KEY"];
12753
+ if (!url || !seatId || !orgId || !seatKey) return config = null;
12754
+ if (!UUID_RE.test(seatId) || !UUID_RE.test(orgId)) return config = null;
12755
+ config = { url, seatId, orgId, seatKey };
12756
+ return config;
12757
+ }
12758
+ function intNonNeg(v) {
12759
+ const n = Math.trunc(Number(v));
12760
+ return Number.isFinite(n) && n > 0 ? n : 0;
12761
+ }
12762
+ function usageEventsFromEnvelope(out, toolName) {
12763
+ if (!out || typeof out !== "object" || Array.isArray(out)) return [];
12764
+ const usage = out["usage"];
12765
+ if (!usage || typeof usage !== "object") return [];
12766
+ const byCall = usage["by_call"];
12767
+ if (!Array.isArray(byCall) || byCall.length === 0) return [];
12768
+ const timing = out["timing"];
12769
+ const timingByCall = timing && typeof timing === "object" ? timing["by_call"] : void 0;
12770
+ const latencies = Array.isArray(timingByCall) ? timingByCall : [];
12771
+ const aligned = latencies.length === byCall.length;
12772
+ const pattern = toolToPattern(toolName);
12773
+ const events = [];
12774
+ for (let i = 0; i < byCall.length; i += 1) {
12775
+ const u = byCall[i];
12776
+ const provider = providerToSchema(String(u.provider ?? ""));
12777
+ if (!provider) continue;
12778
+ const model = String(u.model ?? "").slice(0, 128) || "unknown";
12779
+ const cost = Math.max(0, Number(u.cost_usd) || 0);
12780
+ const latencyMs = aligned ? intNonNeg(latencies[i]?.["wall_ms"]) : 0;
12781
+ events.push({
12782
+ provider,
12783
+ model,
12784
+ pattern,
12785
+ promptTokens: intNonNeg(u.prompt_tokens),
12786
+ completionTokens: intNonNeg(u.completion_tokens),
12787
+ costUsdEstimate: cost,
12788
+ latencyMs,
12789
+ status: "ok"
12790
+ });
12791
+ }
12792
+ return events;
12793
+ }
12794
+ var queue = [];
12795
+ var flushTimer = null;
12796
+ function scheduleFlush() {
12797
+ if (flushTimer) return;
12798
+ flushTimer = setTimeout(() => {
12799
+ flushTimer = null;
12800
+ void flush();
12801
+ }, FLUSH_INTERVAL_MS);
12802
+ if (typeof flushTimer.unref === "function") flushTimer.unref();
12803
+ }
12804
+ function enqueue(cfg, input) {
12805
+ const base = {
12806
+ eventId: import_node_crypto7.default.randomUUID(),
12807
+ seatId: cfg.seatId,
12808
+ orgId: cfg.orgId,
12809
+ ts: (/* @__PURE__ */ new Date()).toISOString(),
12810
+ provider: input.provider,
12811
+ model: input.model,
12812
+ pattern: input.pattern,
12813
+ promptTokens: input.promptTokens,
12814
+ completionTokens: input.completionTokens,
12815
+ costUsdEstimate: input.costUsdEstimate,
12816
+ latencyMs: input.latencyMs,
12817
+ status: input.status
12818
+ };
12819
+ const signatureHmac = signEvent(cfg.seatKey, base);
12820
+ queue.push({ ...base, signatureHmac });
12821
+ if (queue.length > MAX_QUEUE) queue.splice(0, queue.length - MAX_QUEUE);
12822
+ if (queue.length >= FLUSH_BATCH_AT) void flush();
12823
+ else scheduleFlush();
12824
+ }
12825
+ function recordUsage(out, toolName) {
12826
+ try {
12827
+ const cfg = getConfig();
12828
+ if (!cfg) return;
12829
+ if (toolName === "update_crosscheck") return;
12830
+ for (const ev of usageEventsFromEnvelope(out, toolName)) enqueue(cfg, ev);
12831
+ } catch {
12832
+ }
12833
+ }
12834
+ async function postBatch(url, batch) {
12835
+ let resp;
12836
+ try {
12837
+ resp = await fetch(url, {
12838
+ method: "POST",
12839
+ headers: { "content-type": "application/json" },
12840
+ body: JSON.stringify({ events: batch }),
12841
+ signal: AbortSignal.timeout(POST_TIMEOUT_MS)
12842
+ });
12843
+ } catch {
12844
+ queue.unshift(...batch);
12845
+ if (queue.length > MAX_QUEUE) queue.splice(0, queue.length - MAX_QUEUE);
12846
+ return;
12847
+ }
12848
+ if (resp.ok) return;
12849
+ if (resp.status === 400) {
12850
+ try {
12851
+ process.stderr.write(
12852
+ `crosscheck-agent: telemetry batch rejected (400), dropped ${batch.length} event(s)
12853
+ `
12854
+ );
12855
+ } catch {
12856
+ }
12857
+ return;
12858
+ }
12859
+ queue.unshift(...batch);
12860
+ if (queue.length > MAX_QUEUE) queue.splice(0, queue.length - MAX_QUEUE);
12861
+ }
12862
+ async function flush() {
12863
+ const cfg = getConfig();
12864
+ if (!cfg) return;
12865
+ if (flushTimer) {
12866
+ clearTimeout(flushTimer);
12867
+ flushTimer = null;
12868
+ }
12869
+ while (queue.length > 0) {
12870
+ const batch = queue.splice(0, BATCH_MAX);
12871
+ await postBatch(cfg.url, batch);
12872
+ if (queue.length > 0 && queue[0] === batch[0]) break;
12873
+ }
12874
+ }
12875
+
12691
12876
  // src/server.ts
12692
12877
  var SERVER_NAME = "crosscheck-agent";
12693
- var SERVER_VERSION = true ? "0.1.9" : "0.0.0-dev";
12878
+ var SERVER_VERSION = true ? "0.1.10" : "0.0.0-dev";
12694
12879
  function extractRunSummaryText(out) {
12695
12880
  if (out === null || typeof out !== "object" || Array.isArray(out)) return void 0;
12696
12881
  const rs = out["run_summary"];
@@ -12772,10 +12957,9 @@ function createServer(opts = {}) {
12772
12957
  () => tool.handler(args)
12773
12958
  );
12774
12959
  if (out && typeof out === "object" && !Array.isArray(out)) {
12775
- attachUpdateNotice(out, name, {
12776
- ...opts.repoRoot ? { repoRoot: opts.repoRoot } : {}
12777
- });
12960
+ attachUpdateNotice(out, name);
12778
12961
  }
12962
+ recordUsage(out, name);
12779
12963
  const durationMs = Number(
12780
12964
  (process.hrtime.bigint() - startedHr) / 1000000n
12781
12965
  );
@@ -12822,6 +13006,11 @@ function createServer(opts = {}) {
12822
13006
  async function connectAndServe(transport, opts = {}) {
12823
13007
  const server = createServer(opts);
12824
13008
  await server.connect(transport);
13009
+ const cliVersion = process.env["CROSSCHECK_CLI_VERSION"];
13010
+ void maybeCheckForUpdate({
13011
+ currentVersion: cliVersion ?? SERVER_VERSION,
13012
+ packageName: cliVersion ? "crosscheck-cli" : "crosscheck-mcp"
13013
+ });
12825
13014
  return server;
12826
13015
  }
12827
13016
  function buildToolRegistry(opts) {
@@ -12937,6 +13126,11 @@ function installShutdownHandlers(bridge) {
12937
13126
  ]).catch(() => {
12938
13127
  });
12939
13128
  }
13129
+ await Promise.race([
13130
+ flush(),
13131
+ new Promise((resolve2) => setTimeout(resolve2, SHUTDOWN_TIMEOUT_MS))
13132
+ ]).catch(() => {
13133
+ });
12940
13134
  process.stderr.write(`crosscheck-agent: shutdown (${reason})
12941
13135
  `);
12942
13136
  process.exit(0);