@solongate/proxy 0.81.42 → 0.81.44

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.
package/dist/index.js CHANGED
@@ -7537,6 +7537,7 @@ function LivePanel({ active: active2 }) {
7537
7537
  const inspectFromRef = useRef2("stream");
7538
7538
  const seenRef = useRef2(/* @__PURE__ */ new Set());
7539
7539
  const lastLocalTs = useRef2(0);
7540
+ const localSeq = useRef2(0);
7540
7541
  const pausedUntil = useRef2(0);
7541
7542
  const sessRef = useRef2([]);
7542
7543
  const mergedRef = useRef2([]);
@@ -7582,7 +7583,7 @@ function LivePanel({ active: active2 }) {
7582
7583
  const at = Date.parse(j.ts ?? "");
7583
7584
  if (!Number.isFinite(at) || at <= lastLocalTs.current) continue;
7584
7585
  fresh.push({
7585
- id: "l:" + at + ":" + (j.tool ?? ""),
7586
+ id: "l:" + at + ":" + (j.tool ?? "") + ":" + localSeq.current++,
7586
7587
  at,
7587
7588
  tool: j.tool ?? "?",
7588
7589
  decision: j.decision ?? "ALLOW",
@@ -7705,20 +7706,45 @@ function LivePanel({ active: active2 }) {
7705
7706
  }, [active2, frozen]);
7706
7707
  const startRef = useRef2(Date.now());
7707
7708
  const lastNotifyAt = useRef2(0);
7709
+ const toastQueue = useRef2(/* @__PURE__ */ new Map());
7710
+ const toastTimer = useRef2(null);
7711
+ const TOAST_RANK = { "DLP hit": 0, "Call denied": 1, "Rate-limit burst": 2 };
7712
+ const flushToasts = useCallback2(function flush() {
7713
+ toastTimer.current = null;
7714
+ const q2 = toastQueue.current;
7715
+ if (!q2.size) return;
7716
+ const wait = 3e3 - (Date.now() - lastNotifyAt.current);
7717
+ if (wait > 0) {
7718
+ toastTimer.current = setTimeout(flush, wait);
7719
+ return;
7720
+ }
7721
+ lastNotifyAt.current = Date.now();
7722
+ const items = [...q2.entries()].sort((a, b) => (TOAST_RANK[a[0]] ?? 9) - (TOAST_RANK[b[0]] ?? 9));
7723
+ q2.clear();
7724
+ try {
7725
+ process.stdout.write("\x07");
7726
+ } catch {
7727
+ }
7728
+ const total = items.reduce((n, [, v]) => n + v.count, 0);
7729
+ if (items.length === 1 && total === 1) desktopNotify(items[0][0], items[0][1].msg);
7730
+ else desktopNotify("Security alerts", items.map(([t, v]) => `${v.count}\xD7 ${t}`).join(" \xB7 "));
7731
+ }, []);
7732
+ useEffect2(
7733
+ () => () => {
7734
+ if (toastTimer.current) clearTimeout(toastTimer.current);
7735
+ },
7736
+ []
7737
+ );
7708
7738
  const fireAlert = useCallback2(
7709
- (id, title, msg, level = "warn") => {
7739
+ (id, title, msg, level = "warn", desktop = true) => {
7710
7740
  pushLog(`${level === "bad" ? "\u26D4" : "\u23F8"} ${msg}`, level);
7711
7741
  setAlerts((a) => [...a.filter((x) => x.id !== id), { id, msg, level, until: Date.now() + 15e3 }].slice(-4));
7712
- if (!CONFIG.notifications) return;
7713
- if (Date.now() - lastNotifyAt.current < 3e3) return;
7714
- lastNotifyAt.current = Date.now();
7715
- try {
7716
- process.stdout.write("\x07");
7717
- } catch {
7718
- }
7719
- desktopNotify(title, msg);
7742
+ if (!desktop || !CONFIG.notifications) return;
7743
+ const cur = toastQueue.current.get(title);
7744
+ toastQueue.current.set(title, { count: (cur?.count ?? 0) + 1, msg: cur?.msg ?? msg });
7745
+ if (!toastTimer.current) toastTimer.current = setTimeout(flushToasts, 400);
7720
7746
  },
7721
- [pushLog]
7747
+ [pushLog, flushToasts]
7722
7748
  );
7723
7749
  useEffect2(() => {
7724
7750
  if (!active2) return;
@@ -7726,7 +7752,7 @@ function LivePanel({ active: active2 }) {
7726
7752
  const prev = prevStatus.current.get(r.id);
7727
7753
  if (prev && prev === "active" && r.status === "idle") {
7728
7754
  const name = r.isMe ? `${r.agent} (this machine)` : r.agent;
7729
- fireAlert("idle:" + r.id, "Agent idle", `${name} went IDLE, no tool call for 60s`, "warn");
7755
+ fireAlert("idle:" + r.id, "Agent idle", `${name} went IDLE, no tool call for 60s`, "warn", false);
7730
7756
  }
7731
7757
  prevStatus.current.set(r.id, r.status);
7732
7758
  }
@@ -10150,6 +10176,9 @@ var tui_exports = {};
10150
10176
  __export(tui_exports, {
10151
10177
  launchTui: () => launchTui
10152
10178
  });
10179
+ import { appendFileSync as appendFileSync2, mkdirSync as mkdirSync6 } from "fs";
10180
+ import { homedir as homedir7 } from "os";
10181
+ import { join as join9 } from "path";
10153
10182
  import { render } from "ink";
10154
10183
  import { jsx as jsx10 } from "react/jsx-runtime";
10155
10184
  async function launchTui() {
@@ -10160,10 +10189,26 @@ async function launchTui() {
10160
10189
  return;
10161
10190
  }
10162
10191
  process.stdout.write("\x1B[?1049h\x1B[H");
10192
+ const debugLog = join9(homedir7(), ".solongate", "dataroom-debug.log");
10193
+ const saved = { log: console.log, warn: console.warn, error: console.error, info: console.info, debug: console.debug };
10194
+ const toFile = (level) => (...args) => {
10195
+ try {
10196
+ mkdirSync6(join9(homedir7(), ".solongate"), { recursive: true });
10197
+ appendFileSync2(debugLog, `${(/* @__PURE__ */ new Date()).toISOString()} [${level}] ${args.map((a) => typeof a === "string" ? a : JSON.stringify(a)).join(" ")}
10198
+ `);
10199
+ } catch {
10200
+ }
10201
+ };
10202
+ console.log = toFile("log");
10203
+ console.warn = toFile("warn");
10204
+ console.error = toFile("error");
10205
+ console.info = toFile("info");
10206
+ console.debug = toFile("debug");
10163
10207
  try {
10164
- const { waitUntilExit } = render(/* @__PURE__ */ jsx10(App, {}));
10208
+ const { waitUntilExit } = render(/* @__PURE__ */ jsx10(App, {}), { patchConsole: false });
10165
10209
  await waitUntilExit();
10166
10210
  } finally {
10211
+ Object.assign(console, saved);
10167
10212
  process.stdout.write("\x1B[?1049l");
10168
10213
  }
10169
10214
  }
@@ -10863,8 +10908,8 @@ var init_agents2 = __esm({
10863
10908
 
10864
10909
  // src/commands/doctor.ts
10865
10910
  import { existsSync as existsSync4, statSync as statSync2 } from "fs";
10866
- import { homedir as homedir7 } from "os";
10867
- import { join as join9 } from "path";
10911
+ import { homedir as homedir8 } from "os";
10912
+ import { join as join10 } from "path";
10868
10913
  async function run6(argv) {
10869
10914
  const { flags } = parse(argv);
10870
10915
  const json = flagBool(flags, "json");
@@ -10924,14 +10969,14 @@ var init_doctor = __esm({
10924
10969
  init_api_client();
10925
10970
  init_format();
10926
10971
  init_args();
10927
- LOCAL_LOG2 = join9(homedir7(), ".solongate", "local-logs", "solongate-audit.jsonl");
10972
+ LOCAL_LOG2 = join10(homedir8(), ".solongate", "local-logs", "solongate-audit.jsonl");
10928
10973
  }
10929
10974
  });
10930
10975
 
10931
10976
  // src/commands/watch.ts
10932
10977
  import { closeSync as closeSync2, existsSync as existsSync5, openSync as openSync2, readSync as readSync2, statSync as statSync3 } from "fs";
10933
- import { homedir as homedir8 } from "os";
10934
- import { join as join10 } from "path";
10978
+ import { homedir as homedir9 } from "os";
10979
+ import { join as join11 } from "path";
10935
10980
  function tailLocal(file, maxBytes = 131072) {
10936
10981
  try {
10937
10982
  const size = statSync3(file).size;
@@ -11038,7 +11083,7 @@ var init_watch = __esm({
11038
11083
  init_cli_utils();
11039
11084
  init_args();
11040
11085
  init_format();
11041
- LOCAL_LOG3 = join10(homedir8(), ".solongate", "local-logs", "solongate-audit.jsonl");
11086
+ LOCAL_LOG3 = join11(homedir9(), ".solongate", "local-logs", "solongate-audit.jsonl");
11042
11087
  trunc = (s, n) => s.length <= n ? s : s.slice(0, n - 1) + "\u2026";
11043
11088
  time = (ms) => new Date(ms).toTimeString().slice(0, 8);
11044
11089
  }
@@ -11329,9 +11374,9 @@ var init_commands = __esm({
11329
11374
  });
11330
11375
 
11331
11376
  // src/global-install.ts
11332
- import { readFileSync as readFileSync8, writeFileSync as writeFileSync7, existsSync as existsSync6, mkdirSync as mkdirSync6 } from "fs";
11333
- import { resolve as resolve4, join as join11, dirname } from "path";
11334
- import { homedir as homedir9 } from "os";
11377
+ import { readFileSync as readFileSync8, writeFileSync as writeFileSync7, existsSync as existsSync6, mkdirSync as mkdirSync7 } from "fs";
11378
+ import { resolve as resolve4, join as join12, dirname } from "path";
11379
+ import { homedir as homedir10 } from "os";
11335
11380
  import { fileURLToPath } from "url";
11336
11381
  import { createInterface } from "readline";
11337
11382
  import { execFileSync as execFileSync2 } from "child_process";
@@ -11394,10 +11439,10 @@ function unlockFile(file) {
11394
11439
  function protectedTargets() {
11395
11440
  const p = globalPaths();
11396
11441
  return [
11397
- join11(p.hooksDir, "guard.mjs"),
11398
- join11(p.hooksDir, "audit.mjs"),
11399
- join11(p.hooksDir, "stop.mjs"),
11400
- join11(p.hooksDir, "shield.mjs"),
11442
+ join12(p.hooksDir, "guard.mjs"),
11443
+ join12(p.hooksDir, "audit.mjs"),
11444
+ join12(p.hooksDir, "stop.mjs"),
11445
+ join12(p.hooksDir, "shield.mjs"),
11401
11446
  p.configPath,
11402
11447
  p.settingsPath
11403
11448
  ];
@@ -11409,25 +11454,25 @@ function unlockProtected() {
11409
11454
  for (const f of protectedTargets()) unlockFile(f);
11410
11455
  }
11411
11456
  function globalPaths() {
11412
- const home = homedir9();
11413
- const sgDir = join11(home, ".solongate");
11414
- const hooksDir = join11(sgDir, "hooks");
11415
- const claudeDir = join11(home, ".claude");
11457
+ const home = homedir10();
11458
+ const sgDir = join12(home, ".solongate");
11459
+ const hooksDir = join12(sgDir, "hooks");
11460
+ const claudeDir = join12(home, ".claude");
11416
11461
  return {
11417
11462
  home,
11418
11463
  sgDir,
11419
11464
  hooksDir,
11420
11465
  claudeDir,
11421
- settingsPath: join11(claudeDir, "settings.json"),
11422
- backupPath: join11(claudeDir, "settings.solongate.bak"),
11423
- configPath: join11(sgDir, "cloud-guard.json")
11466
+ settingsPath: join12(claudeDir, "settings.json"),
11467
+ backupPath: join12(claudeDir, "settings.solongate.bak"),
11468
+ configPath: join12(sgDir, "cloud-guard.json")
11424
11469
  };
11425
11470
  }
11426
11471
  function readHook(filename) {
11427
- return readFileSync8(join11(HOOKS_DIR, filename), "utf-8");
11472
+ return readFileSync8(join12(HOOKS_DIR, filename), "utf-8");
11428
11473
  }
11429
11474
  function readGuard() {
11430
- const bundled = join11(HOOKS_DIR, "guard.bundled.mjs");
11475
+ const bundled = join12(HOOKS_DIR, "guard.bundled.mjs");
11431
11476
  return existsSync6(bundled) ? readFileSync8(bundled, "utf-8") : readHook("guard.mjs");
11432
11477
  }
11433
11478
  function ask(question) {
@@ -11462,7 +11507,7 @@ function shimTargets() {
11462
11507
  return [];
11463
11508
  }
11464
11509
  }
11465
- return [".bashrc", ".zshrc", ".profile"].map((f) => join11(homedir9(), f)).filter((f) => existsSync6(f));
11510
+ return [".bashrc", ".zshrc", ".profile"].map((f) => join12(homedir10(), f)).filter((f) => existsSync6(f));
11466
11511
  }
11467
11512
  function writeShimBlock(file, block2) {
11468
11513
  const re = new RegExp(escapeRe(SHIM_BEGIN) + "[\\s\\S]*?" + escapeRe(SHIM_END) + "\\r?\\n?", "g");
@@ -11472,7 +11517,7 @@ function writeShimBlock(file, block2) {
11472
11517
  if (content.length && !content.endsWith("\n")) content += "\n";
11473
11518
  content += block2 + "\n";
11474
11519
  }
11475
- mkdirSync6(dirname(file), { recursive: true });
11520
+ mkdirSync7(dirname(file), { recursive: true });
11476
11521
  writeFileSync7(file, content);
11477
11522
  }
11478
11523
  function installClaudeShim(shieldPath) {
@@ -11516,15 +11561,15 @@ async function runGlobalInstall(opts = {}) {
11516
11561
  process.exit(1);
11517
11562
  }
11518
11563
  const apiUrl = opts.apiUrl || process.env["SOLONGATE_API_URL"] || "https://api.solongate.com";
11519
- mkdirSync6(p.hooksDir, { recursive: true });
11520
- mkdirSync6(p.claudeDir, { recursive: true });
11564
+ mkdirSync7(p.hooksDir, { recursive: true });
11565
+ mkdirSync7(p.claudeDir, { recursive: true });
11521
11566
  unlockProtected();
11522
- writeFileSync7(join11(p.hooksDir, "guard.mjs"), readGuard());
11523
- writeFileSync7(join11(p.hooksDir, "audit.mjs"), readHook("audit.mjs"));
11524
- writeFileSync7(join11(p.hooksDir, "stop.mjs"), readHook("stop.mjs"));
11525
- writeFileSync7(join11(p.hooksDir, "shield.mjs"), readHook("shield.mjs"));
11567
+ writeFileSync7(join12(p.hooksDir, "guard.mjs"), readGuard());
11568
+ writeFileSync7(join12(p.hooksDir, "audit.mjs"), readHook("audit.mjs"));
11569
+ writeFileSync7(join12(p.hooksDir, "stop.mjs"), readHook("stop.mjs"));
11570
+ writeFileSync7(join12(p.hooksDir, "shield.mjs"), readHook("shield.mjs"));
11526
11571
  console.log(` Installed hooks \u2192 ${p.hooksDir}`);
11527
- installClaudeShim(join11(p.hooksDir, "shield.mjs"));
11572
+ installClaudeShim(join12(p.hooksDir, "shield.mjs"));
11528
11573
  writeFileSync7(p.configPath, JSON.stringify({ apiKey, apiUrl }, null, 2) + "\n");
11529
11574
  console.log(` Wrote ${p.configPath}`);
11530
11575
  let existing = {};
@@ -11540,9 +11585,9 @@ async function runGlobalInstall(opts = {}) {
11540
11585
  existing = {};
11541
11586
  }
11542
11587
  }
11543
- const guardAbs = join11(p.hooksDir, "guard.mjs").replace(/\\/g, "/");
11544
- const auditAbs = join11(p.hooksDir, "audit.mjs").replace(/\\/g, "/");
11545
- const stopAbs = join11(p.hooksDir, "stop.mjs").replace(/\\/g, "/");
11588
+ const guardAbs = join12(p.hooksDir, "guard.mjs").replace(/\\/g, "/");
11589
+ const auditAbs = join12(p.hooksDir, "audit.mjs").replace(/\\/g, "/");
11590
+ const stopAbs = join12(p.hooksDir, "stop.mjs").replace(/\\/g, "/");
11546
11591
  const nodeBin = process.execPath.replace(/\\/g, "/");
11547
11592
  const call = process.platform === "win32" ? "& " : "";
11548
11593
  const hookCmd = (script) => `${call}"${nodeBin}" "${script}" claude-code "Claude Code"`;
@@ -11734,9 +11779,9 @@ import { spawn as spawn4 } from "child_process";
11734
11779
  import { URL as URL2 } from "url";
11735
11780
  import { readFileSync as readFileSync9, existsSync as existsSync7, readdirSync, statSync as statSync4 } from "fs";
11736
11781
  import { resolve as resolve5 } from "path";
11737
- import { homedir as homedir10 } from "os";
11782
+ import { homedir as homedir11 } from "os";
11738
11783
  function findCacheFile() {
11739
- const dir = resolve5(homedir10(), ".solongate");
11784
+ const dir = resolve5(homedir11(), ".solongate");
11740
11785
  const envSel = process.env.SOLONGATE_AGENT_ID;
11741
11786
  if (envSel) {
11742
11787
  const f = resolve5(dir, ".policy-cache-" + envSel.replace(/[^a-zA-Z0-9_-]/g, "_") + ".json");
@@ -12051,8 +12096,8 @@ __export(logs_server_exports, {
12051
12096
  });
12052
12097
  import { createServer as createServer2 } from "http";
12053
12098
  import { readFileSync as readFileSync10, statSync as statSync5 } from "fs";
12054
- import { resolve as resolve6, join as join12, isAbsolute } from "path";
12055
- import { homedir as homedir11 } from "os";
12099
+ import { resolve as resolve6, join as join13, isAbsolute } from "path";
12100
+ import { homedir as homedir12 } from "os";
12056
12101
  import { readdirSync as readdirSync2 } from "fs";
12057
12102
  function allowedOrigins() {
12058
12103
  const base = [
@@ -12069,15 +12114,15 @@ function resolveLocalLogDir(rawPath) {
12069
12114
  const dir = String(rawPath || "").trim().replace(/[\\/]+$/, "");
12070
12115
  if (!dir) return null;
12071
12116
  if (isAbsolute(dir)) return dir;
12072
- return resolve6(homedir11(), ".solongate", "local-logs");
12117
+ return resolve6(homedir12(), ".solongate", "local-logs");
12073
12118
  }
12074
12119
  async function findLogDir() {
12075
- const base = resolve6(homedir11(), ".solongate");
12120
+ const base = resolve6(homedir12(), ".solongate");
12076
12121
  try {
12077
12122
  const files = readdirSync2(base).filter((f) => f.startsWith(".policy-cache-") && f.endsWith(".json"));
12078
12123
  for (const f of files) {
12079
12124
  try {
12080
- const c2 = JSON.parse(readFileSync10(join12(base, f), "utf-8"));
12125
+ const c2 = JSON.parse(readFileSync10(join13(base, f), "utf-8"));
12081
12126
  const p = c2?.security?.localLogs?.path;
12082
12127
  if (typeof p === "string" && p.trim()) return { dir: resolveLocalLogDir(p), configured: p };
12083
12128
  } catch {
@@ -12086,7 +12131,7 @@ async function findLogDir() {
12086
12131
  } catch {
12087
12132
  }
12088
12133
  try {
12089
- const cfgRaw = readFileSync10(join12(base, "cloud-guard.json"), "utf-8");
12134
+ const cfgRaw = readFileSync10(join13(base, "cloud-guard.json"), "utf-8");
12090
12135
  const { apiKey, apiUrl } = JSON.parse(cfgRaw);
12091
12136
  if (apiKey) {
12092
12137
  const url = `${apiUrl || "https://api.solongate.com"}/api/v1/policies/active`;
@@ -12115,7 +12160,7 @@ function setCors(req, res) {
12115
12160
  }
12116
12161
  function fileInfo(dir) {
12117
12162
  if (!dir) return { file: null, exists: false, size: 0, mtimeMs: 0 };
12118
- const file = join12(dir, LOG_FILENAME);
12163
+ const file = join13(dir, LOG_FILENAME);
12119
12164
  try {
12120
12165
  const st = statSync5(file);
12121
12166
  return { file, exists: true, size: st.size, mtimeMs: st.mtimeMs };
@@ -12565,8 +12610,8 @@ var init_inject = __esm({
12565
12610
 
12566
12611
  // src/create.ts
12567
12612
  var create_exports = {};
12568
- import { mkdirSync as mkdirSync7, writeFileSync as writeFileSync9, existsSync as existsSync9 } from "fs";
12569
- import { resolve as resolve8, join as join13 } from "path";
12613
+ import { mkdirSync as mkdirSync8, writeFileSync as writeFileSync9, existsSync as existsSync9 } from "fs";
12614
+ import { resolve as resolve8, join as join14 } from "path";
12570
12615
  import { execSync as execSync2 } from "child_process";
12571
12616
  function withSpinner(message, fn) {
12572
12617
  const frames = ["\u28FE", "\u28FD", "\u28FB", "\u28BF", "\u287F", "\u28DF", "\u28EF", "\u28F7"];
@@ -12646,7 +12691,7 @@ EXAMPLES
12646
12691
  }
12647
12692
  function createProject(dir, name, _policy) {
12648
12693
  writeFileSync9(
12649
- join13(dir, "package.json"),
12694
+ join14(dir, "package.json"),
12650
12695
  JSON.stringify(
12651
12696
  {
12652
12697
  name,
@@ -12676,7 +12721,7 @@ function createProject(dir, name, _policy) {
12676
12721
  ) + "\n"
12677
12722
  );
12678
12723
  writeFileSync9(
12679
- join13(dir, "tsconfig.json"),
12724
+ join14(dir, "tsconfig.json"),
12680
12725
  JSON.stringify(
12681
12726
  {
12682
12727
  compilerOptions: {
@@ -12696,9 +12741,9 @@ function createProject(dir, name, _policy) {
12696
12741
  2
12697
12742
  ) + "\n"
12698
12743
  );
12699
- mkdirSync7(join13(dir, "src"), { recursive: true });
12744
+ mkdirSync8(join14(dir, "src"), { recursive: true });
12700
12745
  writeFileSync9(
12701
- join13(dir, "src", "index.ts"),
12746
+ join14(dir, "src", "index.ts"),
12702
12747
  `#!/usr/bin/env node
12703
12748
 
12704
12749
  console.log = (...args: unknown[]) => {
@@ -12740,7 +12785,7 @@ console.log('Press Ctrl+C to stop.');
12740
12785
  `
12741
12786
  );
12742
12787
  writeFileSync9(
12743
- join13(dir, ".mcp.json"),
12788
+ join14(dir, ".mcp.json"),
12744
12789
  JSON.stringify(
12745
12790
  {
12746
12791
  mcpServers: {
@@ -12758,12 +12803,12 @@ console.log('Press Ctrl+C to stop.');
12758
12803
  ) + "\n"
12759
12804
  );
12760
12805
  writeFileSync9(
12761
- join13(dir, ".env"),
12806
+ join14(dir, ".env"),
12762
12807
  `SOLONGATE_API_KEY=sg_live_YOUR_KEY_HERE
12763
12808
  `
12764
12809
  );
12765
12810
  writeFileSync9(
12766
- join13(dir, ".gitignore"),
12811
+ join14(dir, ".gitignore"),
12767
12812
  `node_modules/
12768
12813
  dist/
12769
12814
  *.solongate-backup
@@ -12782,7 +12827,7 @@ async function main3() {
12782
12827
  process.exit(1);
12783
12828
  }
12784
12829
  withSpinner(`Setting up ${opts.name}...`, () => {
12785
- mkdirSync7(dir, { recursive: true });
12830
+ mkdirSync8(dir, { recursive: true });
12786
12831
  createProject(dir, opts.name, opts.policy);
12787
12832
  });
12788
12833
  if (!opts.noInstall) {
package/dist/tui/index.js CHANGED
@@ -5,6 +5,9 @@ var __export = (target, all) => {
5
5
  };
6
6
 
7
7
  // src/tui/index.tsx
8
+ import { appendFileSync, mkdirSync as mkdirSync4 } from "fs";
9
+ import { homedir as homedir6 } from "os";
10
+ import { join as join6 } from "path";
8
11
  import { render } from "ink";
9
12
 
10
13
  // src/tui/App.tsx
@@ -970,6 +973,7 @@ function LivePanel({ active: active2 }) {
970
973
  const inspectFromRef = useRef2("stream");
971
974
  const seenRef = useRef2(/* @__PURE__ */ new Set());
972
975
  const lastLocalTs = useRef2(0);
976
+ const localSeq = useRef2(0);
973
977
  const pausedUntil = useRef2(0);
974
978
  const sessRef = useRef2([]);
975
979
  const mergedRef = useRef2([]);
@@ -1015,7 +1019,7 @@ function LivePanel({ active: active2 }) {
1015
1019
  const at = Date.parse(j.ts ?? "");
1016
1020
  if (!Number.isFinite(at) || at <= lastLocalTs.current) continue;
1017
1021
  fresh.push({
1018
- id: "l:" + at + ":" + (j.tool ?? ""),
1022
+ id: "l:" + at + ":" + (j.tool ?? "") + ":" + localSeq.current++,
1019
1023
  at,
1020
1024
  tool: j.tool ?? "?",
1021
1025
  decision: j.decision ?? "ALLOW",
@@ -1138,20 +1142,45 @@ function LivePanel({ active: active2 }) {
1138
1142
  }, [active2, frozen]);
1139
1143
  const startRef = useRef2(Date.now());
1140
1144
  const lastNotifyAt = useRef2(0);
1145
+ const toastQueue = useRef2(/* @__PURE__ */ new Map());
1146
+ const toastTimer = useRef2(null);
1147
+ const TOAST_RANK = { "DLP hit": 0, "Call denied": 1, "Rate-limit burst": 2 };
1148
+ const flushToasts = useCallback2(function flush() {
1149
+ toastTimer.current = null;
1150
+ const q2 = toastQueue.current;
1151
+ if (!q2.size) return;
1152
+ const wait = 3e3 - (Date.now() - lastNotifyAt.current);
1153
+ if (wait > 0) {
1154
+ toastTimer.current = setTimeout(flush, wait);
1155
+ return;
1156
+ }
1157
+ lastNotifyAt.current = Date.now();
1158
+ const items = [...q2.entries()].sort((a, b) => (TOAST_RANK[a[0]] ?? 9) - (TOAST_RANK[b[0]] ?? 9));
1159
+ q2.clear();
1160
+ try {
1161
+ process.stdout.write("\x07");
1162
+ } catch {
1163
+ }
1164
+ const total = items.reduce((n, [, v]) => n + v.count, 0);
1165
+ if (items.length === 1 && total === 1) desktopNotify(items[0][0], items[0][1].msg);
1166
+ else desktopNotify("Security alerts", items.map(([t, v]) => `${v.count}\xD7 ${t}`).join(" \xB7 "));
1167
+ }, []);
1168
+ useEffect2(
1169
+ () => () => {
1170
+ if (toastTimer.current) clearTimeout(toastTimer.current);
1171
+ },
1172
+ []
1173
+ );
1141
1174
  const fireAlert = useCallback2(
1142
- (id, title, msg, level = "warn") => {
1175
+ (id, title, msg, level = "warn", desktop = true) => {
1143
1176
  pushLog(`${level === "bad" ? "\u26D4" : "\u23F8"} ${msg}`, level);
1144
1177
  setAlerts((a) => [...a.filter((x) => x.id !== id), { id, msg, level, until: Date.now() + 15e3 }].slice(-4));
1145
- if (!CONFIG.notifications) return;
1146
- if (Date.now() - lastNotifyAt.current < 3e3) return;
1147
- lastNotifyAt.current = Date.now();
1148
- try {
1149
- process.stdout.write("\x07");
1150
- } catch {
1151
- }
1152
- desktopNotify(title, msg);
1178
+ if (!desktop || !CONFIG.notifications) return;
1179
+ const cur = toastQueue.current.get(title);
1180
+ toastQueue.current.set(title, { count: (cur?.count ?? 0) + 1, msg: cur?.msg ?? msg });
1181
+ if (!toastTimer.current) toastTimer.current = setTimeout(flushToasts, 400);
1153
1182
  },
1154
- [pushLog]
1183
+ [pushLog, flushToasts]
1155
1184
  );
1156
1185
  useEffect2(() => {
1157
1186
  if (!active2) return;
@@ -1159,7 +1188,7 @@ function LivePanel({ active: active2 }) {
1159
1188
  const prev = prevStatus.current.get(r.id);
1160
1189
  if (prev && prev === "active" && r.status === "idle") {
1161
1190
  const name = r.isMe ? `${r.agent} (this machine)` : r.agent;
1162
- fireAlert("idle:" + r.id, "Agent idle", `${name} went IDLE, no tool call for 60s`, "warn");
1191
+ fireAlert("idle:" + r.id, "Agent idle", `${name} went IDLE, no tool call for 60s`, "warn", false);
1163
1192
  }
1164
1193
  prevStatus.current.set(r.id, r.status);
1165
1194
  }
@@ -3436,10 +3465,26 @@ async function launchTui() {
3436
3465
  return;
3437
3466
  }
3438
3467
  process.stdout.write("\x1B[?1049h\x1B[H");
3468
+ const debugLog = join6(homedir6(), ".solongate", "dataroom-debug.log");
3469
+ const saved = { log: console.log, warn: console.warn, error: console.error, info: console.info, debug: console.debug };
3470
+ const toFile = (level) => (...args) => {
3471
+ try {
3472
+ mkdirSync4(join6(homedir6(), ".solongate"), { recursive: true });
3473
+ appendFileSync(debugLog, `${(/* @__PURE__ */ new Date()).toISOString()} [${level}] ${args.map((a) => typeof a === "string" ? a : JSON.stringify(a)).join(" ")}
3474
+ `);
3475
+ } catch {
3476
+ }
3477
+ };
3478
+ console.log = toFile("log");
3479
+ console.warn = toFile("warn");
3480
+ console.error = toFile("error");
3481
+ console.info = toFile("info");
3482
+ console.debug = toFile("debug");
3439
3483
  try {
3440
- const { waitUntilExit } = render(/* @__PURE__ */ jsx10(App, {}));
3484
+ const { waitUntilExit } = render(/* @__PURE__ */ jsx10(App, {}), { patchConsole: false });
3441
3485
  await waitUntilExit();
3442
3486
  } finally {
3487
+ Object.assign(console, saved);
3443
3488
  process.stdout.write("\x1B[?1049l");
3444
3489
  }
3445
3490
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@solongate/proxy",
3
- "version": "0.81.42",
3
+ "version": "0.81.44",
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": {