crosscheck-mcp 0.1.9 → 0.1.11

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