context-compress 2026.3.20 → 2026.3.21

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.
@@ -21216,6 +21216,54 @@ 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/utils.ts
21221
+ function detectInjectionPatterns(content) {
21222
+ const warnings = [];
21223
+ const patterns = [
21224
+ { re: /ignore\s+(all\s+)?previous\s+instructions/i, label: "instruction override" },
21225
+ { re: /you\s+are\s+now\s+/i, label: "role reassignment" },
21226
+ {
21227
+ re: /(?:^|\n)\s*system\s*:\s*(?:you are|you're|as an? )/im,
21228
+ label: "system prompt injection"
21229
+ },
21230
+ { re: /\[INST\]|\[\/INST\]|<\|im_start\|>|<\|im_end\|>/i, label: "chat template injection" },
21231
+ { re: /\n\n(?:Human|Assistant):/m, label: "chat delimiter injection" },
21232
+ { re: /reveal\s+(your|the)\s+(system|secret|confidential)/i, label: "data exfiltration" },
21233
+ { re: /act\s+as\s+(if\s+you\s+are|a)\s+/i, label: "role manipulation" }
21234
+ ];
21235
+ for (const { re, label } of patterns) {
21236
+ if (re.test(content)) {
21237
+ warnings.push(label);
21238
+ }
21239
+ }
21240
+ return warnings;
21241
+ }
21242
+ async function limitConcurrency(tasks, limit) {
21243
+ const results = new Array(tasks.length);
21244
+ let nextIndex = 0;
21245
+ async function runNext() {
21246
+ while (nextIndex < tasks.length) {
21247
+ const index = nextIndex++;
21248
+ try {
21249
+ const value = await tasks[index]();
21250
+ results[index] = { status: "fulfilled", value };
21251
+ } catch (reason) {
21252
+ results[index] = { status: "rejected", reason };
21253
+ }
21254
+ }
21255
+ }
21256
+ const workers = Array.from({ length: Math.min(limit, tasks.length) }, () => runNext());
21257
+ await Promise.all(workers);
21258
+ return results;
21259
+ }
21260
+ function formatBytes(bytes) {
21261
+ if (bytes < 1024) return `${bytes}B`;
21262
+ if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)}KB`;
21263
+ return `${(bytes / (1024 * 1024)).toFixed(1)}MB`;
21264
+ }
21265
+
21266
+ // src/executor.ts
21219
21267
  var DEFAULT_TIMEOUT = 3e4;
21220
21268
  var SAFE_ENV_KEYS = [
21221
21269
  "PATH",
@@ -21380,20 +21428,26 @@ function smartTruncate(output, maxBytes) {
21380
21428
  `;
21381
21429
  return headLines.join("\n") + separator + tailLines.join("\n");
21382
21430
  }
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
21431
  var SubprocessExecutor = class {
21389
21432
  runtimes;
21390
21433
  config;
21391
21434
  env;
21435
+ activeProcesses = /* @__PURE__ */ new Set();
21392
21436
  constructor(runtimes, config3) {
21393
21437
  this.runtimes = runtimes;
21394
21438
  this.config = config3;
21395
21439
  this.env = buildEnv(config3);
21396
21440
  }
21441
+ /** Kill all active child processes and their process trees. */
21442
+ shutdown() {
21443
+ for (const proc of this.activeProcesses) {
21444
+ try {
21445
+ if (proc.pid) killProcessTree(proc.pid);
21446
+ } catch {
21447
+ }
21448
+ }
21449
+ this.activeProcesses.clear();
21450
+ }
21397
21451
  /**
21398
21452
  * Execute code in a subprocess.
21399
21453
  */
@@ -21495,6 +21549,7 @@ var SubprocessExecutor = class {
21495
21549
  shell: useShell,
21496
21550
  detached: process.platform !== "win32"
21497
21551
  });
