context-compress 2026.3.20 → 2026.3.22

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.
@@ -11061,7 +11061,7 @@ function debug(...args) {
11061
11061
  }
11062
11062
 
11063
11063
  // src/server.ts
11064
- import { readFileSync as readFileSync2, realpathSync, statSync } from "node:fs";
11064
+ import { readFileSync as readFileSync3, realpathSync, statSync } from "node:fs";
11065
11065
  import { dirname, join as join4, resolve } from "node:path";
11066
11066
  import { fileURLToPath } from "node:url";
11067
11067
 
@@ -21216,7 +21216,182 @@ import { execFileSync, spawn } from "node:child_process";
21216
21216
  import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
21217
21217
  import { tmpdir } from "node:os";
21218
21218
  import { join as join2 } from "node:path";
21219
+
21220
+ // src/filters.ts
21221
+ function applyCommandFilter(code, stdout) {
21222
+ const cmd = code.trim().split(/\s+/)[0];
21223
+ const fullCmd = code.trim();
21224
+ if (cmd === "git") return filterGit(fullCmd, stdout);
21225
+ if (cmd === "npm" || cmd === "yarn" || cmd === "pnpm" || cmd === "bun")
21226
+ return filterPackageManager(fullCmd, stdout);
21227
+ if (fullCmd.includes("test") || fullCmd.includes("jest") || fullCmd.includes("vitest") || fullCmd.includes("pytest") || fullCmd.includes("cargo test")) {
21228
+ return filterTestOutput(stdout);
21229
+ }
21230
+ if (cmd === "cargo" || cmd === "make" || cmd === "gradle")
21231
+ return filterBuildOutput(fullCmd, stdout);
21232
+ if (cmd === "docker" || cmd === "kubectl") return filterContainerOutput(fullCmd, stdout);
21233
+ if (cmd === "ls" || cmd === "find" || cmd === "tree") return filterFileList(fullCmd, stdout);
21234
+ return { output: stdout, filtered: false };
21235
+ }
21236
+ function filterGit(cmd, stdout) {
21237
+ if (/git\s+(push|pull|fetch|clone)/.test(cmd)) {
21238
+ const lines = stdout.split("\n");
21239
+ const filtered = lines.filter(
21240
+ (l) => !l.startsWith("remote: Counting") && !l.startsWith("remote: Compressing") && !l.startsWith("remote: Total") && !l.includes("Unpacking objects:") && !l.includes("Receiving objects:") && !l.includes("Resolving deltas:") && !/^\s*\d+%/.test(l)
21241
+ );
21242
+ return { output: filtered.join("\n"), filtered: true };
21243
+ }
21244
+ if (/git\s+status/.test(cmd)) {
21245
+ const lines = stdout.split("\n");
21246
+ const filtered = lines.filter((l) => !l.startsWith(" (use ") && l.trim() !== "");
21247
+ return { output: filtered.join("\n"), filtered: true };
21248
+ }
21249
+ return { output: stdout, filtered: false };
21250
+ }
21251
+ function filterPackageManager(cmd, stdout) {
21252
+ if (/\b(install|add|i)\b/.test(cmd)) {
21253
+ const lines = stdout.split("\n");
21254
+ const filtered = lines.filter(
21255
+ (l) => !l.startsWith("npm warn") && !l.includes("packages are looking for funding") && !l.includes("run `npm fund`") && !l.startsWith("npm notice") && !/^[\s\u2502\u251C\u2514\u2500]+$/.test(l) && // tree-drawing characters
21256
+ !/^\s*$/.test(l)
21257
+ );
21258
+ return { output: filtered.join("\n"), filtered: true };
21259
+ }
21260
+ if (/\btest\b/.test(cmd)) {
21261
+ return filterTestOutput(stdout);
21262
+ }
21263
+ return { output: stdout, filtered: false };
21264
+ }
21265
+ var FAIL_MARKER_RE = /^\s*[\u2717\u2718\u00D7]\s/;
21266
+ var FAIL_WORD_RE = /\bFAIL\b/;
21267
+ var FAILED_RE = /\bfailed?\b/i;
21268
+ var ERROR_RE = /\bERROR\b/;
21269
+ var SUMMARY_RE = /^\s*(Tests?|Suites?|Test Suites)\s*:|^\s*(pass|fail|skip|pending|todo)\s|\b\d+\s+(passing|failing|pending|skipped)\b|^(ok|not ok)\s|^\u2139\s|^(PASS|FAIL)\s/i;
21270
+ function isFailMarker(line) {
21271
+ return FAIL_MARKER_RE.test(line) || FAIL_WORD_RE.test(line) || FAILED_RE.test(line) || ERROR_RE.test(line);
21272
+ }
21273
+ function isSummaryLine(line) {
21274
+ return SUMMARY_RE.test(line);
21275
+ }
21276
+ function filterTestOutput(stdout) {
21277
+ const lines = stdout.split("\n");
21278
+ const failures = [];
21279
+ const summary = [];
21280
+ let inFailure = false;
21281
+ let failCount = 0;
21282
+ for (const line of lines) {
21283
+ if (isFailMarker(line)) {
21284
+ inFailure = true;
21285
+ failCount++;
21286
+ }
21287
+ if (inFailure) {
21288
+ failures.push(line);
21289
+ if (line.trim() === "" && failures.length > 3) inFailure = false;
21290
+ }
21291
+ if (isSummaryLine(line)) {
21292
+ summary.push(line);
21293
+ }
21294
+ }
21295
+ if (failCount === 0 && summary.length > 0) {
21296
+ return { output: summary.join("\n"), filtered: true };
21297
+ }
21298
+ if (failures.length > 0) {
21299
+ const result = [...failures, "", ...summary].join("\n");
21300
+ return { output: result, filtered: true };
21301
+ }
21302
+ return { output: stdout, filtered: false };
21303
+ }
21304
+ function filterBuildOutput(cmd, stdout) {
21305
+ const lines = stdout.split("\n");
21306
+ const filtered = lines.filter(
21307
+ (l) => !l.includes("Downloading") && !l.includes("Downloaded") && !/Compiling\s+\d+\s+of\s+\d+/.test(l) && !l.includes("Blocking waiting for file lock") && !/^\s*$/.test(l)
21308
+ );
21309
+ return { output: filtered.join("\n"), filtered: filtered.length < lines.length };
21310
+ }
21311
+ function filterContainerOutput(cmd, stdout) {
21312
+ if (/docker\s+build/.test(cmd)) {
21313
+ const lines = stdout.split("\n");
21314
+ const filtered = lines.filter(
21315
+ (l) => !l.startsWith(" ---> ") && !l.startsWith("Sending build context")
21316
+ );
21317
+ return { output: filtered.join("\n"), filtered: true };
21318
+ }
21319
+ return { output: stdout, filtered: false };
21320
+ }
21321
+ function filterFileList(cmd, stdout) {
21322
+ const lines = stdout.split("\n").filter((l) => l.trim() !== "");
21323
+ if (lines.length <= 30) return { output: stdout, filtered: false };
21324
+ if (cmd.includes("-R") || cmd.startsWith("find")) {
21325
+ const dirs = /* @__PURE__ */ new Map();
21326
+ for (const line of lines) {
21327
+ const parts = line.split("/");
21328
+ const dir = parts.length > 1 ? parts.slice(0, -1).join("/") : ".";
21329
+ dirs.set(dir, (dirs.get(dir) ?? 0) + 1);
21330
+ }
21331
+ if (dirs.size > 5 && lines.length > 50) {
21332
+ const summary = Array.from(dirs.entries()).sort((a, b) => b[1] - a[1]).map(([dir, count]) => ` ${dir}/ (${count} files)`).join("\n");
21333
+ return {
21334
+ output: `${lines.length} files found:
21335
+ ${summary}`,
21336
+ filtered: true
21337
+ };
21338
+ }
21339
+ }
21340
+ return { output: stdout, filtered: false };
21341
+ }
21342
+
21343
+ // src/utils.ts
21344
+ function detectInjectionPatterns(content) {
21345
+ const warnings = [];
21346
+ const patterns = [
21347
+ { re: /ignore\s+(all\s+)?previous\s+instructions/i, label: "instruction override" },
21348
+ { re: /you\s+are\s+now\s+/i, label: "role reassignment" },
21349
+ {
21350
+ re: /(?:^|\n)\s*system\s*:\s*(?:you are|you're|as an? )/im,
21351
+ label: "system prompt injection"
21352
+ },
21353
+ { re: /\[INST\]|\[\/INST\]|<\|im_start\|>|<\|im_end\|>/i, label: "chat template injection" },
21354
+ { re: /\n\n(?:Human|Assistant):/m, label: "chat delimiter injection" },
21355
+ { re: /reveal\s+(your|the)\s+(system|secret|confidential)/i, label: "data exfiltration" },
21356
+ { re: /act\s+as\s+(if\s+you\s+are|a)\s+/i, label: "role manipulation" }
21357
+ ];
21358
+ for (const { re, label } of patterns) {
21359
+ if (re.test(content)) {
21360
+ warnings.push(label);
21361
+ }
21362
+ }
21363
+ return warnings;
21364
+ }
21365
+ async function limitConcurrency(tasks, limit) {
21366
+ const results = new Array(tasks.length);
21367
+ let nextIndex = 0;
21368
+ async function runNext() {
21369
+ while (nextIndex < tasks.length) {
21370
+ const index = nextIndex++;
21371
+ try {
21372
+ const value = await tasks[index]();
21373
+ results[index] = { status: "fulfilled", value };
21374
+ } catch (reason) {
21375
+ results[index] = { status: "rejected", reason };
21376
+ }
21377
+ }
21378
+ }
21379
+ const workers = Array.from({ length: Math.min(limit, tasks.length) }, () => runNext());
21380
+ await Promise.all(workers);
21381
+ return results;
21382
+ }
21383
+ function formatBytes(bytes) {
21384
+ if (bytes < 1024) return `${bytes}B`;
21385
+ if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)}KB`;
21386
+ return `${(bytes / (1024 * 1024)).toFixed(1)}MB`;
21387
+ }
21388
+
21389
+ // src/executor.ts
21219
21390
  var DEFAULT_TIMEOUT = 3e4;
21391
+ var ANSI_RE = /\x1b\[[0-9;]*[a-zA-Z]/;
21392
+ function stripAnsi(str) {
21393
+ return str.replace(new RegExp(ANSI_RE.source, "g"), "");
21394
+ }
21220
21395
  var SAFE_ENV_KEYS = [
21221
21396
  "PATH",
21222
21397
  "HOME",
@@ -21269,6 +21444,21 @@ function killProcessTree(pid) {
21269
21444
  }
21270
21445
  }
21271
21446
  }
21447
+ function stripProgressLines(output) {
21448
+ const lines = output.split("\n");
21449
+ const filtered = lines.filter((l) => {
21450
+ const trimmed = l.trim();
21451
+ if (ANSI_RE.test(l) && trimmed.replace(new RegExp(ANSI_RE.source, "g"), "").trim() === "")
21452
+ return false;
21453
+ if (/^[\s\[│├└─═━▓░█▒▏▎▍▌▋▊▉\]>=#\-.\d%]+$/.test(trimmed) && trimmed.length > 3) return false;
21454
+ if (/^[⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏\-\\|/]\s/.test(trimmed)) return false;
21455
+ if (/(?:downloading|uploading|fetching|resolving)\s+[\d.]+\s*[kmg]?b/i.test(trimmed))
21456
+ return false;
21457
+ if (/\d+\.?\d*\s*[kmg]?b\/s/i.test(trimmed) && /eta|remaining/i.test(trimmed)) return false;
21458
+ return true;
21459
+ });
21460
+ return filtered.join("\n");
21461
+ }
21272
21462
  function deduplicateLines(output) {
21273
21463
  const lines = output.split("\n");
21274
21464
  if (lines.length < 3) return output;
@@ -21300,12 +21490,12 @@ function deduplicateLines(output) {
21300
21490
  function groupErrorLines(output) {
21301
21491
  const lines = output.split("\n");
21302
21492
  if (lines.length < 5) return output;
21303
- const ERROR_RE = /^(.*?(?:error|warning|Error|Warning|ERR|WARN)[:\s])\s*(.+?)(?:\s+(?:at|in|on)\s+(?:line\s+)?(\d+))?$/i;
21493
+ const ERROR_RE2 = /^(.*?(?:error|warning|Error|Warning|ERR|WARN)[:\s])\s*(.+?)(?:\s+(?:at|in|on)\s+(?:line\s+)?(\d+))?$/i;
21304
21494
  const errorGroups = /* @__PURE__ */ new Map();
21305
21495
  const resultLines = [];
21306
21496
  let groupedCount = 0;
21307
21497
  for (const line of lines) {
21308
- const match = line.match(ERROR_RE);
21498
+ const match = line.match(ERROR_RE2);
21309
21499
  if (match) {
21310
21500
  const prefix = match[1].trim();
21311
21501
  const msg = match[2].trim();
@@ -21380,20 +21570,26 @@ function smartTruncate(output, maxBytes) {
21380
21570
  `;
