@solongate/proxy 0.59.3 → 0.59.5

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.
@@ -3164,7 +3164,6 @@ function exportHTML({ data, results }) {
3164
3164
  return filePath;
3165
3165
  }
3166
3166
  function exportPDF(payload) {
3167
- const { data, results } = payload;
3168
3167
  const htmlPath = exportHTML(payload);
3169
3168
  const pdfHtml = `<!DOCTYPE html>
3170
3169
  <html><head>
package/dist/index.js CHANGED
@@ -41,8 +41,8 @@ function loginCredential() {
41
41
  try {
42
42
  const p = join(homedir(), ".solongate", "cloud-guard.json");
43
43
  if (!existsSync(p)) return {};
44
- const c3 = JSON.parse(readFileSync(p, "utf-8"));
45
- return c3 && typeof c3 === "object" ? c3 : {};
44
+ const c2 = JSON.parse(readFileSync(p, "utf-8"));
45
+ return c2 && typeof c2 === "object" ? c2 : {};
46
46
  } catch {
47
47
  return {};
48
48
  }
@@ -78,6 +78,7 @@ async function fetchCloudPolicy(apiKey, apiUrl, policyId) {
78
78
  return {
79
79
  id: String(data.id ?? "cloud"),
80
80
  name: String(data.name ?? "Cloud Policy"),
81
+ description: String(data.description ?? ""),
81
82
  version: Number(data._version ?? 1),
82
83
  rules: data.rules ?? [],
83
84
  createdAt: String(data._created_at ?? ""),
@@ -197,21 +198,6 @@ function parseArgs(argv) {
197
198
  let separatorIndex = args.indexOf("--");
198
199
  const flags = separatorIndex >= 0 ? args.slice(0, separatorIndex) : args;
199
200
  let upstreamArgs = separatorIndex >= 0 ? args.slice(separatorIndex + 1) : [];
200
- const flagsWithValue = /* @__PURE__ */ new Set([
201
- "--policy",
202
- "--name",
203
- "--rate-limit",
204
- "--global-rate-limit",
205
- "--config",
206
- "--api-key",
207
- "--api-url",
208
- "--upstream-url",
209
- "--upstream-transport",
210
- "--port",
211
- "--policy-id",
212
- "--id",
213
- "--agent-name"
214
- ]);
215
201
  for (let i = 0; i < flags.length; i++) {
216
202
  if (!flags[i].startsWith("--")) {
217
203
  if (upstreamArgs.length === 0) {
@@ -1447,9 +1433,9 @@ var require_parse_cst = __commonJS({
1447
1433
  offset = this.node.range.end;
1448
1434
  } else {
1449
1435
  if (inlineComment) {
1450
- const c3 = comments[0];
1451
- this.props.push(c3);
1452
- offset = c3.end;
1436
+ const c2 = comments[0];
1437
+ this.props.push(c2);
1438
+ offset = c2.end;
1453
1439
  } else {
1454
1440
  offset = PlainValue.Node.endOfLine(src, start + 1);
1455
1441
  }
@@ -3190,8 +3176,8 @@ ${ctx.indent}`;
3190
3176
  } else if (node instanceof Collection) {
3191
3177
  let count = 0;
3192
3178
  for (const item of node.items) {
3193
- const c3 = getAliasCount(item, anchors);
3194
- if (c3 > count) count = c3;
3179
+ const c2 = getAliasCount(item, anchors);
3180
+ if (c2 > count) count = c2;
3195
3181
  }
3196
3182
  return count;
3197
3183
  } else if (node instanceof Pair) {
@@ -6580,14 +6566,14 @@ __export(global_install_exports, {
6580
6566
  runGlobalRestore: () => runGlobalRestore,
6581
6567
  unlockProtected: () => unlockProtected
6582
6568
  });
6583
- import { readFileSync as readFileSync5, writeFileSync as writeFileSync3, existsSync as existsSync4, mkdirSync as mkdirSync4 } from "fs";
6584
- import { resolve as resolve3, join as join4, dirname as dirname2 } from "path";
6569
+ import { readFileSync as readFileSync4, writeFileSync as writeFileSync3, existsSync as existsSync3, mkdirSync as mkdirSync3 } from "fs";
6570
+ import { resolve as resolve3, join as join4, dirname } from "path";
6585
6571
  import { homedir as homedir2 } from "os";
6586
6572
  import { fileURLToPath } from "url";
6587
6573
  import { createInterface } from "readline";
6588
6574
  import { execFileSync as execFileSync2 } from "child_process";
6589
6575
  function lockFile(file) {
6590
- if (!existsSync4(file)) return;
6576
+ if (!existsSync3(file)) return;
6591
6577
  try {
6592
6578
  if (process.platform === "win32") {
6593
6579
  try {
@@ -6613,7 +6599,7 @@ function lockFile(file) {
6613
6599
  }
6614
6600
  }
6615
6601
  function unlockFile(file) {
6616
- if (!existsSync4(file)) return;
6602
+ if (!existsSync3(file)) return;
6617
6603
  try {
6618
6604
  if (process.platform === "win32") {
6619
6605
  try {
@@ -6675,11 +6661,11 @@ function globalPaths() {
6675
6661
  };
6676
6662
  }
6677
6663
  function readHook(filename) {
6678
- return readFileSync5(join4(HOOKS_DIR, filename), "utf-8");
6664
+ return readFileSync4(join4(HOOKS_DIR, filename), "utf-8");
6679
6665
  }
6680
6666
  function readGuard() {
6681
6667
  const bundled = join4(HOOKS_DIR, "guard.bundled.mjs");
6682
- return existsSync4(bundled) ? readFileSync5(bundled, "utf-8") : readHook("guard.mjs");
6668
+ return existsSync3(bundled) ? readFileSync4(bundled, "utf-8") : readHook("guard.mjs");
6683
6669
  }
6684
6670
  function ask(question) {
6685
6671
  const rl = createInterface({ input: process.stdin, output: process.stderr });
@@ -6692,12 +6678,12 @@ function runGlobalRestore() {
6692
6678
  const p = globalPaths();
6693
6679
  unlockProtected();
6694
6680
  removeClaudeShim();
6695
- if (existsSync4(p.backupPath)) {
6696
- writeFileSync3(p.settingsPath, readFileSync5(p.backupPath, "utf-8"));
6681
+ if (existsSync3(p.backupPath)) {
6682
+ writeFileSync3(p.settingsPath, readFileSync4(p.backupPath, "utf-8"));
6697
6683
  console.log(` Restored ${p.settingsPath} from backup.`);
6698
- } else if (existsSync4(p.settingsPath)) {
6684
+ } else if (existsSync3(p.settingsPath)) {
6699
6685
  try {
6700
- const s = JSON.parse(readFileSync5(p.settingsPath, "utf-8"));
6686
+ const s = JSON.parse(readFileSync4(p.settingsPath, "utf-8"));
6701
6687
  delete s.hooks;
6702
6688
  writeFileSync3(p.settingsPath, JSON.stringify(s, null, 2) + "\n");
6703
6689
  console.log(` Removed SolonGate hooks from ${p.settingsPath}.`);
@@ -6733,17 +6719,17 @@ function shimTargets() {
6733
6719
  return [];
6734
6720
  }
6735
6721
  }
6736
- return [".bashrc", ".zshrc", ".profile"].map((f) => join4(homedir2(), f)).filter((f) => existsSync4(f));
6722
+ return [".bashrc", ".zshrc", ".profile"].map((f) => join4(homedir2(), f)).filter((f) => existsSync3(f));
6737
6723
  }
6738
6724
  function writeShimBlock(file, block) {
6739
6725
  const re = new RegExp(escapeRe(SHIM_BEGIN) + "[\\s\\S]*?" + escapeRe(SHIM_END) + "\\r?\\n?", "g");
6740
- let content = existsSync4(file) ? readFileSync5(file, "utf-8") : "";
6726
+ let content = existsSync3(file) ? readFileSync4(file, "utf-8") : "";
6741
6727
  content = content.replace(re, "");
6742
6728
  if (block) {
6743
6729
  if (content.length && !content.endsWith("\n")) content += "\n";
6744
6730
  content += block + "\n";
6745
6731
  }
6746
- mkdirSync4(dirname2(file), { recursive: true });
6732
+ mkdirSync3(dirname(file), { recursive: true });
6747
6733
  writeFileSync3(file, content);
6748
6734
  }
6749
6735
  function installClaudeShim(shieldPath) {
@@ -6788,7 +6774,7 @@ async function runGlobalInstall(opts = {}) {
6788
6774
  let apiKey = opts.apiKey || process.env["SOLONGATE_API_KEY"] || "";
6789
6775
  if (!apiKey || apiKey === "sg_live_your_key_here") {
6790
6776
  try {
6791
- const cfg = JSON.parse(readFileSync5(p.configPath, "utf-8"));
6777
+ const cfg = JSON.parse(readFileSync4(p.configPath, "utf-8"));
6792
6778
  if (cfg && typeof cfg.apiKey === "string") apiKey = cfg.apiKey;
6793
6779
  } catch {
6794
6780
  }
@@ -6801,8 +6787,8 @@ async function runGlobalInstall(opts = {}) {
6801
6787
  process.exit(1);
6802
6788
  }
6803
6789
  const apiUrl = opts.apiUrl || process.env["SOLONGATE_API_URL"] || "https://api.solongate.com";
6804
- mkdirSync4(p.hooksDir, { recursive: true });
6805
- mkdirSync4(p.claudeDir, { recursive: true });
6790
+ mkdirSync3(p.hooksDir, { recursive: true });
6791
+ mkdirSync3(p.claudeDir, { recursive: true });
6806
6792
  unlockProtected();
6807
6793
  writeFileSync3(join4(p.hooksDir, "guard.mjs"), readGuard());
6808
6794
  writeFileSync3(join4(p.hooksDir, "audit.mjs"), readHook("audit.mjs"));
@@ -6813,9 +6799,9 @@ async function runGlobalInstall(opts = {}) {
6813
6799
  writeFileSync3(p.configPath, JSON.stringify({ apiKey, apiUrl }, null, 2) + "\n");
6814
6800
  console.log(` Wrote ${p.configPath}`);
6815
6801
  let existing = {};
6816
- if (existsSync4(p.settingsPath)) {
6817
- const raw = readFileSync5(p.settingsPath, "utf-8");
6818
- if (!existsSync4(p.backupPath)) {
6802
+ if (existsSync3(p.settingsPath)) {
6803
+ const raw = readFileSync4(p.settingsPath, "utf-8");
6804
+ if (!existsSync3(p.backupPath)) {
6819
6805
  writeFileSync3(p.backupPath, raw);
6820
6806
  console.log(` Backed up existing settings \u2192 ${p.backupPath}`);
6821
6807
  }
@@ -6851,7 +6837,7 @@ var __dirname, HOOKS_DIR, SHIM_BEGIN, SHIM_END;
6851
6837
  var init_global_install = __esm({
6852
6838
  "src/global-install.ts"() {
6853
6839
  "use strict";
6854
- __dirname = dirname2(fileURLToPath(import.meta.url));
6840
+ __dirname = dirname(fileURLToPath(import.meta.url));
6855
6841
  HOOKS_DIR = resolve3(__dirname, "..", "hooks");
6856
6842
  SHIM_BEGIN = "# >>> SolonGate shield (auto secret redaction) >>>";
6857
6843
  SHIM_END = "# <<< SolonGate shield <<<";
@@ -7017,7 +7003,7 @@ import { createServer, request as httpRequest } from "http";
7017
7003
  import { request as httpsRequest } from "https";
7018
7004
  import { spawn as spawn2 } from "child_process";
7019
7005
  import { URL as URL2 } from "url";
7020
- import { readFileSync as readFileSync6, existsSync as existsSync5, readdirSync, statSync } from "fs";
7006
+ import { readFileSync as readFileSync5, existsSync as existsSync4, readdirSync, statSync } from "fs";
7021
7007
  import { resolve as resolve4 } from "path";
7022
7008
  import { homedir as homedir3 } from "os";
7023
7009
  function findCacheFile() {
@@ -7025,7 +7011,7 @@ function findCacheFile() {
7025
7011
  const envSel = process.env.SOLONGATE_AGENT_ID;
7026
7012
  if (envSel) {
7027
7013
  const f = resolve4(dir, ".policy-cache-" + envSel.replace(/[^a-zA-Z0-9_-]/g, "_") + ".json");
7028
- if (existsSync5(f)) return f;
7014
+ if (existsSync4(f)) return f;
7029
7015
  }
7030
7016
  let best = null, bestTs = -1;
7031
7017
  try {
@@ -7046,10 +7032,10 @@ function findCacheFile() {
7046
7032
  function loadCfg() {
7047
7033
  try {
7048
7034
  const f = findCacheFile();
7049
- if (f && existsSync5(f)) {
7050
- const c3 = JSON.parse(readFileSync6(f, "utf-8"));
7051
- const d = c3?.security?.dlpRedact;
7052
- const g = c3?.security?.ghost;
7035
+ if (f && existsSync4(f)) {
7036
+ const c2 = JSON.parse(readFileSync5(f, "utf-8"));
7037
+ const d = c2?.security?.dlpRedact;
7038
+ const g = c2?.security?.ghost;
7053
7039
  const ghost = g && Array.isArray(g.patterns) ? g.patterns : [];
7054
7040
  if (d && Array.isArray(d.patterns)) return { patterns: d.patterns, custom: Array.isArray(d.custom) ? d.custom : [], ghost };
7055
7041
  return { patterns: DLP_PATTERNS.map((p) => p.name), custom: [], ghost };
@@ -7061,15 +7047,15 @@ function loadCfg() {
7061
7047
  function ghostGlobToRegExp(glob) {
7062
7048
  let re = "";
7063
7049
  for (let i = 0; i < glob.length; i++) {
7064
- const c3 = glob[i];
7065
- if (c3 === "*") {
7050
+ const c2 = glob[i];
7051
+ if (c2 === "*") {
7066
7052
  if (glob[i + 1] === "*") {
7067
7053
  re += ".*";
7068
7054
  i++;
7069
7055
  } else re += "[^/]*";
7070
- } else if (c3 === "?") re += "[^/]";
7071
- else if ("\\^$.|+()[]{}".indexOf(c3) !== -1) re += "\\" + c3;
7072
- else re += c3;
7056
+ } else if (c2 === "?") re += "[^/]";
7057
+ else if ("\\^$.|+()[]{}".indexOf(c2) !== -1) re += "\\" + c2;
7058
+ else re += c2;
7073
7059
  }
7074
7060
  try {
7075
7061
  return new RegExp("^" + re + "$");
@@ -7162,9 +7148,9 @@ function redactString(s, cfg) {
7162
7148
  const allow = new Set(cfg.patterns);
7163
7149
  let out = s;
7164
7150
  for (const p of DLP_PATTERNS) if (allow.has(p.name)) out = out.replace(p.re, `[REDACTED: ${p.name}]`);
7165
- for (const c3 of cfg.custom) {
7151
+ for (const c2 of cfg.custom) {
7166
7152
  try {
7167
- out = out.replace(dlpGlobToRe(c3.re, "g"), `[REDACTED: ${c3.name || "custom"}]`);
7153
+ out = out.replace(dlpGlobToRe(c2.re, "g"), `[REDACTED: ${c2.name || "custom"}]`);
7168
7154
  } catch {
7169
7155
  }
7170
7156
  }
@@ -7201,7 +7187,7 @@ function startProxy(upstream) {
7201
7187
  const forward = upstream.protocol === "https:" ? httpsRequest : httpRequest;
7202
7188
  const server = createServer((req, res) => {
7203
7189
  const chunks = [];
7204
- req.on("data", (c3) => chunks.push(c3));
7190
+ req.on("data", (c2) => chunks.push(c2));
7205
7191
  req.on("end", () => {
7206
7192
  let body = Buffer.concat(chunks);
7207
7193
  try {
@@ -7323,7 +7309,7 @@ __export(logs_server_exports, {
7323
7309
  runLogsServer: () => runLogsServer
7324
7310
  });
7325
7311
  import { createServer as createServer2 } from "http";
7326
- import { readFileSync as readFileSync7, statSync as statSync2 } from "fs";
7312
+ import { readFileSync as readFileSync6, statSync as statSync2 } from "fs";
7327
7313
  import { resolve as resolve5, join as join5, isAbsolute } from "path";
7328
7314
  import { homedir as homedir4 } from "os";
7329
7315
  import { readdirSync as readdirSync2 } from "fs";
@@ -7350,8 +7336,8 @@ async function findLogDir() {
7350
7336
  const files = readdirSync2(base).filter((f) => f.startsWith(".policy-cache-") && f.endsWith(".json"));
7351
7337
  for (const f of files) {
7352
7338
  try {
7353
- const c3 = JSON.parse(readFileSync7(join5(base, f), "utf-8"));
7354
- const p = c3?.security?.localLogs?.path;
7339
+ const c2 = JSON.parse(readFileSync6(join5(base, f), "utf-8"));
7340
+ const p = c2?.security?.localLogs?.path;
7355
7341
  if (typeof p === "string" && p.trim()) return { dir: resolveLocalLogDir(p), configured: p };
7356
7342
  } catch {
7357
7343
  }
@@ -7359,7 +7345,7 @@ async function findLogDir() {
7359
7345
  } catch {
7360
7346
  }
7361
7347
  try {
7362
- const cfgRaw = readFileSync7(join5(base, "cloud-guard.json"), "utf-8");
7348
+ const cfgRaw = readFileSync6(join5(base, "cloud-guard.json"), "utf-8");
7363
7349
  const { apiKey, apiUrl } = JSON.parse(cfgRaw);
7364
7350
  if (apiKey) {
7365
7351
  const url = `${apiUrl || "https://api.solongate.com"}/api/v1/policies/active`;
@@ -7449,7 +7435,7 @@ async function runLogsServer() {
7449
7435
  return;
7450
7436
  }
7451
7437
  try {
7452
- const text = readFileSync7(info.file, "utf-8");
7438
+ const text = readFileSync6(info.file, "utf-8");
7453
7439
  res.writeHead(200, { "Content-Type": "text/plain; charset=utf-8", "Last-Modified": lastMod, "X-Solongate-Exists": "1" });
7454
7440
  res.end(text);
7455
7441
  } catch {
@@ -7501,7 +7487,7 @@ var init_logs_server = __esm({
7501
7487
 
7502
7488
  // src/inject.ts
7503
7489
  var inject_exports = {};
7504
- import { readFileSync as readFileSync8, writeFileSync as writeFileSync4, existsSync as existsSync7, copyFileSync } from "fs";
7490
+ import { readFileSync as readFileSync7, writeFileSync as writeFileSync4, existsSync as existsSync5, copyFileSync } from "fs";
7505
7491
  import { resolve as resolve6 } from "path";
7506
7492
  import { execSync } from "child_process";
7507
7493
  function parseInjectArgs(argv) {
@@ -7559,9 +7545,9 @@ WHAT IT DOES
7559
7545
  `);
7560
7546
  }
7561
7547
  function detectProject() {
7562
- if (!existsSync7(resolve6("package.json"))) return false;
7548
+ if (!existsSync5(resolve6("package.json"))) return false;
7563
7549
  try {
7564
- const pkg = JSON.parse(readFileSync8(resolve6("package.json"), "utf-8"));
7550
+ const pkg = JSON.parse(readFileSync7(resolve6("package.json"), "utf-8"));
7565
7551
  const allDeps = { ...pkg.dependencies, ...pkg.devDependencies };
7566
7552
  return !!(allDeps["@modelcontextprotocol/sdk"] || allDeps["@modelcontextprotocol/server"]);
7567
7553
  } catch {
@@ -7570,18 +7556,18 @@ function detectProject() {
7570
7556
  }
7571
7557
  function findTsEntryFile() {
7572
7558
  try {
7573
- const pkg = JSON.parse(readFileSync8(resolve6("package.json"), "utf-8"));
7559
+ const pkg = JSON.parse(readFileSync7(resolve6("package.json"), "utf-8"));
7574
7560
  if (pkg.bin) {
7575
7561
  const binPath = typeof pkg.bin === "string" ? pkg.bin : Object.values(pkg.bin)[0];
7576
7562
  if (typeof binPath === "string") {
7577
7563
  const srcPath = binPath.replace(/^\.\/dist\//, "./src/").replace(/\.js$/, ".ts");
7578
- if (existsSync7(resolve6(srcPath))) return resolve6(srcPath);
7579
- if (existsSync7(resolve6(binPath))) return resolve6(binPath);
7564
+ if (existsSync5(resolve6(srcPath))) return resolve6(srcPath);
7565
+ if (existsSync5(resolve6(binPath))) return resolve6(binPath);
7580
7566
  }
7581
7567
  }
7582
7568
  if (pkg.main) {
7583
7569
  const srcPath = pkg.main.replace(/^\.\/dist\//, "./src/").replace(/\.js$/, ".ts");
7584
- if (existsSync7(resolve6(srcPath))) return resolve6(srcPath);
7570
+ if (existsSync5(resolve6(srcPath))) return resolve6(srcPath);
7585
7571
  }
7586
7572
  } catch {
7587
7573
  }
@@ -7593,11 +7579,11 @@ function findTsEntryFile() {
7593
7579
  "server.ts",
7594
7580
  "main.ts"
7595
7581
  ];
7596
- for (const c3 of candidates) {
7597
- const full = resolve6(c3);
7598
- if (existsSync7(full)) {
7582
+ for (const c2 of candidates) {
7583
+ const full = resolve6(c2);
7584
+ if (existsSync5(full)) {
7599
7585
  try {
7600
- const content = readFileSync8(full, "utf-8");
7586
+ const content = readFileSync7(full, "utf-8");
7601
7587
  if (content.includes("McpServer") || content.includes("McpServer")) {
7602
7588
  return full;
7603
7589
  }
@@ -7605,19 +7591,19 @@ function findTsEntryFile() {
7605
7591
  }
7606
7592
  }
7607
7593
  }
7608
- for (const c3 of candidates) {
7609
- if (existsSync7(resolve6(c3))) return resolve6(c3);
7594
+ for (const c2 of candidates) {
7595
+ if (existsSync5(resolve6(c2))) return resolve6(c2);
7610
7596
  }
7611
7597
  return null;
7612
7598
  }
7613
7599
  function detectPackageManager() {
7614
- if (existsSync7(resolve6("pnpm-lock.yaml"))) return "pnpm";
7615
- if (existsSync7(resolve6("yarn.lock"))) return "yarn";
7600
+ if (existsSync5(resolve6("pnpm-lock.yaml"))) return "pnpm";
7601
+ if (existsSync5(resolve6("yarn.lock"))) return "yarn";
7616
7602
  return "npm";
7617
7603
  }
7618
7604
  function installSdk() {
7619
7605
  try {
7620
- const pkg = JSON.parse(readFileSync8(resolve6("package.json"), "utf-8"));
7606
+ const pkg = JSON.parse(readFileSync7(resolve6("package.json"), "utf-8"));
7621
7607
  const allDeps = { ...pkg.dependencies, ...pkg.devDependencies };
7622
7608
  if (allDeps["@solongate/proxy"]) {
7623
7609
  log3(" @solongate/proxy already installed");
@@ -7638,7 +7624,7 @@ function installSdk() {
7638
7624
  }
7639
7625
  }
7640
7626
  function injectTypeScript(filePath) {
7641
- const original = readFileSync8(filePath, "utf-8");
7627
+ const original = readFileSync7(filePath, "utf-8");
7642
7628
  const changes = [];
7643
7629
  let modified = original;
7644
7630
  if (modified.includes("SecureMcpServer")) {
@@ -7755,7 +7741,7 @@ async function main2() {
7755
7741
  }
7756
7742
  log3(" Language: TypeScript");
7757
7743
  const entryFile = opts.file ? resolve6(opts.file) : findTsEntryFile();
7758
- if (!entryFile || !existsSync7(entryFile)) {
7744
+ if (!entryFile || !existsSync5(entryFile)) {
7759
7745
  log3(` Could not find entry file.${opts.file ? ` File not found: ${opts.file}` : ""}`);
7760
7746
  log3("");
7761
7747
  log3(" Specify it manually: --file <path>");
@@ -7768,7 +7754,7 @@ async function main2() {
7768
7754
  log3("");
7769
7755
  const backupPath = entryFile + ".solongate-backup";
7770
7756
  if (opts.restore) {
7771
- if (!existsSync7(backupPath)) {
7757
+ if (!existsSync5(backupPath)) {
7772
7758
  log3(" No backup found. Nothing to restore.");
7773
7759
  process.exit(1);
7774
7760
  }
@@ -7804,7 +7790,7 @@ async function main2() {
7804
7790
  log3(" To apply: npx @solongate/proxy inject");
7805
7791
  process.exit(0);
7806
7792
  }
7807
- if (!existsSync7(backupPath)) {
7793
+ if (!existsSync5(backupPath)) {
7808
7794
  copyFileSync(entryFile, backupPath);
7809
7795
  log3("");
7810
7796
  log3(` Backup: ${backupPath}`);
@@ -7838,7 +7824,7 @@ var init_inject = __esm({
7838
7824
 
7839
7825
  // src/create.ts
7840
7826
  var create_exports = {};
7841
- import { mkdirSync as mkdirSync5, writeFileSync as writeFileSync5, existsSync as existsSync8 } from "fs";
7827
+ import { mkdirSync as mkdirSync4, writeFileSync as writeFileSync5, existsSync as existsSync6 } from "fs";
7842
7828
  import { resolve as resolve7, join as join6 } from "path";
7843
7829
  import { execSync as execSync2 } from "child_process";
7844
7830
  function withSpinner(message, fn) {
@@ -7969,7 +7955,7 @@ function createProject(dir, name, _policy) {
7969
7955
  2
7970
7956
  ) + "\n"
7971
7957
  );
7972
- mkdirSync5(join6(dir, "src"), { recursive: true });
7958
+ mkdirSync4(join6(dir, "src"), { recursive: true });
7973
7959
  writeFileSync5(
7974
7960
  join6(dir, "src", "index.ts"),
7975
7961
  `#!/usr/bin/env node
@@ -8050,12 +8036,12 @@ async function main3() {
8050
8036
  const opts = parseCreateArgs(process.argv);
8051
8037
  const dir = resolve7(opts.name);
8052
8038
  printBanner("Create MCP Server");
8053
- if (existsSync8(dir)) {
8039
+ if (existsSync6(dir)) {
8054
8040
  log3(` ${c.red}Error:${c.reset} Directory "${opts.name}" already exists.`);
8055
8041
  process.exit(1);
8056
8042
  }
8057
8043
  withSpinner(`Setting up ${opts.name}...`, () => {
8058
- mkdirSync5(dir, { recursive: true });
8044
+ mkdirSync4(dir, { recursive: true });
8059
8045
  createProject(dir, opts.name, opts.policy);
8060
8046
  });
8061
8047
  if (!opts.noInstall) {
@@ -8129,14 +8115,14 @@ var init_create = __esm({
8129
8115
 
8130
8116
  // src/pull-push.ts
8131
8117
  var pull_push_exports = {};
8132
- import { readFileSync as readFileSync9, writeFileSync as writeFileSync6, existsSync as existsSync9 } from "fs";
8118
+ import { readFileSync as readFileSync8, writeFileSync as writeFileSync6, existsSync as existsSync7 } from "fs";
8133
8119
  import { resolve as resolve8 } from "path";
8134
8120
  function loadEnv() {
8135
8121
  if (process.env.SOLONGATE_API_KEY) return;
8136
8122
  const envPath = resolve8(".env");
8137
- if (!existsSync9(envPath)) return;
8123
+ if (!existsSync7(envPath)) return;
8138
8124
  try {
8139
- const content = readFileSync9(envPath, "utf-8");
8125
+ const content = readFileSync8(envPath, "utf-8");
8140
8126
  for (const line of content.split("\n")) {
8141
8127
  const trimmed = line.trim();
8142
8128
  if (!trimmed || trimmed.startsWith("#")) continue;
@@ -8233,7 +8219,7 @@ async function list(apiKey, policyId) {
8233
8219
  )
8234
8220
  );
8235
8221
  for (const { policy, rules } of fullPolicies) {
8236
- printPolicySummary(policy, rules);
8222
+ printPolicySummary(policy, [...rules]);
8237
8223
  }
8238
8224
  log5(dim(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
8239
8225
  log5("");
@@ -8336,7 +8322,7 @@ async function pull(apiKey, file, policyId) {
8336
8322
  log5("");
8337
8323
  }
8338
8324
  async function push(apiKey, file, policyId) {
8339
- if (!existsSync9(file)) {
8325
+ if (!existsSync7(file)) {
8340
8326
  log5(red(`ERROR: File not found: ${file}`));
8341
8327
  process.exit(1);
8342
8328
  }
@@ -8353,7 +8339,7 @@ async function push(apiKey, file, policyId) {
8353
8339
  log5(" solongate-proxy list");
8354
8340
  process.exit(1);
8355
8341
  }
8356
- const content = readFileSync9(file, "utf-8");
8342
+ const content = readFileSync8(file, "utf-8");
8357
8343
  let policy;
8358
8344
  try {
8359
8345
  policy = JSON.parse(content);
@@ -8467,7 +8453,7 @@ import {
8467
8453
  } from "@modelcontextprotocol/sdk/types.js";
8468
8454
  import { createServer as createHttpServer } from "http";
8469
8455
  import { resolve as resolve2, join as join3 } from "path";
8470
- import { mkdirSync as mkdirSync3, appendFileSync } from "fs";
8456
+ import { mkdirSync as mkdirSync2, appendFileSync } from "fs";
8471
8457
 
8472
8458
  // src/core/errors.ts
8473
8459
  var SolonGateError = class extends Error {
@@ -9677,7 +9663,7 @@ var PolicyStore = class {
9677
9663
 
9678
9664
  // src/policy-engine/opa/rego-compiler.ts
9679
9665
  import { execFileSync } from "child_process";
9680
- import { writeFileSync, readFileSync as readFileSync2, mkdirSync as mkdirSync2, rmSync } from "fs";
9666
+ import { writeFileSync, readFileSync as readFileSync2, mkdirSync, rmSync } from "fs";
9681
9667
  import { join as join2 } from "path";
9682
9668
  import { tmpdir } from "os";
9683
9669
  import { randomUUID } from "crypto";
@@ -10914,8 +10900,6 @@ var SolonGateProxy = class {
10914
10900
  /** Agent identity for trust map — resolved from CLI flag, HTTP headers, or MCP clientInfo */
10915
10901
  agentId = null;
10916
10902
  agentName = null;
10917
- /** Per-session agent info for HTTP mode (keyed by session ID) */
10918
- httpAgentInfo = /* @__PURE__ */ new Map();
10919
10903
  /** Per-request sub-agent info from HTTP headers (transient, overwritten per request) */
10920
10904
  httpSubAgent = null;
10921
10905
  constructor(config) {
@@ -11121,7 +11105,7 @@ var SolonGateProxy = class {
11121
11105
  pid: process.pid
11122
11106
  };
11123
11107
  const debugDir = resolve2(".solongate");
11124
- mkdirSync3(debugDir, { recursive: true });
11108
+ mkdirSync2(debugDir, { recursive: true });
11125
11109
  appendFileSync(join3(debugDir, ".debug-proxy"), JSON.stringify(debugInfo) + "\n");
11126
11110
  } catch {
11127
11111
  }
@@ -11397,33 +11381,6 @@ ${msg.content.text}`;
11397
11381
  /**
11398
11382
  * Extract protected filenames from policy DENY rules (filenameConstraints.denied).
11399
11383
  */
11400
- extractProtectedFiles() {
11401
- const files = /* @__PURE__ */ new Set();
11402
- for (const rule of this.config.policy.rules) {
11403
- if (rule.effect === "DENY" && rule.enabled !== false) {
11404
- const denied = rule.filenameConstraints?.denied;
11405
- if (denied) {
11406
- for (const f of denied) files.add(f);
11407
- }
11408
- }
11409
- }
11410
- return [...files];
11411
- }
11412
- /**
11413
- * Extract protected paths from policy DENY rules (pathConstraints.denied).
11414
- */
11415
- extractProtectedPaths() {
11416
- const paths = /* @__PURE__ */ new Set();
11417
- for (const rule of this.config.policy.rules) {
11418
- if (rule.effect === "DENY" && rule.enabled !== false) {
11419
- const denied = rule.pathConstraints?.denied;
11420
- if (denied) {
11421
- for (const p of denied) paths.add(p);
11422
- }
11423
- }
11424
- }
11425
- return [...paths];
11426
- }
11427
11384
  startPolicySync() {
11428
11385
  const apiKey = this.config.apiKey;
11429
11386
  if (!apiKey) return;
package/dist/inject.js CHANGED
@@ -137,8 +137,8 @@ function findTsEntryFile() {
137
137
  "server.ts",
138
138
  "main.ts"
139
139
  ];
140
- for (const c3 of candidates) {
141
- const full = resolve(c3);
140
+ for (const c2 of candidates) {
141
+ const full = resolve(c2);
142
142
  if (existsSync(full)) {
143
143
  try {
144
144
  const content = readFileSync(full, "utf-8");
@@ -149,8 +149,8 @@ function findTsEntryFile() {
149
149
  }
150
150
  }
151
151
  }
152
- for (const c3 of candidates) {
153
- if (existsSync(resolve(c3))) return resolve(c3);
152
+ for (const c2 of candidates) {
153
+ if (existsSync(resolve(c2))) return resolve(c2);
154
154
  }
155
155
  return null;
156
156
  }
package/dist/lib.js CHANGED
@@ -8991,7 +8991,7 @@ import {
8991
8991
  } from "@modelcontextprotocol/sdk/types.js";
8992
8992
  import { createServer as createHttpServer } from "http";
8993
8993
  import { resolve as resolve2, join as join3 } from "path";
8994
- import { mkdirSync as mkdirSync3, appendFileSync } from "fs";
8994
+ import { mkdirSync as mkdirSync2, appendFileSync } from "fs";
8995
8995
 
8996
8996
  // src/config.ts
8997
8997
  import { readFileSync as readFileSync2, existsSync } from "fs";
@@ -9030,6 +9030,7 @@ async function fetchCloudPolicy(apiKey, apiUrl, policyId) {
9030
9030
  return {
9031
9031
  id: String(data.id ?? "cloud"),
9032
9032
  name: String(data.name ?? "Cloud Policy"),
9033
+ description: String(data.description ?? ""),
9033
9034
  version: Number(data._version ?? 1),
9034
9035
  rules: data.rules ?? [],
9035
9036
  createdAt: String(data._created_at ?? ""),
@@ -9364,8 +9365,6 @@ var SolonGateProxy = class {
9364
9365
  /** Agent identity for trust map — resolved from CLI flag, HTTP headers, or MCP clientInfo */
9365
9366
  agentId = null;
9366
9367
  agentName = null;
9367
- /** Per-session agent info for HTTP mode (keyed by session ID) */
9368
- httpAgentInfo = /* @__PURE__ */ new Map();
9369
9368
  /** Per-request sub-agent info from HTTP headers (transient, overwritten per request) */
9370
9369
  httpSubAgent = null;
9371
9370
  constructor(config) {
@@ -9571,7 +9570,7 @@ var SolonGateProxy = class {
9571
9570
  pid: process.pid
9572
9571
  };
9573
9572
  const debugDir = resolve2(".solongate");
9574
- mkdirSync3(debugDir, { recursive: true });
9573
+ mkdirSync2(debugDir, { recursive: true });
9575
9574
  appendFileSync(join3(debugDir, ".debug-proxy"), JSON.stringify(debugInfo) + "\n");
9576
9575
  } catch {
9577
9576
  }
@@ -9847,33 +9846,6 @@ ${msg.content.text}`;
9847
9846
  /**
9848
9847
  * Extract protected filenames from policy DENY rules (filenameConstraints.denied).
9849
9848
  */
9850
- extractProtectedFiles() {
9851
- const files = /* @__PURE__ */ new Set();
9852
- for (const rule of this.config.policy.rules) {
9853
- if (rule.effect === "DENY" && rule.enabled !== false) {
9854
- const denied = rule.filenameConstraints?.denied;
9855
- if (denied) {
9856
- for (const f of denied) files.add(f);
9857
- }
9858
- }
9859
- }
9860
- return [...files];
9861
- }
9862
- /**
9863
- * Extract protected paths from policy DENY rules (pathConstraints.denied).
9864
- */
9865
- extractProtectedPaths() {
9866
- const paths = /* @__PURE__ */ new Set();
9867
- for (const rule of this.config.policy.rules) {
9868
- if (rule.effect === "DENY" && rule.enabled !== false) {
9869
- const denied = rule.pathConstraints?.denied;
9870
- if (denied) {
9871
- for (const p of denied) paths.add(p);
9872
- }
9873
- }
9874
- }
9875
- return [...paths];
9876
- }
9877
9849
  startPolicySync() {
9878
9850
  const apiKey = this.config.apiKey;
9879
9851
  if (!apiKey) return;
package/dist/proxy.d.ts CHANGED
@@ -24,8 +24,6 @@ export declare class SolonGateProxy {
24
24
  /** Agent identity for trust map — resolved from CLI flag, HTTP headers, or MCP clientInfo */
25
25
  private agentId;
26
26
  private agentName;
27
- /** Per-session agent info for HTTP mode (keyed by session ID) */
28
- private httpAgentInfo;
29
27
  /** Per-request sub-agent info from HTTP headers (transient, overwritten per request) */
30
28
  private httpSubAgent;
31
29
  constructor(config: ProxyConfig);
@@ -74,11 +72,6 @@ export declare class SolonGateProxy {
74
72
  /**
75
73
  * Extract protected filenames from policy DENY rules (filenameConstraints.denied).
76
74
  */
77
- private extractProtectedFiles;
78
- /**
79
- * Extract protected paths from policy DENY rules (pathConstraints.denied).
80
- */
81
- private extractProtectedPaths;
82
75
  private startPolicySync;
83
76
  /**
84
77
  * Start serving downstream.
package/dist/pull-push.js CHANGED
@@ -40,6 +40,7 @@ async function fetchCloudPolicy(apiKey, apiUrl, policyId) {
40
40
  return {
41
41
  id: String(data.id ?? "cloud"),
42
42
  name: String(data.name ?? "Cloud Policy"),
43
+ description: String(data.description ?? ""),
43
44
  version: Number(data._version ?? 1),
44
45
  rules: data.rules ?? [],
45
46
  createdAt: String(data._created_at ?? ""),
@@ -161,7 +162,7 @@ async function list(apiKey, policyId) {
161
162
  )
162
163
  );
163
164
  for (const { policy, rules } of fullPolicies) {
164
- printPolicySummary(policy, rules);
165
+ printPolicySummary(policy, [...rules]);
165
166
  }
166
167
  log(dim(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
167
168
  log("");
package/hooks/audit.mjs CHANGED
@@ -11,7 +11,7 @@ import { homedir } from 'node:os';
11
11
  // Bump on every audit.mjs change. The cloud serves the newest version; the guard
12
12
  // hook installs it on its next run (no re-login needed). See guard.mjs
13
13
  // fetchAndInstallHook / maybeSelfUpdate.
14
- const HOOK_VERSION = 14;
14
+ const HOOK_VERSION = 15;
15
15
 
16
16
  function loadEnvKey(dir) {
17
17
  try {
@@ -46,20 +46,39 @@ function loadGlobalCloudConfig() {
46
46
  // instead of a stale-TTL zero. Returns null when no matching measurement exists —
47
47
  // an unknown eval time must be stored as null, not a fake 0 that drags averages.
48
48
  function readLastEvalMs(toolName, sessionId) {
49
+ // Prefer the ring: average the recent same-session eval records. When several
50
+ // tool calls run in PARALLEL they all land here within a moment, so each one
51
+ // reports the burst's arithmetic-mean eval time instead of a blank (the single
52
+ // .last-eval flag would be overwritten / half-read under that concurrency). A
53
+ // lone sequential call has only itself in the window → it reports its own time.
54
+ try {
55
+ const ring = resolve('.solongate', '.eval-ring.jsonl');
56
+ if (existsSync(ring)) {
57
+ const now = Date.now();
58
+ const recent = [];
59
+ for (const line of readFileSync(ring, 'utf-8').split('\n')) {
60
+ if (!line) continue;
61
+ let r; try { r = JSON.parse(line); } catch { continue; }
62
+ if (!r || typeof r.ms !== 'number' || typeof r.ts !== 'number') continue;
63
+ if (now - r.ts > 2500) continue; // recent burst only
64
+ if (r.session && sessionId && r.session !== sessionId) continue;
65
+ recent.push(r.ms);
66
+ }
67
+ if (recent.length) return Math.max(0, Math.round(recent.reduce((a, b) => a + b, 0) / recent.length));
68
+ }
69
+ } catch {}
70
+ // Fallback: the single-value flag (original behavior).
49
71
  try {
50
72
  const p = resolve('.solongate', '.last-eval');
51
73
  if (!existsSync(p)) return null;
52
74
  const c = JSON.parse(readFileSync(p, 'utf-8'));
53
75
  if (!c || typeof c.ms !== 'number' || typeof c.ts !== 'number') return null;
54
76
  if (typeof c.tool === 'string') {
55
- // New flag format: match this invocation (tool + session when both known);
56
- // 2h ceiling only guards against a truly abandoned flag file.
57
77
  if (c.tool !== toolName) return null;
58
78
  if (c.session && sessionId && c.session !== sessionId) return null;
59
79
  if (Date.now() - c.ts > 2 * 3600 * 1000) return null;
60
80
  return Math.max(0, Math.round(c.ms));
61
81
  }
62
- // Legacy flag (no tool info): keep the old conservative 30s freshness rule.
63
82
  if (Date.now() - c.ts < 30000) return Math.max(0, Math.round(c.ms));
64
83
  return null;
65
84
  } catch { return null; }
@@ -34,9 +34,9 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
34
34
  mod
35
35
  ));
36
36
 
37
- // node_modules/.pnpm/@open-policy-agent+opa-wasm@1.10.0/node_modules/@open-policy-agent/opa-wasm/src/builtins/json.js
37
+ // ../../node_modules/.pnpm/@open-policy-agent+opa-wasm@1.10.0/node_modules/@open-policy-agent/opa-wasm/src/builtins/json.js
38
38
  var require_json = __commonJS({
39
- "node_modules/.pnpm/@open-policy-agent+opa-wasm@1.10.0/node_modules/@open-policy-agent/opa-wasm/src/builtins/json.js"(exports, module) {
39
+ "../../node_modules/.pnpm/@open-policy-agent+opa-wasm@1.10.0/node_modules/@open-policy-agent/opa-wasm/src/builtins/json.js"(exports, module) {
40
40
  function isValidJSON(str) {
41
41
  if (typeof str !== "string") {
42
42
  return;
@@ -57,9 +57,9 @@ var require_json = __commonJS({
57
57
  }
58
58
  });
59
59
 
60
- // node_modules/.pnpm/sprintf-js@1.1.3/node_modules/sprintf-js/src/sprintf.js
60
+ // ../../node_modules/.pnpm/sprintf-js@1.1.3/node_modules/sprintf-js/src/sprintf.js
61
61
  var require_sprintf = __commonJS({
62
- "node_modules/.pnpm/sprintf-js@1.1.3/node_modules/sprintf-js/src/sprintf.js"(exports) {
62
+ "../../node_modules/.pnpm/sprintf-js@1.1.3/node_modules/sprintf-js/src/sprintf.js"(exports) {
63
63
  !function() {
64
64
  "use strict";
65
65
  var re = {
@@ -260,26 +260,26 @@ var require_sprintf = __commonJS({
260
260
  }
261
261
  });
262
262
 
263
- // node_modules/.pnpm/@open-policy-agent+opa-wasm@1.10.0/node_modules/@open-policy-agent/opa-wasm/src/builtins/strings.js
263
+ // ../../node_modules/.pnpm/@open-policy-agent+opa-wasm@1.10.0/node_modules/@open-policy-agent/opa-wasm/src/builtins/strings.js
264
264
  var require_strings = __commonJS({
265
- "node_modules/.pnpm/@open-policy-agent+opa-wasm@1.10.0/node_modules/@open-policy-agent/opa-wasm/src/builtins/strings.js"(exports, module) {
265
+ "../../node_modules/.pnpm/@open-policy-agent+opa-wasm@1.10.0/node_modules/@open-policy-agent/opa-wasm/src/builtins/strings.js"(exports, module) {
266
266
  var vsprintf = require_sprintf().vsprintf;
267
267
  var sprintf = (s, values) => vsprintf(s, values);
268
268
  module.exports = { sprintf };
269
269
  }
270
270
  });
271
271
 
272
- // node_modules/.pnpm/@open-policy-agent+opa-wasm@1.10.0/node_modules/@open-policy-agent/opa-wasm/src/builtins/regex.js
272
+ // ../../node_modules/.pnpm/@open-policy-agent+opa-wasm@1.10.0/node_modules/@open-policy-agent/opa-wasm/src/builtins/regex.js
273
273
  var require_regex = __commonJS({
274
- "node_modules/.pnpm/@open-policy-agent+opa-wasm@1.10.0/node_modules/@open-policy-agent/opa-wasm/src/builtins/regex.js"(exports, module) {
274
+ "../../node_modules/.pnpm/@open-policy-agent+opa-wasm@1.10.0/node_modules/@open-policy-agent/opa-wasm/src/builtins/regex.js"(exports, module) {
275
275
  var regexSplit = (pattern, s) => s.split(RegExp(pattern));
276
276
  module.exports = { "regex.split": regexSplit };
277
277
  }
278
278
  });
279
279
 
280
- // node_modules/.pnpm/yaml@1.10.3/node_modules/yaml/dist/PlainValue-516d5bc2.js
280
+ // ../../node_modules/.pnpm/yaml@1.10.3/node_modules/yaml/dist/PlainValue-516d5bc2.js
281
281
  var require_PlainValue_516d5bc2 = __commonJS({
282
- "node_modules/.pnpm/yaml@1.10.3/node_modules/yaml/dist/PlainValue-516d5bc2.js"(exports) {
282
+ "../../node_modules/.pnpm/yaml@1.10.3/node_modules/yaml/dist/PlainValue-516d5bc2.js"(exports) {
283
283
  "use strict";
284
284
  var Char = {
285
285
  ANCHOR: "&",
@@ -1042,9 +1042,9 @@ ${ctx}
1042
1042
  }
1043
1043
  });
1044
1044
 
1045
- // node_modules/.pnpm/yaml@1.10.3/node_modules/yaml/dist/parse-cst.js
1045
+ // ../../node_modules/.pnpm/yaml@1.10.3/node_modules/yaml/dist/parse-cst.js
1046
1046
  var require_parse_cst = __commonJS({
1047
- "node_modules/.pnpm/yaml@1.10.3/node_modules/yaml/dist/parse-cst.js"(exports) {
1047
+ "../../node_modules/.pnpm/yaml@1.10.3/node_modules/yaml/dist/parse-cst.js"(exports) {
1048
1048
  "use strict";
1049
1049
  var PlainValue = require_PlainValue_516d5bc2();
1050
1050
  var BlankLine = class extends PlainValue.Node {
@@ -2535,9 +2535,9 @@ var require_parse_cst = __commonJS({
2535
2535
  }
2536
2536
  });
2537
2537
 
2538
- // node_modules/.pnpm/yaml@1.10.3/node_modules/yaml/dist/resolveSeq-95613e94.js
2538
+ // ../../node_modules/.pnpm/yaml@1.10.3/node_modules/yaml/dist/resolveSeq-95613e94.js
2539
2539
  var require_resolveSeq_95613e94 = __commonJS({
2540
- "node_modules/.pnpm/yaml@1.10.3/node_modules/yaml/dist/resolveSeq-95613e94.js"(exports) {
2540
+ "../../node_modules/.pnpm/yaml@1.10.3/node_modules/yaml/dist/resolveSeq-95613e94.js"(exports) {
2541
2541
  "use strict";
2542
2542
  var PlainValue = require_PlainValue_516d5bc2();
2543
2543
  function addCommentBefore(str, indent, comment) {
@@ -4512,9 +4512,9 @@ ${ca}` : ca;
4512
4512
  }
4513
4513
  });
4514
4514
 
4515
- // node_modules/.pnpm/yaml@1.10.3/node_modules/yaml/dist/warnings-793925ce.js
4515
+ // ../../node_modules/.pnpm/yaml@1.10.3/node_modules/yaml/dist/warnings-793925ce.js
4516
4516
  var require_warnings_793925ce = __commonJS({
4517
- "node_modules/.pnpm/yaml@1.10.3/node_modules/yaml/dist/warnings-793925ce.js"(exports) {
4517
+ "../../node_modules/.pnpm/yaml@1.10.3/node_modules/yaml/dist/warnings-793925ce.js"(exports) {
4518
4518
  "use strict";
4519
4519
  var PlainValue = require_PlainValue_516d5bc2();
4520
4520
  var resolveSeq = require_resolveSeq_95613e94();
@@ -4879,9 +4879,9 @@ ${pair.comment}` : item.comment;
4879
4879
  }
4880
4880
  });
4881
4881
 
4882
- // node_modules/.pnpm/yaml@1.10.3/node_modules/yaml/dist/Schema-bcc6c2d7.js
4882
+ // ../../node_modules/.pnpm/yaml@1.10.3/node_modules/yaml/dist/Schema-bcc6c2d7.js
4883
4883
  var require_Schema_bcc6c2d7 = __commonJS({
4884
- "node_modules/.pnpm/yaml@1.10.3/node_modules/yaml/dist/Schema-bcc6c2d7.js"(exports) {
4884
+ "../../node_modules/.pnpm/yaml@1.10.3/node_modules/yaml/dist/Schema-bcc6c2d7.js"(exports) {
4885
4885
  "use strict";
4886
4886
  var PlainValue = require_PlainValue_516d5bc2();
4887
4887
  var resolveSeq = require_resolveSeq_95613e94();
@@ -5345,9 +5345,9 @@ var require_Schema_bcc6c2d7 = __commonJS({
5345
5345
  }
5346
5346
  });
5347
5347
 
5348
- // node_modules/.pnpm/yaml@1.10.3/node_modules/yaml/dist/Document-a8d0fbf9.js
5348
+ // ../../node_modules/.pnpm/yaml@1.10.3/node_modules/yaml/dist/Document-a8d0fbf9.js
5349
5349
  var require_Document_a8d0fbf9 = __commonJS({
5350
- "node_modules/.pnpm/yaml@1.10.3/node_modules/yaml/dist/Document-a8d0fbf9.js"(exports) {
5350
+ "../../node_modules/.pnpm/yaml@1.10.3/node_modules/yaml/dist/Document-a8d0fbf9.js"(exports) {
5351
5351
  "use strict";
5352
5352
  var PlainValue = require_PlainValue_516d5bc2();
5353
5353
  var resolveSeq = require_resolveSeq_95613e94();
@@ -6028,9 +6028,9 @@ ${cbNode.commentBefore}` : cb;
6028
6028
  }
6029
6029
  });
6030
6030
 
6031
- // node_modules/.pnpm/yaml@1.10.3/node_modules/yaml/dist/index.js
6031
+ // ../../node_modules/.pnpm/yaml@1.10.3/node_modules/yaml/dist/index.js
6032
6032
  var require_dist = __commonJS({
6033
- "node_modules/.pnpm/yaml@1.10.3/node_modules/yaml/dist/index.js"(exports) {
6033
+ "../../node_modules/.pnpm/yaml@1.10.3/node_modules/yaml/dist/index.js"(exports) {
6034
6034
  "use strict";
6035
6035
  var parseCst = require_parse_cst();
6036
6036
  var Document$1 = require_Document_a8d0fbf9();
@@ -6099,16 +6099,16 @@ var require_dist = __commonJS({
6099
6099
  }
6100
6100
  });
6101
6101
 
6102
- // node_modules/.pnpm/yaml@1.10.3/node_modules/yaml/index.js
6102
+ // ../../node_modules/.pnpm/yaml@1.10.3/node_modules/yaml/index.js
6103
6103
  var require_yaml = __commonJS({
6104
- "node_modules/.pnpm/yaml@1.10.3/node_modules/yaml/index.js"(exports, module) {
6104
+ "../../node_modules/.pnpm/yaml@1.10.3/node_modules/yaml/index.js"(exports, module) {
6105
6105
  module.exports = require_dist().YAML;
6106
6106
  }
6107
6107
  });
6108
6108
 
6109
- // node_modules/.pnpm/@open-policy-agent+opa-wasm@1.10.0/node_modules/@open-policy-agent/opa-wasm/src/builtins/yaml.js
6109
+ // ../../node_modules/.pnpm/@open-policy-agent+opa-wasm@1.10.0/node_modules/@open-policy-agent/opa-wasm/src/builtins/yaml.js
6110
6110
  var require_yaml2 = __commonJS({
6111
- "node_modules/.pnpm/@open-policy-agent+opa-wasm@1.10.0/node_modules/@open-policy-agent/opa-wasm/src/builtins/yaml.js"(exports, module) {
6111
+ "../../node_modules/.pnpm/@open-policy-agent+opa-wasm@1.10.0/node_modules/@open-policy-agent/opa-wasm/src/builtins/yaml.js"(exports, module) {
6112
6112
  var yaml = require_yaml();
6113
6113
  var errors = /* @__PURE__ */ new Set([
6114
6114
  "YAMLReferenceError",
@@ -6143,9 +6143,9 @@ var require_yaml2 = __commonJS({
6143
6143
  }
6144
6144
  });
6145
6145
 
6146
- // node_modules/.pnpm/@open-policy-agent+opa-wasm@1.10.0/node_modules/@open-policy-agent/opa-wasm/src/builtins/index.js
6146
+ // ../../node_modules/.pnpm/@open-policy-agent+opa-wasm@1.10.0/node_modules/@open-policy-agent/opa-wasm/src/builtins/index.js
6147
6147
  var require_builtins = __commonJS({
6148
- "node_modules/.pnpm/@open-policy-agent+opa-wasm@1.10.0/node_modules/@open-policy-agent/opa-wasm/src/builtins/index.js"(exports, module) {
6148
+ "../../node_modules/.pnpm/@open-policy-agent+opa-wasm@1.10.0/node_modules/@open-policy-agent/opa-wasm/src/builtins/index.js"(exports, module) {
6149
6149
  var json = require_json();
6150
6150
  var strings = require_strings();
6151
6151
  var regex = require_regex();
@@ -6159,9 +6159,9 @@ var require_builtins = __commonJS({
6159
6159
  }
6160
6160
  });
6161
6161
 
6162
- // node_modules/.pnpm/@open-policy-agent+opa-wasm@1.10.0/node_modules/@open-policy-agent/opa-wasm/src/opa.js
6162
+ // ../../node_modules/.pnpm/@open-policy-agent+opa-wasm@1.10.0/node_modules/@open-policy-agent/opa-wasm/src/opa.js
6163
6163
  var require_opa = __commonJS({
6164
- "node_modules/.pnpm/@open-policy-agent+opa-wasm@1.10.0/node_modules/@open-policy-agent/opa-wasm/src/opa.js"(exports, module) {
6164
+ "../../node_modules/.pnpm/@open-policy-agent+opa-wasm@1.10.0/node_modules/@open-policy-agent/opa-wasm/src/opa.js"(exports, module) {
6165
6165
  var builtIns = require_builtins();
6166
6166
  function stringDecoder(mem) {
6167
6167
  return function(addr) {
@@ -6514,7 +6514,7 @@ var require_opa = __commonJS({
6514
6514
  }
6515
6515
  });
6516
6516
 
6517
- // node_modules/.pnpm/@open-policy-agent+opa-wasm@1.10.0/node_modules/@open-policy-agent/opa-wasm/src/index.mjs
6517
+ // ../../node_modules/.pnpm/@open-policy-agent+opa-wasm@1.10.0/node_modules/@open-policy-agent/opa-wasm/src/index.mjs
6518
6518
  var src_exports = {};
6519
6519
  __export(src_exports, {
6520
6520
  default: () => src_default,
@@ -6522,20 +6522,21 @@ __export(src_exports, {
6522
6522
  });
6523
6523
  var import_opa, loadPolicy, src_default;
6524
6524
  var init_src = __esm({
6525
- "node_modules/.pnpm/@open-policy-agent+opa-wasm@1.10.0/node_modules/@open-policy-agent/opa-wasm/src/index.mjs"() {
6525
+ "../../node_modules/.pnpm/@open-policy-agent+opa-wasm@1.10.0/node_modules/@open-policy-agent/opa-wasm/src/index.mjs"() {
6526
6526
  import_opa = __toESM(require_opa(), 1);
6527
6527
  loadPolicy = import_opa.default.loadPolicy;
6528
6528
  src_default = import_opa.default;
6529
6529
  }
6530
6530
  });
6531
6531
 
6532
- // packages/proxy/hooks/guard.mjs
6532
+ // hooks/guard.mjs
6533
6533
  import { readFileSync, existsSync, statSync, writeFileSync, mkdirSync, chmodSync, renameSync, appendFileSync } from "node:fs";
6534
+ import { spawn } from "node:child_process";
6534
6535
  import { resolve, join, dirname, isAbsolute } from "node:path";
6535
6536
  import { homedir } from "node:os";
6536
6537
  import { gunzipSync } from "node:zlib";
6537
6538
  import { createHash } from "node:crypto";
6538
- var HOOK_VERSION = 32;
6539
+ var HOOK_VERSION = 34;
6539
6540
  function localLogsOnly(security) {
6540
6541
  const l = security && security.localLogs;
6541
6542
  return !!(l && l.enabled && typeof l.path === "string" && l.path.trim());
@@ -6713,6 +6714,51 @@ var AGENT_TYPE = process.argv[2] || "claude-code";
6713
6714
  var POLICY_SELECTOR = process.env.SOLONGATE_AGENT_ID || "";
6714
6715
  var AGENT_ID = POLICY_SELECTOR || AGENT_TYPE;
6715
6716
  var AGENT_NAME = process.env.SOLONGATE_AGENT_NAME || process.argv[3] || AGENT_TYPE;
6717
+ var REFRESH_MODE = process.argv.includes("--sg-refresh-policy");
6718
+ async function refreshPolicyCache() {
6719
+ try {
6720
+ const agentKey = (AGENT_ID || "default").replace(/[^a-zA-Z0-9_-]/g, "_");
6721
+ const cacheFile = join(resolve(homedir(), ".solongate"), ".policy-cache-" + agentKey + ".json");
6722
+ let selfProtect = true, security = null, hookVersions = null, policy = null;
6723
+ try {
6724
+ if (existsSync(cacheFile)) {
6725
+ const c = JSON.parse(readFileSync(cacheFile, "utf-8"));
6726
+ if (c) {
6727
+ policy = c.policy ?? null;
6728
+ if (typeof c.selfProtect === "boolean")
6729
+ selfProtect = c.selfProtect;
6730
+ if (c.security !== void 0)
6731
+ security = c.security;
6732
+ if (c.hookVersions)
6733
+ hookVersions = c.hookVersions;
6734
+ }
6735
+ }
6736
+ } catch {
6737
+ }
6738
+ try {
6739
+ const res = await fetch(API_URL + "/api/v1/policies/active?agent_id=" + encodeURIComponent(AGENT_ID || "") + "&hv=" + HOOK_VERSION, { headers: AUTH_HEADERS, signal: AbortSignal.timeout(8e3) });
6740
+ if (res.ok) {
6741
+ const body = await res.json();
6742
+ if (typeof body?.self_protection_enabled === "boolean")
6743
+ selfProtect = body.self_protection_enabled;
6744
+ if (body?.security !== void 0)
6745
+ security = body.security;
6746
+ if (body?.hook_versions && typeof body.hook_versions === "object")
6747
+ hookVersions = body.hook_versions;
6748
+ policy = body && body.policy ? body.policy : null;
6749
+ }
6750
+ } catch {
6751
+ }
6752
+ try {
6753
+ writeFileSync(cacheFile, JSON.stringify({ _ts: Date.now(), policy, selfProtect, security, hookVersions }));
6754
+ } catch {
6755
+ }
6756
+ } catch {
6757
+ }
6758
+ }
6759
+ if (REFRESH_MODE) {
6760
+ refreshPolicyCache().finally(() => process.exit(0));
6761
+ }
6716
6762
  function blockTool(reason) {
6717
6763
  if (AGENT_TYPE === "gemini-cli") {
6718
6764
  process.stdout.write(JSON.stringify({
@@ -7610,6 +7656,8 @@ function readReferencedFiles(args, cwd) {
7610
7656
  }
7611
7657
  process.stdin.on("data", (c) => input += c);
7612
7658
  process.stdin.on("end", async () => {
7659
+ if (REFRESH_MODE)
7660
+ return;
7613
7661
  if (process.env.SOLONGATE_DEBUG) {
7614
7662
  }
7615
7663
  if (!API_KEY) {
@@ -7671,21 +7719,6 @@ process.stdin.on("end", async () => {
7671
7719
  } catch {
7672
7720
  }
7673
7721
  if (refreshDue) {
7674
- try {
7675
- const res = await fetch(API_URL + "/api/v1/policies/active?agent_id=" + encodeURIComponent(AGENT_ID || "") + "&hv=" + HOOK_VERSION, { headers: AUTH_HEADERS, signal: AbortSignal.timeout(2e3) });
7676
- if (res.ok) {
7677
- const body = await res.json();
7678
- if (typeof body?.self_protection_enabled === "boolean")
7679
- selfProtectEnabled = body.self_protection_enabled;
7680
- if (body?.security !== void 0)
7681
- securityCfg = body.security;
7682
- if (body?.hook_versions && typeof body.hook_versions === "object")
7683
- CLOUD_HOOK_VERSIONS = body.hook_versions;
7684
- if (body && body.policy)
7685
- dashboardPolicy = body.policy;
7686
- }
7687
- } catch {
7688
- }
7689
7722
  if (!dashboardPolicy && staleCache) {
7690
7723
  dashboardPolicy = staleCache.policy;
7691
7724
  if (typeof staleCache.selfProtect === "boolean")
@@ -7695,10 +7728,19 @@ process.stdin.on("end", async () => {
7695
7728
  if (staleCache.hookVersions)
7696
7729
  CLOUD_HOOK_VERSIONS = staleCache.hookVersions;
7697
7730
  }
7698
- const keepPolicy = dashboardPolicy || staleCache && staleCache.policy || null;
7699
- try {
7700
- writeFileSync(policyCacheFile, JSON.stringify({ _ts: Date.now(), policy: keepPolicy, selfProtect: selfProtectEnabled, security: securityCfg, hookVersions: CLOUD_HOOK_VERSIONS }));
7701
- } catch {
7731
+ if (API_KEY) {
7732
+ try {
7733
+ const lock = join(resolve(homedir(), ".solongate"), ".policy-refresh-" + agentKey + ".lock");
7734
+ const due = !existsSync(lock) || Date.now() - statSync(lock).mtimeMs > 3e3;
7735
+ if (due) {
7736
+ try {
7737
+ writeFileSync(lock, String(Date.now()));
7738
+ } catch {
7739
+ }
7740
+ spawn(process.execPath, [process.argv[1], AGENT_TYPE, AGENT_NAME, "--sg-refresh-policy"], { detached: true, stdio: "ignore", env: process.env }).unref();
7741
+ }
7742
+ } catch {
7743
+ }
7702
7744
  }
7703
7745
  }
7704
7746
  if (process.env.SOLONGATE_DEBUG) {
@@ -7808,7 +7850,18 @@ process.stdin.on("end", async () => {
7808
7850
  try {
7809
7851
  const _fd = resolve(".solongate");
7810
7852
  mkdirSync(_fd, { recursive: true });
7811
- writeFileSync(join(_fd, ".last-eval"), JSON.stringify({ ms: Date.now() - _evalStart, ts: Date.now(), tool: toolName, session: data.session_id || "" }));
7853
+ const _rec = { ms: Date.now() - _evalStart, ts: Date.now(), tool: toolName, session: data.session_id || "" };
7854
+ writeFileSync(join(_fd, ".last-eval"), JSON.stringify(_rec));
7855
+ try {
7856
+ const ring = join(_fd, ".eval-ring.jsonl");
7857
+ appendFileSync(ring, JSON.stringify(_rec) + "\n");
7858
+ try {
7859
+ if (statSync(ring).size > 16384)
7860
+ writeFileSync(ring, readFileSync(ring, "utf-8").split("\n").filter(Boolean).slice(-50).join("\n") + "\n");
7861
+ } catch {
7862
+ }
7863
+ } catch {
7864
+ }
7812
7865
  } catch {
7813
7866
  }
7814
7867
  if (reason) {
package/hooks/guard.mjs CHANGED
@@ -22,6 +22,7 @@
22
22
  * Auto-installed by: npx @solongate/proxy init --global
23
23
  */
24
24
  import { readFileSync, existsSync, statSync, writeFileSync, mkdirSync, chmodSync, renameSync, appendFileSync } from 'node:fs';
25
+ import { spawn } from 'node:child_process';
25
26
  import { resolve, join, dirname, isAbsolute } from 'node:path';
26
27
  import { homedir } from 'node:os';
27
28
  import { gunzipSync } from 'node:zlib';
@@ -31,7 +32,7 @@ import { createHash } from 'node:crypto';
31
32
  // the installed hook self-updates when the cloud version is higher (see
32
33
  // maybeSelfUpdate). This is what makes guard fixes propagate without a manual
33
34
  // reinstall — the same trust model as the OPA WASM this hook already runs.
34
- const HOOK_VERSION = 32;
35
+ const HOOK_VERSION = 34;
35
36
 
36
37
  // True when local log storage is ON. In that mode logs are kept LOCAL ONLY and
37
38
  // nothing is sent to the cloud audit log.
@@ -248,6 +249,40 @@ const POLICY_SELECTOR = process.env.SOLONGATE_AGENT_ID || '';
248
249
  const AGENT_ID = POLICY_SELECTOR || AGENT_TYPE;
249
250
  const AGENT_NAME = process.env.SOLONGATE_AGENT_NAME || process.argv[3] || AGENT_TYPE;
250
251
 
252
+ // ── Background policy refresh (stale-while-revalidate) ──
253
+ // The hot path NEVER blocks on the network: when the policy cache is stale it
254
+ // serves the cached (or local) policy INSTANTLY and spawns this detached mode to
255
+ // fetch a fresh policy and rewrite the cache for the NEXT call. Result: fast tool
256
+ // calls AND ~one-call propagation of policy changes — no long-TTL tradeoff.
257
+ const REFRESH_MODE = process.argv.includes('--sg-refresh-policy');
258
+ async function refreshPolicyCache() {
259
+ try {
260
+ const agentKey = (AGENT_ID || 'default').replace(/[^a-zA-Z0-9_-]/g, '_');
261
+ const cacheFile = join(resolve(homedir(), '.solongate'), '.policy-cache-' + agentKey + '.json');
262
+ // Preserve existing fields so a failed fetch never erases the last-known-good.
263
+ let selfProtect = true, security = null, hookVersions = null, policy = null;
264
+ try {
265
+ if (existsSync(cacheFile)) {
266
+ const c = JSON.parse(readFileSync(cacheFile, 'utf-8'));
267
+ if (c) { policy = c.policy ?? null; if (typeof c.selfProtect === 'boolean') selfProtect = c.selfProtect; if (c.security !== undefined) security = c.security; if (c.hookVersions) hookVersions = c.hookVersions; }
268
+ }
269
+ } catch {}
270
+ try {
271
+ const res = await fetch(API_URL + '/api/v1/policies/active?agent_id=' + encodeURIComponent(AGENT_ID || '') + '&hv=' + HOOK_VERSION, { headers: AUTH_HEADERS, signal: AbortSignal.timeout(8000) });
272
+ if (res.ok) {
273
+ const body = await res.json();
274
+ if (typeof body?.self_protection_enabled === 'boolean') selfProtect = body.self_protection_enabled;
275
+ if (body?.security !== undefined) security = body.security;
276
+ if (body?.hook_versions && typeof body.hook_versions === 'object') hookVersions = body.hook_versions;
277
+ policy = (body && body.policy) ? body.policy : null;
278
+ }
279
+ } catch {}
280
+ // Always advance _ts (success or failure) so the hot path backs off between refreshes.
281
+ try { writeFileSync(cacheFile, JSON.stringify({ _ts: Date.now(), policy, selfProtect, security, hookVersions })); } catch {}
282
+ } catch {}
283
+ }
284
+ if (REFRESH_MODE) { refreshPolicyCache().finally(() => process.exit(0)); }
285
+
251
286
  // ── Per-tool block/allow output ──
252
287
  // Response format depends on the agent:
253
288
  // Claude Code: exit 2 + stderr = BLOCK, exit 0 = ALLOW
@@ -1369,6 +1404,9 @@ function readReferencedFiles(args, cwd) {
1369
1404
 
1370
1405
  process.stdin.on('data', c => input += c);
1371
1406
  process.stdin.on('end', async () => {
1407
+ // Background-refresh invocation has no tool call to evaluate — it already ran
1408
+ // refreshPolicyCache() at startup; do nothing on the (empty) stdin.
1409
+ if (REFRESH_MODE) return;
1372
1410
  // No policy selected => no enforcement. A plain launch (no SOLONGATE_AGENT_ID)
1373
1411
  // is intentionally unrestricted.
1374
1412
  if (process.env.SOLONGATE_DEBUG) {
@@ -1466,32 +1504,31 @@ process.stdin.on('end', async () => {
1466
1504
  }
1467
1505
  }
1468
1506
  } catch {}
1469
- // Refresh from the API at most once per TTL window.
1507
+ // Cache stale → DO NOT block the tool on the network. Serve the last-known-good
1508
+ // policy right now (or the local policy.json fallback below if there is none)
1509
+ // and kick off a DETACHED background refresh that rewrites the cache for the
1510
+ // NEXT call. Stale-while-revalidate: tool calls stay instant (~ms), yet a
1511
+ // policy change still lands within ~one call, because the fetch runs in
1512
+ // parallel instead of on the hot path.
1470
1513
  if (refreshDue) {
1471
- try {
1472
- const res = await fetch(API_URL + '/api/v1/policies/active?agent_id=' + encodeURIComponent(AGENT_ID || '') + '&hv=' + HOOK_VERSION, { headers: AUTH_HEADERS, signal: AbortSignal.timeout(2000) });
1473
- if (res.ok) {
1474
- const body = await res.json();
1475
- if (typeof body?.self_protection_enabled === 'boolean') selfProtectEnabled = body.self_protection_enabled;
1476
- if (body?.security !== undefined) securityCfg = body.security;
1477
- if (body?.hook_versions && typeof body.hook_versions === 'object') CLOUD_HOOK_VERSIONS = body.hook_versions;
1478
- if (body && body.policy) dashboardPolicy = body.policy;
1479
- }
1480
- } catch {}
1481
- // API slow/unreachable → keep enforcing the last-known-good policy.
1482
1514
  if (!dashboardPolicy && staleCache) {
1483
1515
  dashboardPolicy = staleCache.policy;
1484
1516
  if (typeof staleCache.selfProtect === 'boolean') selfProtectEnabled = staleCache.selfProtect;
1485
1517
  if (staleCache.security !== undefined) securityCfg = staleCache.security;
1486
1518
  if (staleCache.hookVersions) CLOUD_HOOK_VERSIONS = staleCache.hookVersions;
1487
1519
  }
1488
- // ALWAYS advance _ts after an attempt success, stale fallback, or even a
1489
- // cold miss with no policy so a slow/down endpoint is retried at most
1490
- // once per TTL (≈2s per 10s worst case, not 2s per call). Keep the
1491
- // last-known-good policy in the cache so a failed attempt never erases it;
1492
- // when nothing is available, enforcement falls back to ~/.solongate/policy.json below.
1493
- const keepPolicy = dashboardPolicy || (staleCache && staleCache.policy) || null;
1494
- try { writeFileSync(policyCacheFile, JSON.stringify({ _ts: Date.now(), policy: keepPolicy, selfProtect: selfProtectEnabled, security: securityCfg, hookVersions: CLOUD_HOOK_VERSIONS })); } catch {}
1520
+ // Debounce: at most one background refresh in flight per ~3s, so a burst of
1521
+ // stale calls (or several agents sharing this cache) doesn't spawn a swarm.
1522
+ if (API_KEY) {
1523
+ try {
1524
+ const lock = join(resolve(homedir(), '.solongate'), '.policy-refresh-' + agentKey + '.lock');
1525
+ const due = !existsSync(lock) || (Date.now() - statSync(lock).mtimeMs) > 3000;
1526
+ if (due) {
1527
+ try { writeFileSync(lock, String(Date.now())); } catch {}
1528
+ spawn(process.execPath, [process.argv[1], AGENT_TYPE, AGENT_NAME, '--sg-refresh-policy'], { detached: true, stdio: 'ignore', env: process.env }).unref();
1529
+ }
1530
+ } catch {}
1531
+ }
1495
1532
  }
1496
1533
 
1497
1534
  if (process.env.SOLONGATE_DEBUG) {
@@ -1624,7 +1661,21 @@ process.stdin.on('end', async () => {
1624
1661
  // ALLOW path and can't time the guard itself, so it reads this file back.
1625
1662
  // Keyed by tool + session so the audit hook can match THIS invocation even
1626
1663
  // when the tool itself runs for minutes (a bare timestamp TTL lost those).
1627
- try { const _fd = resolve('.solongate'); mkdirSync(_fd, { recursive: true }); writeFileSync(join(_fd, '.last-eval'), JSON.stringify({ ms: Date.now() - _evalStart, ts: Date.now(), tool: toolName, session: data.session_id || '' })); } catch {}
1664
+ try {
1665
+ const _fd = resolve('.solongate'); mkdirSync(_fd, { recursive: true });
1666
+ const _rec = { ms: Date.now() - _evalStart, ts: Date.now(), tool: toolName, session: data.session_id || '' };
1667
+ writeFileSync(join(_fd, '.last-eval'), JSON.stringify(_rec));
1668
+ // Also append to a short ring. When many tool calls run in PARALLEL they all
1669
+ // race on the single .last-eval above (overwrite / torn read → blank eval
1670
+ // time in the log). appendFileSync lines are atomic, so the PostToolUse
1671
+ // audit hook can recover a value by averaging the recent same-session
1672
+ // records — a parallel burst then shares one arithmetic-mean eval time.
1673
+ try {
1674
+ const ring = join(_fd, '.eval-ring.jsonl');
1675
+ appendFileSync(ring, JSON.stringify(_rec) + '\n');
1676
+ try { if (statSync(ring).size > 16384) writeFileSync(ring, readFileSync(ring, 'utf-8').split('\n').filter(Boolean).slice(-50).join('\n') + '\n'); } catch {}
1677
+ } catch {}
1678
+ } catch {}
1628
1679
 
1629
1680
  // Only log DENY decisions from guard hook.
1630
1681
  // ALLOW decisions are logged by the audit hook (PostToolUse) to avoid double-counting.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@solongate/proxy",
3
- "version": "0.59.3",
3
+ "version": "0.59.5",
4
4
  "description": "AI tool security proxy: protect any AI tool server with customizable policies, path/command constraints, rate limiting, and audit logging. No code changes required.",
5
5
  "type": "module",
6
6
  "bin": {