21552
+ this.activeProcesses.add(proc);
21498
21553
  proc.stdout?.on("data", (chunk) => {
21499
21554
  totalBytes += chunk.length;
21500
21555
  if (totalBytes > hardCap) {
@@ -21515,6 +21570,7 @@ var SubprocessExecutor = class {
21515
21570
  });
21516
21571
  proc.on("error", (err) => {
21517
21572
  debug("Process error:", err.message);
21573
+ this.activeProcesses.delete(proc);
21518
21574
  if (!resolved) {
21519
21575
  resolved = true;
21520
21576
  resolve2({
@@ -21527,6 +21583,7 @@ var SubprocessExecutor = class {
21527
21583
  }
21528
21584
  });
21529
21585
  proc.on("close", (code) => {
21586
+ this.activeProcesses.delete(proc);
21530
21587
  if (resolved) return;
21531
21588
  resolved = true;
21532
21589
  let stdout = Buffer.concat(stdoutChunks).toString("utf-8");
@@ -21540,8 +21597,10 @@ var SubprocessExecutor = class {
21540
21597
  stdout += `
21541
21598
  [output capped at ${formatBytes(hardCap)} \u2014 process killed]`;
21542
21599
  }
21543
- stdout = deduplicateLines(stdout);
21544
- stdout = groupErrorLines(stdout);
21600
+ if (stdout.length > 1e4) {
21601
+ stdout = deduplicateLines(stdout);
21602
+ stdout = groupErrorLines(stdout);
21603
+ }
21545
21604
  const truncated = Buffer.byteLength(stdout) > maxOutput;
21546
21605
  if (truncated) {
21547
21606
  stdout = smartTruncate(stdout, maxOutput);
@@ -21612,24 +21671,24 @@ async function resolveAndValidate(url) {
21612
21671
  let resolvedIp = null;
21613
21672
  let v4Error = false;
21614
21673
  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;
21674
+ const [v4Result, v6Result] = await Promise.allSettled([
21675
+ dns.promises.lookup(hostname2, { family: 4 }),
21676
+ dns.promises.lookup(hostname2, { family: 6 })
21677
+ ]);
21678
+ if (v4Result.status === "fulfilled") {
21679
+ if (isPrivateHost(v4Result.value.address)) {
21680
+ throw new Error(`Blocked: ${hostname2} resolved to private IP ${v4Result.value.address}`);
21681
+ }
21682
+ resolvedIp = v4Result.value.address;
21683
+ } else {
21623
21684
  v4Error = true;
21624
21685
  }
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}`);
21686
+ if (v6Result.status === "fulfilled") {
21687
+ if (isPrivateHost(v6Result.value.address)) {
21688
+ throw new Error(`Blocked: ${hostname2} resolved to private IPv6 ${v6Result.value.address}`);
21629
21689
  }
21630
- if (!resolvedIp) resolvedIp = address;
21631
- } catch (err) {
21632
- if (err instanceof Error && err.message.startsWith("Blocked:")) throw err;
21690
+ if (!resolvedIp) resolvedIp = v6Result.value.address;
21691
+ } else {
21633
21692
  v6Error = true;
21634
21693
  }
21635
21694
  if (v4Error && v6Error) {
@@ -22216,17 +22275,20 @@ function sanitizeQuery(raw) {
22216
22275
  const words = q.split(/\s+/).filter((w) => w.length >= 2).map((w) => `"${w}"`);
22217
22276
  return words.length > 0 ? words.join(" OR ") : "";
22218
22277
  }
22219
- function levenshtein(a, b) {
22278
+ function levenshtein(a, b, maxDist) {
22220
22279
  if (a.length === 0) return b.length;
22221
22280
  if (b.length === 0) return a.length;
22222
22281
  let prev = Array.from({ length: b.length + 1 }, (_, i) => i);
22223
22282
  let curr = new Array(b.length + 1);
22224
22283
  for (let i = 1; i <= a.length; i++) {
22225
22284
  curr[0] = i;
22285
+ let rowMin = curr[0];
22226
22286
  for (let j = 1; j <= b.length; j++) {
22227
22287
  const cost = a[i - 1] === b[j - 1] ? 0 : 1;
22228
22288
  curr[j] = Math.min(prev[j] + 1, curr[j - 1] + 1, prev[j - 1] + cost);
22289
+ if (curr[j] < rowMin) rowMin = curr[j];
22229
22290
  }
22291
+ if (maxDist !== void 0 && rowMin > maxDist) return maxDist + 1;
22230
22292
  [prev, curr] = [curr, prev];
22231
22293
  }
22232
22294
  return prev[b.length];
@@ -22422,7 +22484,7 @@ var ContentStore = class {
22422
22484
  let bestWord = word;
22423
22485
  let bestDist = maxDist + 1;
22424
22486
  for (const { word: candidate } of candidates) {
22425
- const dist = levenshtein(word.toLowerCase(), candidate.toLowerCase());
22487
+ const dist = levenshtein(word.toLowerCase(), candidate.toLowerCase(), maxDist);
22426
22488
  if (dist < bestDist && dist <= maxDist) {
22427
22489
  bestDist = dist;
22428
22490
  bestWord = candidate;
@@ -22439,7 +22501,8 @@ var ContentStore = class {
22439
22501
  updateVocabulary(content) {
22440
22502
  const currentCount = this.vocabCountStmt.get().cnt;
22441
22503
  if (currentCount >= MAX_VOCABULARY) return;
22442
- const words = content.split(WORD_SPLIT_RE).filter((w) => w.length >= 3 && !STOPWORDS.has(w.toLowerCase()));
22504
+ const sample = content.length > 51200 ? content.slice(0, 51200) : content;
22505
+ const words = sample.split(WORD_SPLIT_RE).filter((w) => w.length >= 3 && !STOPWORDS.has(w.toLowerCase()));
22443
22506
  const unique = new Set(words.map((w) => w.toLowerCase()));
22444
22507
  const insert = this.vocabInsertStmt;
22445
22508
  let added = 0;
@@ -22677,45 +22740,6 @@ function compactLabel(normal, level) {
22677
22740
  }
22678
22741
  return normal;
22679
22742
  }
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
22743
  async function createServer(config3) {
22720
22744
  const version2 = getVersion();
22721
22745
  debug("Version:", version2);
@@ -22734,6 +22758,20 @@ async function createServer(config3) {
22734
22758
  dbFallback = true;
22735
22759
  }
22736
22760
  const tracker = new SessionTracker();
22761
+ let activeExecutions = 0;
22762
+ const MAX_CONCURRENT_EXECUTIONS = 8;
22763
+ const EXECUTION_LIMIT_ERROR = "Error: too many concurrent executions. Try again shortly.";
22764
+ async function withExecutionLimit(fn) {
22765
+ if (activeExecutions >= MAX_CONCURRENT_EXECUTIONS) {
22766
+ throw new Error(EXECUTION_LIMIT_ERROR);
22767
+ }
22768
+ activeExecutions++;
22769
+ try {
22770
+ return await fn();
22771
+ } finally {
22772
+ activeExecutions--;
22773
+ }
22774
+ }
22737
22775
  function applyIntentFilter(output, intent, sourceLabel) {
22738
22776
  if (Buffer.byteLength(output) <= config3.intentSearchThreshold) return output;
22739
22777
  const indexed = store.index(output, sourceLabel);
@@ -22758,6 +22796,10 @@ Searchable terms: ${terms.join(", ")}
22758
22796
  return compactLabel(filtered, config3.compressionLevel);
22759
22797
  }
22760
22798
  const shutdown = () => {
22799
+ try {
22800
+ executor.shutdown();
22801
+ } catch {
22802
+ }
22761
22803
  try {
22762
22804
  store.close();
22763
22805
  } catch {
@@ -22766,6 +22808,16 @@ Searchable terms: ${terms.join(", ")}
22766
22808
  process.on("SIGINT", shutdown);
22767
22809
  process.on("SIGTERM", shutdown);
22768
22810
  process.on("beforeExit", shutdown);
22811
+ process.on("uncaughtException", (err) => {
22812
+ debug("Uncaught exception:", err);
22813
+ shutdown();
22814
+ process.exit(1);
22815
+ });
22816
+ process.on("unhandledRejection", (err) => {
22817
+ debug("Unhandled rejection:", err);
22818
+ shutdown();
22819
+ process.exit(1);
22820
+ });
22769
22821
  const searchCalls = [];
22770
22822
  const server2 = new McpServer({
22771
22823
  name: "context-compress",
@@ -22787,7 +22839,24 @@ PREFER THIS OVER BASH for: API calls (gh, curl, aws), test runners (npm test, py
22787
22839
  timeout: external_exports.number().default(3e4).describe("Max execution time in ms")
22788
22840
  },
22789
22841
  async ({ language, code, intent, timeout }) => {
22790
- const result = await executor.execute({ language, code, timeout });
22842
+ const codeBytes = Buffer.byteLength(code);
22843
+ if (codeBytes > 1024e3) {
22844
+ return {
22845
+ content: [
22846
+ {
22847
+ type: "text",
22848
+ text: `Error: code too large (${(codeBytes / 1024).toFixed(0)}KB). Max 1MB.`
22849
+ }
22850
+ ]
22851
+ };
22852
+ }
22853
+ let result;
22854
+ try {
22855
+ result = await withExecutionLimit(() => executor.execute({ language, code, timeout }));
22856
+ } catch (e) {
22857
+ const msg = e instanceof Error ? e.message : String(e);
22858
+ return { content: [{ type: "text", text: msg }] };
22859
+ }
22791
22860
  if (result.networkBytes) {
22792
22861
  tracker.trackSandboxed(result.networkBytes);
22793
22862
  }
@@ -22819,6 +22888,17 @@ ${result.stderr}`;
22819
22888
  timeout: external_exports.number().default(3e4).describe("Max execution time in ms")
22820
22889
  },
22821
22890
  async ({ path: filePath, language, code, intent, timeout }) => {
22891
+ const codeBytes = Buffer.byteLength(code);
22892
+ if (codeBytes > 1024e3) {
22893
+ return {
22894
+ content: [
22895
+ {
22896
+ type: "text",
22897
+ text: `Error: code too large (${(codeBytes / 1024).toFixed(0)}KB). Max 1MB.`
22898
+ }
22899
+ ]
22900
+ };
22901
+ }
22822
22902
  const absPath = resolve(projectDir, filePath);
22823
22903
  if (!isWithinProject(absPath)) {
22824
22904
  return {
@@ -22830,12 +22910,20 @@ ${result.stderr}`;
22830
22910
  ]
22831
22911
  };
22832
22912
  }
22833
- const result = await executor.executeFile({
22834
- language,
22835
- code,
22836
- filePath: absPath,
22837
- timeout
22838
- });
22913
+ let result;
22914
+ try {
22915
+ result = await withExecutionLimit(
22916
+ () => executor.executeFile({
22917
+ language,
22918
+ code,
22919
+ filePath: absPath,
22920
+ timeout
22921
+ })
22922
+ );
22923
+ } catch (e) {
22924
+ const msg = e instanceof Error ? e.message : String(e);
22925
+ return { content: [{ type: "text", text: msg }] };
22926
+ }
22839
22927
  let output = result.stdout;
22840
22928
  if (result.stderr && result.exitCode !== 0) {
22841
22929
  output += `
@@ -23020,11 +23108,19 @@ ${hit.snippet}
23020
23108
  }
23021
23109
  const label = source ?? url;
23022
23110
  const fetchCode = buildFetchCode(url, resolvedIp);
23023
- const result = await executor.execute({
23024
- language: "javascript",
23025
- code: fetchCode,
23026
- timeout: 3e4
23027
- });
23111
+ let result;
23112
+ try {
23113
+ result = await withExecutionLimit(
23114
+ () => executor.execute({
23115
+ language: "javascript",
23116
+ code: fetchCode,
23117
+ timeout: 3e4
23118
+ })
23119
+ );
23120
+ } catch (e) {
23121
+ const msg = e instanceof Error ? e.message : String(e);
23122
+ return { content: [{ type: "text", text: msg }] };
23123
+ }
23028
23124
  if (result.exitCode !== 0 || !result.stdout.trim()) {
23029
23125
  const errMsg = `Failed to fetch ${url}: ${result.stderr || "empty response"}`;
23030
23126
  tracker.trackCall("fetch_and_index", Buffer.byteLength(errMsg));
@@ -23076,11 +23172,13 @@ Searchable terms: ${terms.join(", ")}`;
23076
23172
  async ({ commands, queries, timeout }) => {
23077
23173
  const commandResults = await limitConcurrency(
23078
23174
  commands.map((cmd) => async () => {
23079
- const result = await executor.execute({
23080
- language: "shell",
23081
- code: cmd.command,
23082
- timeout
23083
- });
23175
+ const result = await withExecutionLimit(
23176
+ () => executor.execute({
23177
+ language: "shell",
23178
+ code: cmd.command,
23179
+ timeout
23180
+ })
23181
+ );
23084
23182
  return { label: cmd.label, result };
23085
23183
  }),
23086
23184
  4
@@ -23262,7 +23360,14 @@ const resp = await fetch(url, { redirect: 'error' });`;
23262
23360
  }
23263
23361
  return `${fetchSetup}
23264
23362
  if (!resp.ok) { console.error("HTTP " + resp.status); process.exit(1); }
23363
+ const cl = resp.headers.get('content-length');
23364
+ if (cl && parseInt(cl, 10) > 10 * 1024 * 1024) {
23365
+ console.error("Response too large: " + cl + " bytes"); process.exit(1);
23366
+ }
23265
23367
  const html = await resp.text();
23368
+ if (html.length > 10 * 1024 * 1024) {
23369
+ console.error("Response body too large: " + html.length + " chars"); process.exit(1);
23370
+ }
23266
23371
 
23267
23372
  // Strip unwanted tags
23268
23373
  let md = html