21381
21571
  return headLines.join("\n") + separator + tailLines.join("\n");
21382
21572
  }
21383
- function formatBytes(bytes) {
21384
- if (bytes < 1024) return `${bytes}B`;
21385
- if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)}KB`;
21386
- return `${(bytes / (1024 * 1024)).toFixed(1)}MB`;
21387
- }
21388
21573
  var SubprocessExecutor = class {
21389
21574
  runtimes;
21390
21575
  config;
21391
21576
  env;
21577
+ activeProcesses = /* @__PURE__ */ new Set();
21392
21578
  constructor(runtimes, config3) {
21393
21579
  this.runtimes = runtimes;
21394
21580
  this.config = config3;
21395
21581
  this.env = buildEnv(config3);
21396
21582
  }
21583
+ /** Kill all active child processes and their process trees. */
21584
+ shutdown() {
21585
+ for (const proc of this.activeProcesses) {
21586
+ try {
21587
+ if (proc.pid) killProcessTree(proc.pid);
21588
+ } catch {
21589
+ }
21590
+ }
21591
+ this.activeProcesses.clear();
21592
+ }
21397
21593
  /**
21398
21594
  * Execute code in a subprocess.
21399
21595
  */
@@ -21451,7 +21647,8 @@ var SubprocessExecutor = class {
21451
21647
  tmpDir,
21452
21648
  timeout,
21453
21649
  maxOutput,
21454
- plugin.needsShell
21650
+ plugin.needsShell,
21651
+ opts.language === "shell" ? opts.code : void 0
21455
21652
  );
21456
21653
  } finally {
21457
21654
  setTimeout(() => this.cleanupTempDir(tmpDir), 100).unref();
@@ -21478,7 +21675,7 @@ var SubprocessExecutor = class {
21478
21675
  }
21479
21676
  return this.execute({ ...opts, code });
21480
21677
  }
21481
- spawnAndCapture(cmd, args, cwd, timeout, maxOutput, useShell) {
21678
+ spawnAndCapture(cmd, args, cwd, timeout, maxOutput, useShell, shellCode) {
21482
21679
  return new Promise((resolve2) => {
21483
21680
  const hardCap = this.config.hardCapBytes;
21484
21681
  const stdoutChunks = [];
@@ -21495,6 +21692,7 @@ var SubprocessExecutor = class {
21495
21692
  shell: useShell,
21496
21693
  detached: process.platform !== "win32"
21497
21694
  });
21695
+ this.activeProcesses.add(proc);
21498
21696
  proc.stdout?.on("data", (chunk) => {
21499
21697
  totalBytes += chunk.length;
21500
21698
  if (totalBytes > hardCap) {
@@ -21515,6 +21713,7 @@ var SubprocessExecutor = class {
21515
21713
  });
21516
21714
  proc.on("error", (err) => {
21517
21715
  debug("Process error:", err.message);
21716
+ this.activeProcesses.delete(proc);
21518
21717
  if (!resolved) {
21519
21718
  resolved = true;
21520
21719
  resolve2({
@@ -21527,6 +21726,7 @@ var SubprocessExecutor = class {
21527
21726
  }
21528
21727
  });
21529
21728
  proc.on("close", (code) => {
21729
+ this.activeProcesses.delete(proc);
21530
21730
  if (resolved) return;
21531
21731
  resolved = true;
21532
21732
  let stdout = Buffer.concat(stdoutChunks).toString("utf-8");
@@ -21540,8 +21740,18 @@ var SubprocessExecutor = class {
21540
21740
  stdout += `
21541
21741
  [output capped at ${formatBytes(hardCap)} \u2014 process killed]`;
21542
21742
  }
21543
- stdout = deduplicateLines(stdout);
21544
- stdout = groupErrorLines(stdout);
21743
+ if (shellCode && stdout) {
21744
+ const filtered = applyCommandFilter(shellCode, stdout);
21745
+ if (filtered.filtered) {
21746
+ stdout = filtered.output;
21747
+ }
21748
+ }
21749
+ stdout = stripAnsi(stdout);
21750
+ if (stdout.length > 1e4) {
21751
+ stdout = stripProgressLines(stdout);
21752
+ stdout = deduplicateLines(stdout);
21753
+ stdout = groupErrorLines(stdout);
21754
+ }
21545
21755
  const truncated = Buffer.byteLength(stdout) > maxOutput;
21546
21756
  if (truncated) {
21547
21757
  stdout = smartTruncate(stdout, maxOutput);
@@ -21612,24 +21822,24 @@ async function resolveAndValidate(url) {
21612
21822
  let resolvedIp = null;
21613
21823
  let v4Error = false;
21614
21824
  let v6Error = false;
21615
- try {
21616
- const { address } = await dns.promises.lookup(hostname2, { family: 4 });
21617
- if (isPrivateHost(address)) {
21618
- throw new Error(`Blocked: ${hostname2} resolved to private IP ${address}`);
21619
- }
21620
- resolvedIp = address;
21621
- } catch (err) {
21622
- if (err instanceof Error && err.message.startsWith("Blocked:")) throw err;
21825
+ const [v4Result, v6Result] = await Promise.allSettled([
21826
+ dns.promises.lookup(hostname2, { family: 4 }),
21827
+ dns.promises.lookup(hostname2, { family: 6 })
21828
+ ]);
21829
+ if (v4Result.status === "fulfilled") {
21830
+ if (isPrivateHost(v4Result.value.address)) {
21831
+ throw new Error(`Blocked: ${hostname2} resolved to private IP ${v4Result.value.address}`);
21832
+ }
21833
+ resolvedIp = v4Result.value.address;
21834
+ } else {
21623
21835
  v4Error = true;
21624
21836
  }
21625
- try {
21626
- const { address } = await dns.promises.lookup(hostname2, { family: 6 });
21627
- if (isPrivateHost(address)) {
21628
- throw new Error(`Blocked: ${hostname2} resolved to private IPv6 ${address}`);
21837
+ if (v6Result.status === "fulfilled") {
21838
+ if (isPrivateHost(v6Result.value.address)) {
21839
+ throw new Error(`Blocked: ${hostname2} resolved to private IPv6 ${v6Result.value.address}`);
21629
21840
  }
21630
- if (!resolvedIp) resolvedIp = address;
21631
- } catch (err) {
21632
- if (err instanceof Error && err.message.startsWith("Blocked:")) throw err;
21841
+ if (!resolvedIp) resolvedIp = v6Result.value.address;
21842
+ } else {
21633
21843
  v6Error = true;
21634
21844
  }
21635
21845
  if (v4Error && v6Error) {
@@ -21954,6 +22164,7 @@ function hasBun(runtimes) {
21954
22164
  }
21955
22165
 
21956
22166
  // src/stats.ts
22167
+ import { readFileSync as readFileSync2, writeFileSync as writeFileSync2 } from "node:fs";
21957
22168
  var BAR_WIDTH = 20;
21958
22169
  function asciiBar(ratio, width = BAR_WIDTH) {
21959
22170
  const filled = Math.round(ratio * width);
@@ -21973,6 +22184,10 @@ var SessionTracker = class {
21973
22184
  bytesSandboxed: 0,
21974
22185
  sessionStart: Date.now()
21975
22186
  };
22187
+ cumulativeFile;
22188
+ constructor(cumulativeFile) {
22189
+ this.cumulativeFile = cumulativeFile ?? null;
22190
+ }
21976
22191
  trackCall(toolName, responseBytes) {
21977
22192
  this.stats.calls[toolName] = (this.stats.calls[toolName] ?? 0) + 1;
21978
22193
  this.stats.bytesReturned[toolName] = (this.stats.bytesReturned[toolName] ?? 0) + responseBytes;
@@ -21986,6 +22201,47 @@ var SessionTracker = class {
21986
22201
  getSnapshot() {
21987
22202
  return { ...this.stats };
21988
22203
  }
22204
+ /** Load cumulative stats from disk */
22205
+ loadCumulative() {
22206
+ if (!this.cumulativeFile) return null;
22207
+ try {
22208
+ const data = readFileSync2(this.cumulativeFile, "utf-8");
22209
+ return JSON.parse(data);
22210
+ } catch {
22211
+ return null;
22212
+ }
22213
+ }
22214
+ /** Save current session stats to cumulative file */
22215
+ saveCumulative() {
22216
+ if (!this.cumulativeFile) return;
22217
+ const snap = this.stats;
22218
+ const keptOut = snap.bytesIndexed + snap.bytesSandboxed;
22219
+ const totalReturned = Object.values(snap.bytesReturned).reduce((a, b) => a + b, 0);
22220
+ const cumulative = this.loadCumulative() ?? {
22221
+ totalBytesSaved: 0,
22222
+ totalBytesProcessed: 0,
22223
+ totalCalls: 0,
22224
+ totalSessions: 0,
22225
+ firstSeen: (/* @__PURE__ */ new Date()).toISOString(),
22226
+ lastSeen: (/* @__PURE__ */ new Date()).toISOString(),
22227
+ perCommand: {}
22228
+ };
22229
+ cumulative.totalBytesSaved += keptOut;
22230
+ cumulative.totalBytesProcessed += keptOut + totalReturned;
22231
+ cumulative.totalCalls += Object.values(snap.calls).reduce((a, b) => a + b, 0);
22232
+ cumulative.totalSessions += 1;
22233
+ cumulative.lastSeen = (/* @__PURE__ */ new Date()).toISOString();
22234
+ for (const [name, calls] of Object.entries(snap.calls)) {
22235
+ if (!cumulative.perCommand[name]) {
22236
+ cumulative.perCommand[name] = { calls: 0, bytesSaved: 0 };
22237
+ }
22238
+ cumulative.perCommand[name].calls += calls;
22239
+ }
22240
+ try {
22241
+ writeFileSync2(this.cumulativeFile, JSON.stringify(cumulative, null, 2));
22242
+ } catch {
22243
+ }
22244
+ }
21989
22245
  formatReport() {
21990
22246
  const snap = this.stats;
21991
22247
  const elapsed = Date.now() - snap.sessionStart;
@@ -22046,6 +22302,18 @@ var SessionTracker = class {
22046
22302
  `
22047
22303
  Context-compress kept ${formatBytes(keptOut)} out of context (${reductionPct}% savings).`
22048
22304
  );
22305
+ const cumulative = this.loadCumulative();
22306
+ if (cumulative) {
22307
+ lines.push("\n## Cumulative Savings (All Sessions)\n");
22308
+ lines.push("| Metric | Value |");
22309
+ lines.push("|--------|-------|");
22310
+ lines.push(`| Sessions tracked | ${cumulative.totalSessions} |`);
22311
+ lines.push(`| Total data processed | ${formatBytes(cumulative.totalBytesProcessed)} |`);
22312
+ lines.push(`| Total kept out of context | ${formatBytes(cumulative.totalBytesSaved)} |`);
22313
+ const cumTokensMid = Math.round(cumulative.totalBytesSaved / 4);
22314
+ lines.push(`| Est. total tokens saved | ~${cumTokensMid.toLocaleString()} |`);
22315
+ lines.push(`| Tracking since | ${cumulative.firstSeen.split("T")[0]} |`);
22316
+ }
22049
22317
  return lines.join("\n");
22050
22318
  }
22051
22319
  };
@@ -22216,17 +22484,20 @@ function sanitizeQuery(raw) {
22216
22484
  const words = q.split(/\s+/).filter((w) => w.length >= 2).map((w) => `"${w}"`);
22217
22485
  return words.length > 0 ? words.join(" OR ") : "";
22218
22486
  }
22219
- function levenshtein(a, b) {
22487
+ function levenshtein(a, b, maxDist) {
22220
22488
  if (a.length === 0) return b.length;
22221
22489
  if (b.length === 0) return a.length;
22222
22490
  let prev = Array.from({ length: b.length + 1 }, (_, i) => i);
22223
22491
  let curr = new Array(b.length + 1);
22224
22492
  for (let i = 1; i <= a.length; i++) {
22225
22493
  curr[0] = i;
22494
+ let rowMin = curr[0];
22226
22495
  for (let j = 1; j <= b.length; j++) {
22227
22496
  const cost = a[i - 1] === b[j - 1] ? 0 : 1;
22228
22497
  curr[j] = Math.min(prev[j] + 1, curr[j - 1] + 1, prev[j - 1] + cost);
22498
+ if (curr[j] < rowMin) rowMin = curr[j];
22229
22499
  }
22500
+ if (maxDist !== void 0 && rowMin > maxDist) return maxDist + 1;
22230
22501
  [prev, curr] = [curr, prev];
22231
22502
  }
22232
22503
  return prev[b.length];
@@ -22422,7 +22693,7 @@ var ContentStore = class {
22422
22693
  let bestWord = word;
22423
22694
  let bestDist = maxDist + 1;
22424
22695
  for (const { word: candidate } of candidates) {
22425
- const dist = levenshtein(word.toLowerCase(), candidate.toLowerCase());
22696
+ const dist = levenshtein(word.toLowerCase(), candidate.toLowerCase(), maxDist);
22426
22697
  if (dist < bestDist && dist <= maxDist) {
22427
22698
  bestDist = dist;
22428
22699
  bestWord = candidate;
@@ -22439,7 +22710,8 @@ var ContentStore = class {
22439
22710
  updateVocabulary(content) {
22440
22711
  const currentCount = this.vocabCountStmt.get().cnt;
22441
22712
  if (currentCount >= MAX_VOCABULARY) return;
22442
- const words = content.split(WORD_SPLIT_RE).filter((w) => w.length >= 3 && !STOPWORDS.has(w.toLowerCase()));
22713
+ const sample = content.length > 51200 ? content.slice(0, 51200) : content;
22714
+ const words = sample.split(WORD_SPLIT_RE).filter((w) => w.length >= 3 && !STOPWORDS.has(w.toLowerCase()));
22443
22715
  const unique = new Set(words.map((w) => w.toLowerCase()));
22444
22716
  const insert = this.vocabInsertStmt;
22445
22717
  let added = 0;
@@ -22659,7 +22931,7 @@ function getVersion() {
22659
22931
  try {
22660
22932
  const __dirname = dirname(fileURLToPath(import.meta.url));
22661
22933
  const pkgPath = join4(__dirname, "..", "package.json");
22662
- const pkg = JSON.parse(readFileSync2(pkgPath, "utf-8"));
22934
+ const pkg = JSON.parse(readFileSync3(pkgPath, "utf-8"));
22663
22935
  return pkg.version ?? "1.0.0";
22664
22936
  } catch {
22665
22937
  return "1.0.0";
@@ -22677,45 +22949,6 @@ function compactLabel(normal, level) {
22677
22949
  }
22678
22950
  return normal;
22679
22951
  }
22680
- async function limitConcurrency(tasks, limit) {
22681
- const results = new Array(tasks.length);
22682
- let nextIndex = 0;
22683
- async function runNext() {
22684
- while (nextIndex < tasks.length) {
22685
- const index = nextIndex++;
22686
- try {
22687
- const value = await tasks[index]();
22688
- results[index] = { status: "fulfilled", value };
22689
- } catch (reason) {
22690
- results[index] = { status: "rejected", reason };
22691
- }
22692
- }
22693
- }
22694
- const workers = Array.from({ length: Math.min(limit, tasks.length) }, () => runNext());
22695
- await Promise.all(workers);
22696
- return results;
22697
- }
22698
- function detectInjectionPatterns(content) {
22699
- const warnings = [];
22700
- const patterns = [
22701
- { re: /ignore\s+(all\s+)?previous\s+instructions/i, label: "instruction override" },
22702
- { re: /you\s+are\s+now\s+/i, label: "role reassignment" },
22703
- {
22704
- re: /(?:^|\n)\s*system\s*:\s*(?:you are|you're|as an? )/im,
22705
- label: "system prompt injection"
22706
- },
22707
- { re: /\[INST\]|\[\/INST\]|<\|im_start\|>|<\|im_end\|>/i, label: "chat template injection" },
22708
- { re: /\n\n(?:Human|Assistant):/m, label: "chat delimiter injection" },
22709
- { re: /reveal\s+(your|the)\s+(system|secret|confidential)/i, label: "data exfiltration" },
22710
- { re: /act\s+as\s+(if\s+you\s+are|a)\s+/i, label: "role manipulation" }
22711
- ];
22712
- for (const { re, label } of patterns) {
22713
- if (re.test(content)) {
22714
- warnings.push(label);
22715
- }
22716
- }
22717
- return warnings;
22718
- }
22719
22952
  async function createServer(config3) {
22720
22953
  const version2 = getVersion();
22721
22954
  debug("Version:", version2);
@@ -22733,7 +22966,22 @@ async function createServer(config3) {
22733
22966
  store = new ContentStore(":memory:");
22734
22967
  dbFallback = true;
22735
22968
  }
22736
- const tracker = new SessionTracker();
22969
+ const cumulativeFile = config3.persistDb ? join4(config3.dbDir ?? join4(projectDir, ".context-compress"), "stats.json") : void 0;
22970
+ const tracker = new SessionTracker(cumulativeFile);
22971
+ let activeExecutions = 0;
22972
+ const MAX_CONCURRENT_EXECUTIONS = 8;
22973
+ const EXECUTION_LIMIT_ERROR = "Error: too many concurrent executions. Try again shortly.";
22974
+ async function withExecutionLimit(fn) {
22975
+ if (activeExecutions >= MAX_CONCURRENT_EXECUTIONS) {
22976
+ throw new Error(EXECUTION_LIMIT_ERROR);
22977
+ }
22978
+ activeExecutions++;
22979
+ try {
22980
+ return await fn();
22981
+ } finally {
22982
+ activeExecutions--;
22983
+ }
22984
+ }
22737
22985
  function applyIntentFilter(output, intent, sourceLabel) {
22738
22986
  if (Buffer.byteLength(output) <= config3.intentSearchThreshold) return output;
22739
22987
  const indexed = store.index(output, sourceLabel);
@@ -22758,6 +23006,14 @@ Searchable terms: ${terms.join(", ")}
22758
23006
  return compactLabel(filtered, config3.compressionLevel);
22759
23007
  }
22760
23008
  const shutdown = () => {
23009
+ try {
23010
+ tracker.saveCumulative();
23011
+ } catch {
23012
+ }
23013
+ try {
23014
+ executor.shutdown();
23015
+ } catch {
23016
+ }
22761
23017
  try {
22762
23018
  store.close();
22763
23019
  } catch {
@@ -22766,6 +23022,16 @@ Searchable terms: ${terms.join(", ")}
22766
23022
  process.on("SIGINT", shutdown);
22767
23023
  process.on("SIGTERM", shutdown);
22768
23024
  process.on("beforeExit", shutdown);
23025
+ process.on("uncaughtException", (err) => {
23026
+ debug("Uncaught exception:", err);
23027
+ shutdown();
23028
+ process.exit(1);
23029
+ });
23030
+ process.on("unhandledRejection", (err) => {
23031
+ debug("Unhandled rejection:", err);
23032
+ shutdown();
23033
+ process.exit(1);
23034
+ });
22769
23035
  const searchCalls = [];
22770
23036
  const server2 = new McpServer({
22771
23037
  name: "context-compress",
@@ -22787,7 +23053,24 @@ PREFER THIS OVER BASH for: API calls (gh, curl, aws), test runners (npm test, py
22787
23053
  timeout: external_exports.number().default(3e4).describe("Max execution time in ms")
22788
23054
  },
22789
23055
  async ({ language, code, intent, timeout }) => {
22790
- const result = await executor.execute({ language, code, timeout });
23056
+ const codeBytes = Buffer.byteLength(code);
23057
+ if (codeBytes > 1024e3) {
23058
+ return {
23059
+ content: [
23060
+ {
23061
+ type: "text",
23062
+ text: `Error: code too large (${(codeBytes / 1024).toFixed(0)}KB). Max 1MB.`
23063
+ }
23064
+ ]
23065
+ };
23066
+ }
23067
+ let result;
23068
+ try {
23069
+ result = await withExecutionLimit(() => executor.execute({ language, code, timeout }));
23070
+ } catch (e) {
23071
+ const msg = e instanceof Error ? e.message : String(e);
23072
+ return { content: [{ type: "text", text: msg }] };
23073
+ }
22791
23074
  if (result.networkBytes) {
22792
23075
  tracker.trackSandboxed(result.networkBytes);
22793
23076
  }
@@ -22819,6 +23102,17 @@ ${result.stderr}`;
22819
23102
  timeout: external_exports.number().default(3e4).describe("Max execution time in ms")
22820
23103
  },
22821
23104
  async ({ path: filePath, language, code, intent, timeout }) => {
23105
+ const codeBytes = Buffer.byteLength(code);
23106
+ if (codeBytes > 1024e3) {
23107
+ return {
23108
+ content: [
23109
+ {
23110
+ type: "text",
23111
+ text: `Error: code too large (${(codeBytes / 1024).toFixed(0)}KB). Max 1MB.`
23112
+ }
23113
+ ]
23114
+ };
23115
+ }
22822
23116
  const absPath = resolve(projectDir, filePath);
22823
23117
  if (!isWithinProject(absPath)) {
22824
23118
  return {
@@ -22830,12 +23124,20 @@ ${result.stderr}`;
22830
23124
  ]
22831
23125
  };
22832
23126
  }
22833
- const result = await executor.executeFile({
22834
- language,
22835
- code,
22836
- filePath: absPath,
22837
- timeout
22838
- });
23127
+ let result;
23128
+ try {
23129
+ result = await withExecutionLimit(
23130
+ () => executor.executeFile({
23131
+ language,
23132
+ code,
23133
+ filePath: absPath,
23134
+ timeout
23135
+ })
23136
+ );
23137
+ } catch (e) {
23138
+ const msg = e instanceof Error ? e.message : String(e);
23139
+ return { content: [{ type: "text", text: msg }] };
23140
+ }
22839
23141
  let output = result.stdout;
22840
23142
  if (result.stderr && result.exitCode !== 0) {
22841
23143
  output += `
@@ -22886,7 +23188,7 @@ ${result.stderr}`;
22886
23188
  ]
22887
23189
  };
22888
23190
  }
22889
- text = readFileSync2(absPath, "utf-8");
23191
+ text = readFileSync3(absPath, "utf-8");
22890
23192
  label = source ?? filePath;
22891
23193
  } catch (e) {
22892
23194
  const msg = e instanceof Error ? e.message : String(e);
@@ -23020,11 +23322,19 @@ ${hit.snippet}
23020
23322
  }
23021
23323
  const label = source ?? url;
23022
23324
  const fetchCode = buildFetchCode(url, resolvedIp);
23023
- const result = await executor.execute({
23024
- language: "javascript",
23025
- code: fetchCode,
23026
- timeout: 3e4
23027
- });
23325
+ let result;
23326
+ try {
23327
+ result = await withExecutionLimit(
23328
+ () => executor.execute({
23329
+ language: "javascript",
23330
+ code: fetchCode,
23331
+ timeout: 3e4
23332
+ })
23333
+ );
23334
+ } catch (e) {
23335
+ const msg = e instanceof Error ? e.message : String(e);
23336
+ return { content: [{ type: "text", text: msg }] };
23337
+ }
23028
23338
  if (result.exitCode !== 0 || !result.stdout.trim()) {
23029
23339
  const errMsg = `Failed to fetch ${url}: ${result.stderr || "empty response"}`;
23030
23340
  tracker.trackCall("fetch_and_index", Buffer.byteLength(errMsg));
@@ -23076,11 +23386,13 @@ Searchable terms: ${terms.join(", ")}`;
23076
23386
  async ({ commands, queries, timeout }) => {
23077
23387
  const commandResults = await limitConcurrency(
23078
23388
  commands.map((cmd) => async () => {
23079
- const result = await executor.execute({
23080
- language: "shell",
23081
- code: cmd.command,
23082
- timeout
23083
- });
23389
+ const result = await withExecutionLimit(
23390
+ () => executor.execute({
23391
+ language: "shell",
23392
+ code: cmd.command,
23393
+ timeout
23394
+ })
23395
+ );
23084
23396
  return { label: cmd.label, result };
23085
23397
  }),
23086
23398
  4
@@ -23157,6 +23469,7 @@ Searchable terms: ${terms.join(", ")}`;
23157
23469
  "Returns context consumption statistics for the current session. Shows total bytes returned to context, breakdown by tool, call counts, estimated token usage, context savings ratio, and visual charts.",
23158
23470
  {},
23159
23471
  async () => {
23472
+ tracker.saveCumulative();
23160
23473
  const report = tracker.formatReport();
23161
23474
  tracker.trackCall("stats", Buffer.byteLength(report));
23162
23475
  return { content: [{ type: "text", text: report }] };
@@ -23262,7 +23575,14 @@ const resp = await fetch(url, { redirect: 'error' });`;
23262
23575
  }
23263
23576
  return `${fetchSetup}
23264
23577
  if (!resp.ok) { console.error("HTTP " + resp.status); process.exit(1); }
23578
+ const cl = resp.headers.get('content-length');
23579
+ if (cl && parseInt(cl, 10) > 10 * 1024 * 1024) {
23580
+ console.error("Response too large: " + cl + " bytes"); process.exit(1);
23581
+ }
23265
23582
  const html = await resp.text();
23583
+ if (html.length > 10 * 1024 * 1024) {
23584
+ console.error("Response body too large: " + html.length + " chars"); process.exit(1);
23585
+ }
23266
23586
 
23267
23587
  // Strip unwanted tags
23268
23588
  let md = html