@solongate/proxy 0.73.0 → 0.74.0

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.
Files changed (3) hide show
  1. package/dist/index.js +137 -154
  2. package/dist/tui/index.js +136 -153
  3. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -7034,7 +7034,7 @@ import { closeSync, openSync, readSync, statSync } from "fs";
7034
7034
  import { homedir as homedir3 } from "os";
7035
7035
  import { join as join5 } from "path";
7036
7036
  import { useCallback as useCallback2, useEffect as useEffect2, useRef as useRef2, useState as useState2 } from "react";
7037
- import { Fragment as Fragment2, jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
7037
+ import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
7038
7038
  function tailLines(file, maxBytes = 131072) {
7039
7039
  try {
7040
7040
  const size = statSync(file).size;
@@ -7059,7 +7059,6 @@ function cloudItem(e) {
7059
7059
  permission: (e.permission ?? "").slice(0, 4),
7060
7060
  detail: (e.arguments_summary ? JSON.stringify(e.arguments_summary) : e.reason ?? "").replace(/\s+/g, " "),
7061
7061
  dlp: !!e.dlp_matches?.length,
7062
- burst: !!e.rate_limit_burst,
7063
7062
  source: "cloud",
7064
7063
  session: e.session_id ?? void 0,
7065
7064
  agent: e.agent_name ?? void 0,
@@ -7115,12 +7114,6 @@ function PaneTitle({ label, extra, width: width2 }) {
7115
7114
  /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: tail + "\u2500".repeat(Math.max(0, width2 - used)) })
7116
7115
  ] });
7117
7116
  }
7118
- function Stat({ label, value, color }) {
7119
- return /* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", marginRight: 3, children: [
7120
- /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: label }),
7121
- /* @__PURE__ */ jsx2(Text2, { bold: true, color, children: String(value) })
7122
- ] });
7123
- }
7124
7117
  function StreamLine({ e, loc }) {
7125
7118
  return /* @__PURE__ */ jsxs2(Text2, { wrap: "truncate", children: [
7126
7119
  /* @__PURE__ */ jsxs2(Text2, { color: theme.dim, children: [
@@ -7136,7 +7129,6 @@ function StreamLine({ e, loc }) {
7136
7129
  /* @__PURE__ */ jsx2(Text2, { color: e.evalMs != null && e.evalMs > 500 ? theme.warn : theme.dim, children: (e.evalMs != null ? `${e.evalMs}ms` : "\u2014").padEnd(7) }),
7137
7130
  /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: truncate2(e.agent ?? "-", 11).padEnd(12) }),
7138
7131
  e.dlp ? /* @__PURE__ */ jsx2(Text2, { color: theme.bad, children: "DLP! " }) : null,
7139
- e.burst ? /* @__PURE__ */ jsx2(Text2, { color: theme.warn, children: "BURST " }) : null,
7140
7132
  e.rule && e.decision !== "ALLOW" ? /* @__PURE__ */ jsx2(Text2, { color: theme.bad, children: truncate2(e.rule, 14) + " " }) : null,
7141
7133
  /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: e.detail })
7142
7134
  ] });
@@ -7153,10 +7145,10 @@ function LivePanel({ active: active2 }) {
7153
7145
  const [filter, setFilter] = useState2("all");
7154
7146
  const [scroll, setScroll] = useState2(0);
7155
7147
  const [mode, setMode] = useState2("stream");
7156
- const [sessFilter, setSessFilter] = useState2("all");
7157
7148
  const [pickIdx, setPickIdx] = useState2(0);
7149
+ const [detail, setDetail] = useState2(null);
7158
7150
  const [detailCloud, setDetailCloud] = useState2([]);
7159
- const [tlScroll, setTlScroll] = useState2(0);
7151
+ const [detailScroll, setDetailScroll] = useState2(0);
7160
7152
  const seenRef = useRef2(/* @__PURE__ */ new Set());
7161
7153
  const lastLocalTs = useRef2(0);
7162
7154
  const pausedUntil = useRef2(0);
@@ -7201,7 +7193,6 @@ function LivePanel({ active: active2 }) {
7201
7193
  permission: (j.permission ?? "").slice(0, 4),
7202
7194
  detail: (j.arguments ? JSON.stringify(j.arguments) : j.reason ?? "").replace(/\s+/g, " "),
7203
7195
  dlp: !!j.dlp,
7204
- burst: !!j.rate_limit_burst,
7205
7196
  source: "local",
7206
7197
  session: j.session_id,
7207
7198
  agent: j.agent_name,
@@ -7306,6 +7297,19 @@ function LivePanel({ active: active2 }) {
7306
7297
  usePoll(() => {
7307
7298
  if (!paused()) guard.reload();
7308
7299
  }, 6e4, active2);
7300
+ useEffect2(() => {
7301
+ if (!detail) return;
7302
+ setDetailCloud([]);
7303
+ setDetailScroll(0);
7304
+ let alive = true;
7305
+ api.audit.list({ session_id: detail.id, limit: 100 }).then((r) => {
7306
+ if (alive) setDetailCloud(r.entries.map(cloudItem));
7307
+ }).catch(() => {
7308
+ });
7309
+ return () => {
7310
+ alive = false;
7311
+ };
7312
+ }, [detail]);
7309
7313
  const [tick, setTick] = useState2(0);
7310
7314
  useEffect2(() => {
7311
7315
  if (!active2 || frozen) return;
@@ -7325,37 +7329,6 @@ function LivePanel({ active: active2 }) {
7325
7329
  }
7326
7330
  const cloudDeduped = cloudBuf.filter((e) => !localKeys.has(`${e.tool}|${e.decision}|${Math.round(e.at / 6e4)}`));
7327
7331
  const mergedAll = [...cloudDeduped, ...localBuf].sort((a, b) => a.at - b.at);
7328
- const localSess = /* @__PURE__ */ new Map();
7329
- for (const e of localBuf) {
7330
- if (!e.session) continue;
7331
- const cur = localSess.get(e.session) ?? { agent: e.agent ?? "local agent", calls: 0, denies: 0, dlp: 0, lastAt: 0 };
7332
- cur.calls++;
7333
- if (e.decision !== "ALLOW") cur.denies++;
7334
- if (e.dlp) cur.dlp++;
7335
- cur.lastAt = Math.max(cur.lastAt, e.at);
7336
- if (e.agent) cur.agent = e.agent;
7337
- localSess.set(e.session, cur);
7338
- }
7339
- const combinedSessAll = [
7340
- ...[...localSess.entries()].sort((a, b) => b[1].lastAt - a[1].lastAt).map(([id, v]) => ({ source: "local", id, agent: v.agent, calls: v.calls, denies: v.denies, dlp: v.dlp, lastAt: v.lastAt, isMe: ring?.session === id })),
7341
- ...(sessions.data?.agents ?? []).filter((a) => !localSess.has(a.session_id)).sort((a, b) => Date.parse(b.last_seen_at) - Date.parse(a.last_seen_at)).map((a) => ({
7342
- source: "cloud",
7343
- id: a.session_id,
7344
- agent: a.agent_name ?? a.session_id,
7345
- calls: a.total_calls,
7346
- denies: a.denied_calls,
7347
- dlp: a.dlp_events,
7348
- lastAt: Date.parse(a.last_seen_at),
7349
- trust: a.trust_score,
7350
- isMe: false
7351
- }))
7352
- ];
7353
- const combinedSess = sessFilter === "all" ? combinedSessAll : combinedSessAll.filter((r) => sessStatus(r.lastAt, nowMs) === sessFilter);
7354
- const localSessIds = new Set(localSess.keys());
7355
- if (ring?.session) localSessIds.add(ring.session);
7356
- const isLoc = (e) => e.source === "local" || !!e.session && localSessIds.has(e.session);
7357
- const filtered = filter === "all" ? mergedAll : mergedAll.filter((e) => filter === "local" ? isLoc(e) : !isLoc(e));
7358
- const visibleDesc = filtered.slice(-STREAM_KEEP).reverse();
7359
7332
  const points = tsData?.timeseries ?? [];
7360
7333
  const nowSlot = Math.floor(nowMs / 1e4);
7361
7334
  const slotCounts = new Array(60).fill(0);
@@ -7387,6 +7360,36 @@ function LivePanel({ active: active2 }) {
7387
7360
  const latMed = sortedLat.length ? sortedLat[Math.floor(sortedLat.length / 2)] : 0;
7388
7361
  const latHotAt = Math.max(2e3, latMed * 2.5);
7389
7362
  const backingOff = Date.now() < pausedUntil.current;
7363
+ const sess = (sessions.data?.agents ?? []).slice().sort((a, b) => Date.parse(b.last_seen_at) - Date.parse(a.last_seen_at));
7364
+ const sessCounts = sessions.data?.counts;
7365
+ const localSess = /* @__PURE__ */ new Map();
7366
+ for (const e of localBuf) {
7367
+ if (!e.session) continue;
7368
+ const cur = localSess.get(e.session) ?? { agent: e.agent ?? "local agent", calls: 0, denies: 0, lastAt: 0 };
7369
+ cur.calls++;
7370
+ if (e.decision !== "ALLOW") cur.denies++;
7371
+ cur.lastAt = Math.max(cur.lastAt, e.at);
7372
+ if (e.agent) cur.agent = e.agent;
7373
+ localSess.set(e.session, cur);
7374
+ }
7375
+ const combinedSess = [
7376
+ ...[...localSess.entries()].sort((a, b) => b[1].lastAt - a[1].lastAt).map(([id, v]) => ({ source: "local", id, agent: v.agent, calls: v.calls, denies: v.denies, lastAt: v.lastAt, isMe: ring?.session === id })),
7377
+ ...sess.filter((a) => !localSess.has(a.session_id)).map((a) => ({
7378
+ source: "cloud",
7379
+ id: a.session_id,
7380
+ agent: a.agent_name ?? a.session_id,
7381
+ calls: a.total_calls,
7382
+ denies: a.denied_calls,
7383
+ lastAt: Date.parse(a.last_seen_at),
7384
+ trust: a.trust_score,
7385
+ isMe: false
7386
+ }))
7387
+ ];
7388
+ const localSessIds = new Set(localSess.keys());
7389
+ if (ring?.session) localSessIds.add(ring.session);
7390
+ const isLoc = (e) => e.source === "local" || !!e.session && localSessIds.has(e.session);
7391
+ const filtered = filter === "all" ? mergedAll : mergedAll.filter((e) => filter === "local" ? isLoc(e) : !isLoc(e));
7392
+ const visibleDesc = filtered.slice(-STREAM_KEEP).reverse();
7390
7393
  const chartH = rows >= 36 ? 6 : 4;
7391
7394
  const colH = rows >= 32 ? 7 : 5;
7392
7395
  const streamRows = Math.max(4, rows - 5 - (1 + chartH) - colH);
@@ -7394,25 +7397,14 @@ function LivePanel({ active: active2 }) {
7394
7397
  const leftW = Math.floor(innerW * 0.55);
7395
7398
  const rightW = innerW - leftW - 2;
7396
7399
  const colW = Math.max(20, Math.floor((innerW - 4) / 3));
7400
+ const pickable = combinedSess.slice(0, colH - 1);
7397
7401
  const maxScroll = Math.max(0, visibleDesc.length - streamRows);
7398
7402
  const clampedScroll = Math.min(scroll, maxScroll);
7399
7403
  const windowed = visibleDesc.slice(clampedScroll, clampedScroll + streamRows);
7400
- const selIdx = Math.min(pickIdx, Math.max(0, combinedSess.length - 1));
7401
- const selected = combinedSess[selIdx];
7402
- useEffect2(() => {
7403
- if (mode !== "sessions" || !selected?.id) return;
7404
- setTlScroll(0);
7405
- const t = setTimeout(() => {
7406
- api.audit.list({ session_id: selected.id, limit: 100 }).then((r) => {
7407
- if (!frozenRef.current) setDetailCloud(r.entries.map(cloudItem));
7408
- }).catch(() => setDetailCloud([]));
7409
- }, 400);
7410
- return () => clearTimeout(t);
7411
- }, [mode, selected?.id]);
7412
- const detailEntries = selected ? (() => {
7404
+ const detailEntries = detail ? (() => {
7413
7405
  const seen = /* @__PURE__ */ new Set();
7414
7406
  const all = [];
7415
- for (const e of [...detailCloud, ...mergedAll.filter((x) => x.session === selected.id)]) {
7407
+ for (const e of [...detailCloud, ...mergedAll.filter((x) => x.session === detail.id)]) {
7416
7408
  const key = `${e.at}:${e.tool}:${e.decision}`;
7417
7409
  if (seen.has(key)) continue;
7418
7410
  seen.add(key);
@@ -7422,30 +7414,40 @@ function LivePanel({ active: active2 }) {
7422
7414
  })() : [];
7423
7415
  useInput(
7424
7416
  (input, key) => {
7417
+ if (mode === "detail") {
7418
+ const maxD = Math.max(0, detailEntries.length - (rows - 10));
7419
+ if (key.leftArrow) {
7420
+ setMode("stream");
7421
+ setDetail(null);
7422
+ } else if (key.upArrow) setDetailScroll((n) => Math.max(0, n - 1));
7423
+ else if (key.downArrow) setDetailScroll((n) => Math.min(maxD, n + 1));
7424
+ else if (key.pageUp) setDetailScroll((n) => Math.max(0, n - 10));
7425
+ else if (key.pageDown) setDetailScroll((n) => Math.min(maxD, n + 10));
7426
+ return;
7427
+ }
7428
+ if (mode === "pick") {
7429
+ if (key.upArrow) setPickIdx((n) => Math.max(0, n - 1));
7430
+ else if (key.downArrow) setPickIdx((n) => Math.min(pickable.length - 1, n + 1));
7431
+ else if (key.return) {
7432
+ const row = pickable[pickIdx];
7433
+ if (row) {
7434
+ setDetail(row);
7435
+ setMode("detail");
7436
+ }
7437
+ } else if (input === "s" || key.leftArrow) setMode("stream");
7438
+ return;
7439
+ }
7425
7440
  if (input === " ") {
7426
7441
  setFrozen((f) => !f);
7427
7442
  return;
7428
7443
  }
7429
7444
  if (frozen) return;
7430
- if (mode === "sessions") {
7431
- if (key.leftArrow) {
7432
- setMode("stream");
7433
- setDetailCloud([]);
7434
- } else if (key.upArrow) setPickIdx((n) => Math.max(0, n - 1));
7435
- else if (key.downArrow) setPickIdx((n) => Math.min(combinedSess.length - 1, n + 1));
7436
- else if (input === "f") {
7437
- setSessFilter((f) => SESS_FILTERS[(SESS_FILTERS.indexOf(f) + 1) % SESS_FILTERS.length]);
7438
- setPickIdx(0);
7439
- } else if (key.pageUp) setTlScroll((n) => Math.max(0, n - 10));
7440
- else if (key.pageDown) setTlScroll((n) => n + 10);
7441
- return;
7442
- }
7443
7445
  if (input === "f") {
7444
7446
  setFilter((f) => FILTERS[(FILTERS.indexOf(f) + 1) % FILTERS.length]);
7445
7447
  setScroll(0);
7446
7448
  } else if (input === "s") {
7447
7449
  setPickIdx(0);
7448
- setMode("sessions");
7450
+ setMode("pick");
7449
7451
  } else if (key.downArrow) setScroll((n) => Math.min(maxScroll, n + 1));
7450
7452
  else if (key.upArrow) setScroll((n) => Math.max(0, n - 1));
7451
7453
  else if (key.pageDown) setScroll((n) => Math.min(maxScroll, n + streamRows));
@@ -7453,6 +7455,8 @@ function LivePanel({ active: active2 }) {
7453
7455
  },
7454
7456
  { isActive: active2 }
7455
7457
  );
7458
+ const sessionDot = (st) => st === "active" ? { ch: "\u25CF", color: theme.ok } : st === "idle" ? { ch: "\u25CC", color: theme.warn } : { ch: "\u25CB", color: theme.dim };
7459
+ void sessionDot;
7456
7460
  if (s === null && localOn === null) {
7457
7461
  return /* @__PURE__ */ jsxs2(Text2, { color: theme.dim, children: [
7458
7462
  spin,
@@ -7464,89 +7468,69 @@ function LivePanel({ active: active2 }) {
7464
7468
  /* @__PURE__ */ jsx2(Text2, { backgroundColor: BG, color: "#82AAF0", children: ` ${spin} up ${fmtUp(nowMs - startRef.current)} \xB7 ${hhmmss(nowMs)} \xB7 api ${latNow}ms ` }),
7465
7469
  frozen ? /* @__PURE__ */ jsx2(Text2, { backgroundColor: "#123d1f", color: "#7bd88f", bold: true, children: " \u23F5 COPY MODE \u2014 screen frozen, select & copy freely \xB7 space resume " }) : backingOff ? /* @__PURE__ */ jsx2(Text2, { backgroundColor: "#3d2a12", color: "#ffb454", bold: true, children: " RATE LIMITED \xB7 backing off " }) : lastDeny ? /* @__PURE__ */ jsx2(Text2, { backgroundColor: "#3d1220", color: "#ff6b6b", bold: true, children: ` \u26A0 ${hhmmss(lastDeny.at)} ${lastDeny.tool} DENIED ` }) : /* @__PURE__ */ jsx2(Text2, { backgroundColor: BG, color: "#4f6db8", children: " \u2713 clean " })
7466
7470
  ] });
7467
- if (mode === "sessions") {
7468
- const listW = Math.min(44, Math.floor(innerW * 0.38));
7469
- const detailW = innerW - listW - 2;
7470
- const bodyH = rows - 4;
7471
- const listRows = bodyH - 1;
7471
+ if (mode === "detail" && detail) {
7472
7472
  const d = detailEntries;
7473
7473
  const allow = d.filter((e) => e.decision === "ALLOW").length;
7474
7474
  const deny = d.length - allow;
7475
7475
  const dlpN = d.filter((e) => e.dlp).length;
7476
- const burstN = d.filter((e) => e.burst).length;
7477
7476
  const evals = d.filter((e) => e.evalMs != null).map((e) => e.evalMs);
7478
7477
  const evalAvg = evals.length ? Math.round(evals.reduce((a, b) => a + b, 0) / evals.length) : null;
7479
7478
  const tCounts = /* @__PURE__ */ new Map();
7480
- for (const e of d) tCounts.set(e.tool, (tCounts.get(e.tool) ?? 0) + 1);
7481
- const top = [...tCounts.entries()].sort((a, b) => b[1] - a[1]).slice(0, 5);
7479
+ const pCounts = /* @__PURE__ */ new Map();
7480
+ for (const e of d) {
7481
+ tCounts.set(e.tool, (tCounts.get(e.tool) ?? 0) + 1);
7482
+ if (e.permission) pCounts.set(e.permission, (pCounts.get(e.permission) ?? 0) + 1);
7483
+ }
7484
+ const top = [...tCounts.entries()].sort((a, b) => b[1] - a[1]).slice(0, 6);
7485
+ const perms = [...pCounts.entries()].sort((a, b) => b[1] - a[1]);
7482
7486
  const first = d[d.length - 1]?.at;
7483
7487
  const last = d[0]?.at;
7484
- const tlRows = Math.max(3, bodyH - 8);
7485
- const maxTl = Math.max(0, d.length - tlRows);
7486
- const tScroll = Math.min(tlScroll, maxTl);
7487
- const tl = d.slice(tScroll, tScroll + tlRows);
7488
+ const tlRows = Math.max(4, rows - 10);
7489
+ const maxD = Math.max(0, d.length - tlRows);
7490
+ const dScroll = Math.min(detailScroll, maxD);
7491
+ const tl = d.slice(dScroll, dScroll + tlRows);
7488
7492
  return /* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", paddingX: 1, children: [
7489
7493
  titleBar,
7490
- /* @__PURE__ */ jsx2(PaneTitle, { label: "SESSIONS", extra: `${combinedSess.length} shown \xB7 filter ${sessFilter} (f) \xB7 \u2191\u2193 select \xB7 PgUp/Dn timeline \xB7 \u2190 back`, width: innerW }),
7491
- /* @__PURE__ */ jsxs2(Box2, { height: bodyH, children: [
7492
- /* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", width: listW, marginRight: 2, overflow: "hidden", children: [
7493
- combinedSess.length === 0 ? /* @__PURE__ */ jsxs2(Text2, { color: theme.dim, children: [
7494
- "no sessions",
7495
- sessFilter !== "all" ? ` (${sessFilter})` : ""
7496
- ] }) : null,
7497
- combinedSess.slice(0, listRows).map((r, i) => {
7498
- const st = sessStatus(r.lastAt, nowMs);
7499
- const dot = st === "active" ? { ch: "\u25CF", color: theme.ok } : st === "idle" ? { ch: "\u25CC", color: theme.warn } : { ch: "\u25CB", color: theme.dim };
7500
- const sel = i === selIdx;
7501
- return /* @__PURE__ */ jsxs2(Text2, { wrap: "truncate", backgroundColor: sel ? "#1c2f63" : void 0, bold: sel, children: [
7502
- /* @__PURE__ */ jsxs2(Text2, { color: sel ? theme.accentBright : dot.color, children: [
7503
- sel ? "\u25B8" : dot.ch,
7504
- " "
7505
- ] }),
7506
- /* @__PURE__ */ jsx2(Text2, { color: r.source === "local" ? theme.ok : "#4f6db8", children: r.source === "local" ? "LOC " : "CLD " }),
7507
- /* @__PURE__ */ jsx2(Text2, { color: theme.accentBright, children: truncate2(r.isMe ? "this machine" : r.agent, 13).padEnd(14) }),
7508
- /* @__PURE__ */ jsxs2(Text2, { children: [
7509
- String(r.calls).padStart(4),
7510
- "c "
7511
- ] }),
7512
- /* @__PURE__ */ jsxs2(Text2, { color: r.denies ? theme.bad : theme.dim, children: [
7513
- String(r.denies).padStart(3),
7514
- "d "
7515
- ] }),
7516
- /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: ago(r.lastAt) })
7517
- ] }, r.id);
7518
- })
7519
- ] }),
7520
- /* @__PURE__ */ jsx2(Box2, { flexDirection: "column", width: detailW, overflow: "hidden", children: !selected ? /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: "select a session to see its run timeline" }) : /* @__PURE__ */ jsxs2(Fragment2, { children: [
7521
- /* @__PURE__ */ jsxs2(Text2, { wrap: "truncate", children: [
7522
- /* @__PURE__ */ jsx2(Text2, { color: sessStatus(selected.lastAt, nowMs) === "active" ? theme.ok : theme.dim, children: sessStatus(selected.lastAt, nowMs) === "active" ? "\u25CF " : "\u25CB " }),
7523
- /* @__PURE__ */ jsx2(Text2, { bold: true, children: truncate2(selected.isMe ? selected.agent + " (this machine)" : selected.agent, 30) }),
7524
- /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: " " + selected.id })
7525
- ] }),
7526
- /* @__PURE__ */ jsxs2(Box2, { marginTop: 1, children: [
7527
- /* @__PURE__ */ jsx2(Stat, { label: "Total", value: d.length }),
7528
- /* @__PURE__ */ jsx2(Stat, { label: "Allowed", value: allow, color: theme.ok }),
7529
- /* @__PURE__ */ jsx2(Stat, { label: "Denied", value: deny, color: deny ? theme.bad : void 0 }),
7530
- /* @__PURE__ */ jsx2(Stat, { label: "DLP", value: dlpN, color: dlpN ? theme.bad : void 0 }),
7531
- /* @__PURE__ */ jsx2(Stat, { label: "Rate limit", value: burstN, color: burstN ? theme.warn : void 0 }),
7532
- /* @__PURE__ */ jsx2(Stat, { label: "Avg eval", value: evalAvg != null ? `${evalAvg}ms` : "\u2014" }),
7533
- selected.trust != null ? /* @__PURE__ */ jsx2(Stat, { label: "Trust", value: `${selected.trust}/100`, color: theme.accent }) : null
7534
- ] }),
7535
- /* @__PURE__ */ jsx2(Text2, { wrap: "truncate", color: theme.dim, children: `first ${first ? `${hhmmss(first)} (${ago(first)} ago)` : "\u2014"} \xB7 last ${last ? `${hhmmss(last)} (${ago(last)} ago)` : "\u2014"} \xB7 tools ${top.map(([t, c2]) => `${t}\xD7${c2}`).join(" ") || "\u2014"}` }),
7536
- /* @__PURE__ */ jsx2(PaneTitle, { label: "TIMELINE", extra: `${d.length} events \xB7 newest first${tScroll ? ` \xB7 \u25BC${tScroll}` : ""}`, width: detailW }),
7537
- /* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", overflow: "hidden", flexGrow: 1, children: [
7538
- tl.length === 0 ? /* @__PURE__ */ jsxs2(Text2, { color: theme.dim, children: [
7539
- spin,
7540
- " loading history\u2026"
7541
- ] }) : null,
7542
- tl.map((e) => /* @__PURE__ */ jsx2(StreamLine, { e, loc: isLoc(e) }, e.id))
7543
- ] })
7544
- ] }) })
7494
+ /* @__PURE__ */ jsxs2(Text2, { wrap: "truncate", children: [
7495
+ /* @__PURE__ */ jsx2(Text2, { backgroundColor: BG, color: "#AAC8FA", bold: true, children: ` SESSION ${detail.id.slice(0, 8)} ` }),
7496
+ /* @__PURE__ */ jsx2(Text2, { color: detail.source === "local" ? theme.ok : "#4f6db8", children: " " + detail.source.toUpperCase() }),
7497
+ /* @__PURE__ */ jsx2(Text2, { bold: true, children: " " + truncate2(detail.isMe ? detail.agent + " (this machine)" : detail.agent, 34) }),
7498
+ detail.trust != null ? /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: ` trust ${detail.trust}/100` }) : null,
7499
+ /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: " \u2190 back" })
7500
+ ] }),
7501
+ /* @__PURE__ */ jsxs2(Text2, { wrap: "truncate", children: [
7502
+ /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: "\u2502 calls " }),
7503
+ /* @__PURE__ */ jsx2(Text2, { bold: true, children: d.length }),
7504
+ /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: " \u2502 allow " }),
7505
+ /* @__PURE__ */ jsx2(Text2, { color: theme.ok, children: allow }),
7506
+ /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: " \u2502 deny " }),
7507
+ /* @__PURE__ */ jsx2(Text2, { color: deny ? theme.bad : theme.dim, bold: deny > 0, children: deny }),
7508
+ /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: " \u2502 dlp " }),
7509
+ /* @__PURE__ */ jsx2(Text2, { color: dlpN ? theme.bad : theme.dim, children: dlpN }),
7510
+ /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: " \u2502 avg eval " }),
7511
+ /* @__PURE__ */ jsx2(Text2, { children: evalAvg != null ? `${evalAvg}ms` : "\u2014" }),
7512
+ /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: " \u2502 first " }),
7513
+ /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: first ? `${hhmmss(first)} (${ago(first)} ago)` : "\u2014" }),
7514
+ /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: " \u2502 last " }),
7515
+ /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: last ? `${hhmmss(last)} (${ago(last)} ago)` : "\u2014" }),
7516
+ /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: " \u2502" })
7517
+ ] }),
7518
+ /* @__PURE__ */ jsxs2(Text2, { wrap: "truncate", children: [
7519
+ /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: "\u2502 tools " }),
7520
+ /* @__PURE__ */ jsx2(Text2, { color: theme.accent, children: top.map(([t, c2]) => `${t}\xD7${c2}`).join(" ") || "\u2014" }),
7521
+ /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: " \u2502 perms " }),
7522
+ /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: perms.map(([p, c2]) => `${p}\xD7${c2}`).join(" ") || "\u2014" }),
7523
+ /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: " \u2502" })
7524
+ ] }),
7525
+ /* @__PURE__ */ jsx2(PaneTitle, { label: "TIMELINE", extra: `${d.length} calls \xB7 newest first${dScroll ? ` \xB7 \u25BC${dScroll} older` : ""} \xB7 \u2191\u2193 scroll \xB7 \u2190 back`, width: innerW }),
7526
+ /* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", height: tlRows, overflow: "hidden", children: [
7527
+ tl.length === 0 ? /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: detailCloud.length === 0 ? spin + " loading history\u2026" : "no entries" }) : null,
7528
+ tl.map((e) => /* @__PURE__ */ jsx2(StreamLine, { e, loc: isLoc(e) }, e.id))
7545
7529
  ] }),
7546
7530
  /* @__PURE__ */ jsxs2(Text2, { wrap: "truncate", children: [
7547
- /* @__PURE__ */ jsx2(Text2, { backgroundColor: BG, color: "#82AAF0", bold: true, children: " SESSIONS " }),
7548
- /* @__PURE__ */ jsx2(Text2, { backgroundColor: "#0b1530", color: "#4f6db8", children: ` ${combinedSess.length} sessions \xB7 space copy-mode ` }),
7549
- /* @__PURE__ */ jsx2(Text2, { backgroundColor: BG, color: "#82AAF0", children: " \u2191\u2193 select \xB7 f status \xB7 \u2190 back \xB7 esc menu " })
7531
+ /* @__PURE__ */ jsx2(Text2, { backgroundColor: BG, color: "#82AAF0", bold: true, children: " SESSION " }),
7532
+ /* @__PURE__ */ jsx2(Text2, { backgroundColor: "#0b1530", color: "#4f6db8", children: ` ${detail.id} ` }),
7533
+ /* @__PURE__ */ jsx2(Text2, { backgroundColor: BG, color: "#82AAF0", children: " \u2191\u2193 scroll \xB7 \u2190 back \xB7 esc menu " })
7550
7534
  ] })
7551
7535
  ] });
7552
7536
  }
@@ -7568,7 +7552,7 @@ function LivePanel({ active: active2 }) {
7568
7552
  /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: " \u2502 ghost " }),
7569
7553
  /* @__PURE__ */ jsx2(Text2, { color: gh?.mode === "on" ? theme.ok : theme.dim, children: gh?.mode ?? "?" }),
7570
7554
  /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: " \u2502 sess " }),
7571
- /* @__PURE__ */ jsx2(Text2, { color: theme.accent, children: String(combinedSessAll.length) }),
7555
+ /* @__PURE__ */ jsx2(Text2, { color: theme.accent, children: `${combinedSess.length} (${sessCounts?.active ?? 0} live)` }),
7572
7556
  /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: " \u2502 hooks " }),
7573
7557
  /* @__PURE__ */ jsx2(Text2, { color: guard.data?.up_to_date ? theme.ok : theme.warn, children: guard.data ? `v${guard.data.installed ?? "?"}${guard.data.up_to_date ? "" : "\u2192v" + guard.data.latest} ${guard.data.device_count}dev` : "\xB7" }),
7574
7558
  /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: " \u2502" })
@@ -7607,13 +7591,14 @@ function LivePanel({ active: active2 }) {
7607
7591
  ] })
7608
7592
  ] }),
7609
7593
  /* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", width: colW, marginRight: 2, overflow: "hidden", children: [
7610
- /* @__PURE__ */ jsx2(PaneTitle, { label: "SESSIONS", extra: "press s to inspect", width: colW }),
7611
- combinedSessAll.slice(0, colH - 1).map((r) => {
7612
- const st = sessStatus(r.lastAt, nowMs);
7613
- const dot = st === "active" ? { ch: "\u25CF", color: theme.ok } : st === "idle" ? { ch: "\u25CC", color: theme.warn } : { ch: "\u25CB", color: theme.dim };
7614
- return /* @__PURE__ */ jsxs2(Text2, { wrap: "truncate", bold: r.isMe, children: [
7615
- /* @__PURE__ */ jsxs2(Text2, { color: dot.color, children: [
7616
- dot.ch,
7594
+ /* @__PURE__ */ jsx2(PaneTitle, { label: "SESSIONS", extra: mode === "pick" ? "\u2191\u2193 pick \xB7 enter open" : `${combinedSess.length} \xB7 s = inspect`, width: colW }),
7595
+ pickable.length === 0 ? /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: "scanning\u2026" }) : null,
7596
+ pickable.map((r, i) => {
7597
+ const freshDot = nowMs - r.lastAt < 9e4;
7598
+ const sel = mode === "pick" && i === pickIdx;
7599
+ return /* @__PURE__ */ jsxs2(Text2, { wrap: "truncate", backgroundColor: sel ? "#1c2f63" : void 0, bold: sel || r.isMe, children: [
7600
+ /* @__PURE__ */ jsxs2(Text2, { color: sel ? theme.accentBright : freshDot ? theme.ok : theme.dim, children: [
7601
+ sel ? "\u25B8" : freshDot ? "\u25CF" : "\u25CB",
7617
7602
  " "
7618
7603
  ] }),
7619
7604
  /* @__PURE__ */ jsx2(Text2, { color: r.source === "local" ? theme.ok : "#4f6db8", children: r.source === "local" ? "LOC " : "CLD " }),
@@ -7626,7 +7611,7 @@ function LivePanel({ active: active2 }) {
7626
7611
  String(r.denies).padStart(3),
7627
7612
  "d "
7628
7613
  ] }),
7629
- /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: ago(r.lastAt) })
7614
+ /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: (r.trust != null ? `t${r.trust} ` : "") + ago(r.lastAt) })
7630
7615
  ] }, r.id);
7631
7616
  })
7632
7617
  ] }),
@@ -7663,12 +7648,12 @@ function LivePanel({ active: active2 }) {
7663
7648
  ] }),
7664
7649
  /* @__PURE__ */ jsxs2(Text2, { wrap: "truncate", children: [
7665
7650
  /* @__PURE__ */ jsx2(Text2, { backgroundColor: BG, color: "#82AAF0", bold: true, children: " LIVE " }),
7666
- /* @__PURE__ */ jsx2(Text2, { backgroundColor: "#0b1530", color: "#4f6db8", children: ` local ${localOn ? "on" : "off"} \xB7 ${localBuf.length} loc/${cloudDeduped.length} cld \xB7 top ${topTools.map(([t, c2]) => `${t}\xD7${c2}`).join(" ") || "\u2014"} ` }),
7651
+ /* @__PURE__ */ jsx2(Text2, { backgroundColor: "#0b1530", color: "#4f6db8", children: ` local ${localOn ? "on" : "off"} \xB7 ${localBuf.length} loc/${cloudBuf.length} cld \xB7 top ${topTools.map(([t, c2]) => `${t}\xD7${c2}`).join(" ") || "\u2014"} ` }),
7667
7652
  /* @__PURE__ */ jsx2(Text2, { backgroundColor: BG, color: "#82AAF0", children: " space copy-mode \xB7 f filter \xB7 s sessions \xB7 esc menu \xB7 q quit " })
7668
7653
  ] })
7669
7654
  ] });
7670
7655
  }
7671
- var SPIN, BG, DIM_FLOOR, LOCAL_LOG, RING, STREAM_KEEP, hhmmss, fmtUp, FILTERS, SESS_FILTERS, sessStatus;
7656
+ var SPIN, BG, DIM_FLOOR, LOCAL_LOG, RING, STREAM_KEEP, hhmmss, fmtUp, FILTERS;
7672
7657
  var init_Live = __esm({
7673
7658
  "src/tui/panels/Live.tsx"() {
7674
7659
  "use strict";
@@ -7691,8 +7676,6 @@ var init_Live = __esm({
7691
7676
  return `${p(Math.floor(s / 3600))}:${p(Math.floor(s % 3600 / 60))}:${p(s % 60)}`;
7692
7677
  };
7693
7678
  FILTERS = ["all", "local", "cloud"];
7694
- SESS_FILTERS = ["all", "active", "idle", "ended"];
7695
- sessStatus = (lastAt, now) => now - lastAt < 9e4 ? "active" : now - lastAt < 3e5 ? "idle" : "ended";
7696
7679
  }
7697
7680
  });
7698
7681
 
package/dist/tui/index.js CHANGED
@@ -443,7 +443,7 @@ function usePoll(reload, intervalMs, enabled = true) {
443
443
  }
444
444
 
445
445
  // src/tui/panels/Live.tsx
446
- import { Fragment as Fragment2, jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
446
+ import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
447
447
  var SPIN = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
448
448
  var BG = "#12234f";
449
449
  var DIM_FLOOR = "#233457";
@@ -483,7 +483,6 @@ function cloudItem(e) {
483
483
  permission: (e.permission ?? "").slice(0, 4),
484
484
  detail: (e.arguments_summary ? JSON.stringify(e.arguments_summary) : e.reason ?? "").replace(/\s+/g, " "),
485
485
  dlp: !!e.dlp_matches?.length,
486
- burst: !!e.rate_limit_burst,
487
486
  source: "cloud",
488
487
  session: e.session_id ?? void 0,
489
488
  agent: e.agent_name ?? void 0,
@@ -539,12 +538,6 @@ function PaneTitle({ label, extra, width }) {
539
538
  /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: tail + "\u2500".repeat(Math.max(0, width - used)) })
540
539
  ] });
541
540
  }
542
- function Stat({ label, value, color }) {
543
- return /* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", marginRight: 3, children: [
544
- /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: label }),
545
- /* @__PURE__ */ jsx2(Text2, { bold: true, color, children: String(value) })
546
- ] });
547
- }
548
541
  function StreamLine({ e, loc }) {
549
542
  return /* @__PURE__ */ jsxs2(Text2, { wrap: "truncate", children: [
550
543
  /* @__PURE__ */ jsxs2(Text2, { color: theme.dim, children: [
@@ -560,14 +553,11 @@ function StreamLine({ e, loc }) {
560
553
  /* @__PURE__ */ jsx2(Text2, { color: e.evalMs != null && e.evalMs > 500 ? theme.warn : theme.dim, children: (e.evalMs != null ? `${e.evalMs}ms` : "\u2014").padEnd(7) }),
561
554
  /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: truncate(e.agent ?? "-", 11).padEnd(12) }),
562
555
  e.dlp ? /* @__PURE__ */ jsx2(Text2, { color: theme.bad, children: "DLP! " }) : null,
563
- e.burst ? /* @__PURE__ */ jsx2(Text2, { color: theme.warn, children: "BURST " }) : null,
564
556
  e.rule && e.decision !== "ALLOW" ? /* @__PURE__ */ jsx2(Text2, { color: theme.bad, children: truncate(e.rule, 14) + " " }) : null,
565
557
  /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: e.detail })
566
558
  ] });
567
559
  }
568
560
  var FILTERS = ["all", "local", "cloud"];
569
- var SESS_FILTERS = ["all", "active", "idle", "ended"];
570
- var sessStatus = (lastAt, now) => now - lastAt < 9e4 ? "active" : now - lastAt < 3e5 ? "idle" : "ended";
571
561
  function LivePanel({ active: active2 }) {
572
562
  const [s, setS] = useState2(null);
573
563
  const [tsData, setTsData] = useState2(null);
@@ -580,10 +570,10 @@ function LivePanel({ active: active2 }) {
580
570
  const [filter, setFilter] = useState2("all");
581
571
  const [scroll, setScroll] = useState2(0);
582
572
  const [mode, setMode] = useState2("stream");
583
- const [sessFilter, setSessFilter] = useState2("all");
584
573
  const [pickIdx, setPickIdx] = useState2(0);
574
+ const [detail, setDetail] = useState2(null);
585
575
  const [detailCloud, setDetailCloud] = useState2([]);
586
- const [tlScroll, setTlScroll] = useState2(0);
576
+ const [detailScroll, setDetailScroll] = useState2(0);
587
577
  const seenRef = useRef2(/* @__PURE__ */ new Set());
588
578
  const lastLocalTs = useRef2(0);
589
579
  const pausedUntil = useRef2(0);
@@ -628,7 +618,6 @@ function LivePanel({ active: active2 }) {
628
618
  permission: (j.permission ?? "").slice(0, 4),
629
619
  detail: (j.arguments ? JSON.stringify(j.arguments) : j.reason ?? "").replace(/\s+/g, " "),
630
620
  dlp: !!j.dlp,
631
- burst: !!j.rate_limit_burst,
632
621
  source: "local",
633
622
  session: j.session_id,
634
623
  agent: j.agent_name,
@@ -733,6 +722,19 @@ function LivePanel({ active: active2 }) {
733
722
  usePoll(() => {
734
723
  if (!paused()) guard.reload();
735
724
  }, 6e4, active2);
725
+ useEffect2(() => {
726
+ if (!detail) return;
727
+ setDetailCloud([]);
728
+ setDetailScroll(0);
729
+ let alive = true;
730
+ api.audit.list({ session_id: detail.id, limit: 100 }).then((r) => {
731
+ if (alive) setDetailCloud(r.entries.map(cloudItem));
732
+ }).catch(() => {
733
+ });
734
+ return () => {
735
+ alive = false;
736
+ };
737
+ }, [detail]);
736
738
  const [tick, setTick] = useState2(0);
737
739
  useEffect2(() => {
738
740
  if (!active2 || frozen) return;
@@ -752,37 +754,6 @@ function LivePanel({ active: active2 }) {
752
754
  }
753
755
  const cloudDeduped = cloudBuf.filter((e) => !localKeys.has(`${e.tool}|${e.decision}|${Math.round(e.at / 6e4)}`));
754
756
  const mergedAll = [...cloudDeduped, ...localBuf].sort((a, b) => a.at - b.at);
755
- const localSess = /* @__PURE__ */ new Map();
756
- for (const e of localBuf) {
757
- if (!e.session) continue;
758
- const cur = localSess.get(e.session) ?? { agent: e.agent ?? "local agent", calls: 0, denies: 0, dlp: 0, lastAt: 0 };
759
- cur.calls++;
760
- if (e.decision !== "ALLOW") cur.denies++;
761
- if (e.dlp) cur.dlp++;
762
- cur.lastAt = Math.max(cur.lastAt, e.at);
763
- if (e.agent) cur.agent = e.agent;
764
- localSess.set(e.session, cur);
765
- }
766
- const combinedSessAll = [
767
- ...[...localSess.entries()].sort((a, b) => b[1].lastAt - a[1].lastAt).map(([id, v]) => ({ source: "local", id, agent: v.agent, calls: v.calls, denies: v.denies, dlp: v.dlp, lastAt: v.lastAt, isMe: ring?.session === id })),
768
- ...(sessions.data?.agents ?? []).filter((a) => !localSess.has(a.session_id)).sort((a, b) => Date.parse(b.last_seen_at) - Date.parse(a.last_seen_at)).map((a) => ({
769
- source: "cloud",
770
- id: a.session_id,
771
- agent: a.agent_name ?? a.session_id,
772
- calls: a.total_calls,
773
- denies: a.denied_calls,
774
- dlp: a.dlp_events,
775
- lastAt: Date.parse(a.last_seen_at),
776
- trust: a.trust_score,
777
- isMe: false
778
- }))
779
- ];
780
- const combinedSess = sessFilter === "all" ? combinedSessAll : combinedSessAll.filter((r) => sessStatus(r.lastAt, nowMs) === sessFilter);
781
- const localSessIds = new Set(localSess.keys());
782
- if (ring?.session) localSessIds.add(ring.session);
783
- const isLoc = (e) => e.source === "local" || !!e.session && localSessIds.has(e.session);
784
- const filtered = filter === "all" ? mergedAll : mergedAll.filter((e) => filter === "local" ? isLoc(e) : !isLoc(e));
785
- const visibleDesc = filtered.slice(-STREAM_KEEP).reverse();
786
757
  const points = tsData?.timeseries ?? [];
787
758
  const nowSlot = Math.floor(nowMs / 1e4);
788
759
  const slotCounts = new Array(60).fill(0);
@@ -814,6 +785,36 @@ function LivePanel({ active: active2 }) {
814
785
  const latMed = sortedLat.length ? sortedLat[Math.floor(sortedLat.length / 2)] : 0;
815
786
  const latHotAt = Math.max(2e3, latMed * 2.5);
816
787
  const backingOff = Date.now() < pausedUntil.current;
788
+ const sess = (sessions.data?.agents ?? []).slice().sort((a, b) => Date.parse(b.last_seen_at) - Date.parse(a.last_seen_at));
789
+ const sessCounts = sessions.data?.counts;
790
+ const localSess = /* @__PURE__ */ new Map();
791
+ for (const e of localBuf) {
792
+ if (!e.session) continue;
793
+ const cur = localSess.get(e.session) ?? { agent: e.agent ?? "local agent", calls: 0, denies: 0, lastAt: 0 };
794
+ cur.calls++;
795
+ if (e.decision !== "ALLOW") cur.denies++;
796
+ cur.lastAt = Math.max(cur.lastAt, e.at);
797
+ if (e.agent) cur.agent = e.agent;
798
+ localSess.set(e.session, cur);
799
+ }
800
+ const combinedSess = [
801
+ ...[...localSess.entries()].sort((a, b) => b[1].lastAt - a[1].lastAt).map(([id, v]) => ({ source: "local", id, agent: v.agent, calls: v.calls, denies: v.denies, lastAt: v.lastAt, isMe: ring?.session === id })),
802
+ ...sess.filter((a) => !localSess.has(a.session_id)).map((a) => ({
803
+ source: "cloud",
804
+ id: a.session_id,
805
+ agent: a.agent_name ?? a.session_id,
806
+ calls: a.total_calls,
807
+ denies: a.denied_calls,
808
+ lastAt: Date.parse(a.last_seen_at),
809
+ trust: a.trust_score,
810
+ isMe: false
811
+ }))
812
+ ];
813
+ const localSessIds = new Set(localSess.keys());
814
+ if (ring?.session) localSessIds.add(ring.session);
815
+ const isLoc = (e) => e.source === "local" || !!e.session && localSessIds.has(e.session);
816
+ const filtered = filter === "all" ? mergedAll : mergedAll.filter((e) => filter === "local" ? isLoc(e) : !isLoc(e));
817
+ const visibleDesc = filtered.slice(-STREAM_KEEP).reverse();
817
818
  const chartH = rows >= 36 ? 6 : 4;
818
819
  const colH = rows >= 32 ? 7 : 5;
819
820
  const streamRows = Math.max(4, rows - 5 - (1 + chartH) - colH);
@@ -821,25 +822,14 @@ function LivePanel({ active: active2 }) {
821
822
  const leftW = Math.floor(innerW * 0.55);
822
823
  const rightW = innerW - leftW - 2;
823
824
  const colW = Math.max(20, Math.floor((innerW - 4) / 3));
825
+ const pickable = combinedSess.slice(0, colH - 1);
824
826
  const maxScroll = Math.max(0, visibleDesc.length - streamRows);
825
827
  const clampedScroll = Math.min(scroll, maxScroll);
826
828
  const windowed = visibleDesc.slice(clampedScroll, clampedScroll + streamRows);
827
- const selIdx = Math.min(pickIdx, Math.max(0, combinedSess.length - 1));
828
- const selected = combinedSess[selIdx];
829
- useEffect2(() => {
830
- if (mode !== "sessions" || !selected?.id) return;
831
- setTlScroll(0);
832
- const t = setTimeout(() => {
833
- api.audit.list({ session_id: selected.id, limit: 100 }).then((r) => {
834
- if (!frozenRef.current) setDetailCloud(r.entries.map(cloudItem));
835
- }).catch(() => setDetailCloud([]));
836
- }, 400);
837
- return () => clearTimeout(t);
838
- }, [mode, selected?.id]);
839
- const detailEntries = selected ? (() => {
829
+ const detailEntries = detail ? (() => {
840
830
  const seen = /* @__PURE__ */ new Set();
841
831
  const all = [];
842
- for (const e of [...detailCloud, ...mergedAll.filter((x) => x.session === selected.id)]) {
832
+ for (const e of [...detailCloud, ...mergedAll.filter((x) => x.session === detail.id)]) {
843
833
  const key = `${e.at}:${e.tool}:${e.decision}`;
844
834
  if (seen.has(key)) continue;
845
835
  seen.add(key);
@@ -849,30 +839,40 @@ function LivePanel({ active: active2 }) {
849
839
  })() : [];
850
840
  useInput(
851
841
  (input, key) => {
842
+ if (mode === "detail") {
843
+ const maxD = Math.max(0, detailEntries.length - (rows - 10));
844
+ if (key.leftArrow) {
845
+ setMode("stream");
846
+ setDetail(null);
847
+ } else if (key.upArrow) setDetailScroll((n) => Math.max(0, n - 1));
848
+ else if (key.downArrow) setDetailScroll((n) => Math.min(maxD, n + 1));
849
+ else if (key.pageUp) setDetailScroll((n) => Math.max(0, n - 10));
850
+ else if (key.pageDown) setDetailScroll((n) => Math.min(maxD, n + 10));
851
+ return;
852
+ }
853
+ if (mode === "pick") {
854
+ if (key.upArrow) setPickIdx((n) => Math.max(0, n - 1));
855
+ else if (key.downArrow) setPickIdx((n) => Math.min(pickable.length - 1, n + 1));
856
+ else if (key.return) {
857
+ const row = pickable[pickIdx];
858
+ if (row) {
859
+ setDetail(row);
860
+ setMode("detail");
861
+ }
862
+ } else if (input === "s" || key.leftArrow) setMode("stream");
863
+ return;
864
+ }
852
865
  if (input === " ") {
853
866
  setFrozen((f) => !f);
854
867
  return;
855
868
  }
856
869
  if (frozen) return;
857
- if (mode === "sessions") {
858
- if (key.leftArrow) {
859
- setMode("stream");
860
- setDetailCloud([]);
861
- } else if (key.upArrow) setPickIdx((n) => Math.max(0, n - 1));
862
- else if (key.downArrow) setPickIdx((n) => Math.min(combinedSess.length - 1, n + 1));
863
- else if (input === "f") {
864
- setSessFilter((f) => SESS_FILTERS[(SESS_FILTERS.indexOf(f) + 1) % SESS_FILTERS.length]);
865
- setPickIdx(0);
866
- } else if (key.pageUp) setTlScroll((n) => Math.max(0, n - 10));
867
- else if (key.pageDown) setTlScroll((n) => n + 10);
868
- return;
869
- }
870
870
  if (input === "f") {
871
871
  setFilter((f) => FILTERS[(FILTERS.indexOf(f) + 1) % FILTERS.length]);
872
872
  setScroll(0);
873
873
  } else if (input === "s") {
874
874
  setPickIdx(0);
875
- setMode("sessions");
875
+ setMode("pick");
876
876
  } else if (key.downArrow) setScroll((n) => Math.min(maxScroll, n + 1));
877
877
  else if (key.upArrow) setScroll((n) => Math.max(0, n - 1));
878
878
  else if (key.pageDown) setScroll((n) => Math.min(maxScroll, n + streamRows));
@@ -880,6 +880,8 @@ function LivePanel({ active: active2 }) {
880
880
  },
881
881
  { isActive: active2 }
882
882
  );
883
+ const sessionDot = (st) => st === "active" ? { ch: "\u25CF", color: theme.ok } : st === "idle" ? { ch: "\u25CC", color: theme.warn } : { ch: "\u25CB", color: theme.dim };
884
+ void sessionDot;
883
885
  if (s === null && localOn === null) {
884
886
  return /* @__PURE__ */ jsxs2(Text2, { color: theme.dim, children: [
885
887
  spin,
@@ -891,89 +893,69 @@ function LivePanel({ active: active2 }) {
891
893
  /* @__PURE__ */ jsx2(Text2, { backgroundColor: BG, color: "#82AAF0", children: ` ${spin} up ${fmtUp(nowMs - startRef.current)} \xB7 ${hhmmss(nowMs)} \xB7 api ${latNow}ms ` }),
892
894
  frozen ? /* @__PURE__ */ jsx2(Text2, { backgroundColor: "#123d1f", color: "#7bd88f", bold: true, children: " \u23F5 COPY MODE \u2014 screen frozen, select & copy freely \xB7 space resume " }) : backingOff ? /* @__PURE__ */ jsx2(Text2, { backgroundColor: "#3d2a12", color: "#ffb454", bold: true, children: " RATE LIMITED \xB7 backing off " }) : lastDeny ? /* @__PURE__ */ jsx2(Text2, { backgroundColor: "#3d1220", color: "#ff6b6b", bold: true, children: ` \u26A0 ${hhmmss(lastDeny.at)} ${lastDeny.tool} DENIED ` }) : /* @__PURE__ */ jsx2(Text2, { backgroundColor: BG, color: "#4f6db8", children: " \u2713 clean " })
893
895
  ] });
894
- if (mode === "sessions") {
895
- const listW = Math.min(44, Math.floor(innerW * 0.38));
896
- const detailW = innerW - listW - 2;
897
- const bodyH = rows - 4;
898
- const listRows = bodyH - 1;
896
+ if (mode === "detail" && detail) {
899
897
  const d = detailEntries;
900
898
  const allow = d.filter((e) => e.decision === "ALLOW").length;
901
899
  const deny = d.length - allow;
902
900
  const dlpN = d.filter((e) => e.dlp).length;
903
- const burstN = d.filter((e) => e.burst).length;
904
901
  const evals = d.filter((e) => e.evalMs != null).map((e) => e.evalMs);
905
902
  const evalAvg = evals.length ? Math.round(evals.reduce((a, b) => a + b, 0) / evals.length) : null;
906
903
  const tCounts = /* @__PURE__ */ new Map();
907
- for (const e of d) tCounts.set(e.tool, (tCounts.get(e.tool) ?? 0) + 1);
908
- const top = [...tCounts.entries()].sort((a, b) => b[1] - a[1]).slice(0, 5);
904
+ const pCounts = /* @__PURE__ */ new Map();
905
+ for (const e of d) {
906
+ tCounts.set(e.tool, (tCounts.get(e.tool) ?? 0) + 1);
907
+ if (e.permission) pCounts.set(e.permission, (pCounts.get(e.permission) ?? 0) + 1);
908
+ }
909
+ const top = [...tCounts.entries()].sort((a, b) => b[1] - a[1]).slice(0, 6);
910
+ const perms = [...pCounts.entries()].sort((a, b) => b[1] - a[1]);
909
911
  const first = d[d.length - 1]?.at;
910
912
  const last = d[0]?.at;
911
- const tlRows = Math.max(3, bodyH - 8);
912
- const maxTl = Math.max(0, d.length - tlRows);
913
- const tScroll = Math.min(tlScroll, maxTl);
914
- const tl = d.slice(tScroll, tScroll + tlRows);
913
+ const tlRows = Math.max(4, rows - 10);
914
+ const maxD = Math.max(0, d.length - tlRows);
915
+ const dScroll = Math.min(detailScroll, maxD);
916
+ const tl = d.slice(dScroll, dScroll + tlRows);
915
917
  return /* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", paddingX: 1, children: [
916
918
  titleBar,
917
- /* @__PURE__ */ jsx2(PaneTitle, { label: "SESSIONS", extra: `${combinedSess.length} shown \xB7 filter ${sessFilter} (f) \xB7 \u2191\u2193 select \xB7 PgUp/Dn timeline \xB7 \u2190 back`, width: innerW }),
918
- /* @__PURE__ */ jsxs2(Box2, { height: bodyH, children: [
919
- /* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", width: listW, marginRight: 2, overflow: "hidden", children: [
920
- combinedSess.length === 0 ? /* @__PURE__ */ jsxs2(Text2, { color: theme.dim, children: [
921
- "no sessions",
922
- sessFilter !== "all" ? ` (${sessFilter})` : ""
923
- ] }) : null,
924
- combinedSess.slice(0, listRows).map((r, i) => {
925
- const st = sessStatus(r.lastAt, nowMs);
926
- const dot = st === "active" ? { ch: "\u25CF", color: theme.ok } : st === "idle" ? { ch: "\u25CC", color: theme.warn } : { ch: "\u25CB", color: theme.dim };
927
- const sel = i === selIdx;
928
- return /* @__PURE__ */ jsxs2(Text2, { wrap: "truncate", backgroundColor: sel ? "#1c2f63" : void 0, bold: sel, children: [
929
- /* @__PURE__ */ jsxs2(Text2, { color: sel ? theme.accentBright : dot.color, children: [
930
- sel ? "\u25B8" : dot.ch,
931
- " "
932
- ] }),
933
- /* @__PURE__ */ jsx2(Text2, { color: r.source === "local" ? theme.ok : "#4f6db8", children: r.source === "local" ? "LOC " : "CLD " }),
934
- /* @__PURE__ */ jsx2(Text2, { color: theme.accentBright, children: truncate(r.isMe ? "this machine" : r.agent, 13).padEnd(14) }),
935
- /* @__PURE__ */ jsxs2(Text2, { children: [
936
- String(r.calls).padStart(4),
937
- "c "
938
- ] }),
939
- /* @__PURE__ */ jsxs2(Text2, { color: r.denies ? theme.bad : theme.dim, children: [
940
- String(r.denies).padStart(3),
941
- "d "
942
- ] }),
943
- /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: ago(r.lastAt) })
944
- ] }, r.id);
945
- })
946
- ] }),
947
- /* @__PURE__ */ jsx2(Box2, { flexDirection: "column", width: detailW, overflow: "hidden", children: !selected ? /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: "select a session to see its run timeline" }) : /* @__PURE__ */ jsxs2(Fragment2, { children: [
948
- /* @__PURE__ */ jsxs2(Text2, { wrap: "truncate", children: [
949
- /* @__PURE__ */ jsx2(Text2, { color: sessStatus(selected.lastAt, nowMs) === "active" ? theme.ok : theme.dim, children: sessStatus(selected.lastAt, nowMs) === "active" ? "\u25CF " : "\u25CB " }),
950
- /* @__PURE__ */ jsx2(Text2, { bold: true, children: truncate(selected.isMe ? selected.agent + " (this machine)" : selected.agent, 30) }),
951
- /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: " " + selected.id })
952
- ] }),
953
- /* @__PURE__ */ jsxs2(Box2, { marginTop: 1, children: [
954
- /* @__PURE__ */ jsx2(Stat, { label: "Total", value: d.length }),
955
- /* @__PURE__ */ jsx2(Stat, { label: "Allowed", value: allow, color: theme.ok }),
956
- /* @__PURE__ */ jsx2(Stat, { label: "Denied", value: deny, color: deny ? theme.bad : void 0 }),
957
- /* @__PURE__ */ jsx2(Stat, { label: "DLP", value: dlpN, color: dlpN ? theme.bad : void 0 }),
958
- /* @__PURE__ */ jsx2(Stat, { label: "Rate limit", value: burstN, color: burstN ? theme.warn : void 0 }),
959
- /* @__PURE__ */ jsx2(Stat, { label: "Avg eval", value: evalAvg != null ? `${evalAvg}ms` : "\u2014" }),
960
- selected.trust != null ? /* @__PURE__ */ jsx2(Stat, { label: "Trust", value: `${selected.trust}/100`, color: theme.accent }) : null
961
- ] }),
962
- /* @__PURE__ */ jsx2(Text2, { wrap: "truncate", color: theme.dim, children: `first ${first ? `${hhmmss(first)} (${ago(first)} ago)` : "\u2014"} \xB7 last ${last ? `${hhmmss(last)} (${ago(last)} ago)` : "\u2014"} \xB7 tools ${top.map(([t, c2]) => `${t}\xD7${c2}`).join(" ") || "\u2014"}` }),
963
- /* @__PURE__ */ jsx2(PaneTitle, { label: "TIMELINE", extra: `${d.length} events \xB7 newest first${tScroll ? ` \xB7 \u25BC${tScroll}` : ""}`, width: detailW }),
964
- /* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", overflow: "hidden", flexGrow: 1, children: [
965
- tl.length === 0 ? /* @__PURE__ */ jsxs2(Text2, { color: theme.dim, children: [
966
- spin,
967
- " loading history\u2026"
968
- ] }) : null,
969
- tl.map((e) => /* @__PURE__ */ jsx2(StreamLine, { e, loc: isLoc(e) }, e.id))
970
- ] })
971
- ] }) })
919
+ /* @__PURE__ */ jsxs2(Text2, { wrap: "truncate", children: [
920
+ /* @__PURE__ */ jsx2(Text2, { backgroundColor: BG, color: "#AAC8FA", bold: true, children: ` SESSION ${detail.id.slice(0, 8)} ` }),
921
+ /* @__PURE__ */ jsx2(Text2, { color: detail.source === "local" ? theme.ok : "#4f6db8", children: " " + detail.source.toUpperCase() }),
922
+ /* @__PURE__ */ jsx2(Text2, { bold: true, children: " " + truncate(detail.isMe ? detail.agent + " (this machine)" : detail.agent, 34) }),
923
+ detail.trust != null ? /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: ` trust ${detail.trust}/100` }) : null,
924
+ /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: " \u2190 back" })
925
+ ] }),
926
+ /* @__PURE__ */ jsxs2(Text2, { wrap: "truncate", children: [
927
+ /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: "\u2502 calls " }),
928
+ /* @__PURE__ */ jsx2(Text2, { bold: true, children: d.length }),
929
+ /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: " \u2502 allow " }),
930
+ /* @__PURE__ */ jsx2(Text2, { color: theme.ok, children: allow }),
931
+ /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: " \u2502 deny " }),
932
+ /* @__PURE__ */ jsx2(Text2, { color: deny ? theme.bad : theme.dim, bold: deny > 0, children: deny }),
933
+ /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: " \u2502 dlp " }),
934
+ /* @__PURE__ */ jsx2(Text2, { color: dlpN ? theme.bad : theme.dim, children: dlpN }),
935
+ /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: " \u2502 avg eval " }),
936
+ /* @__PURE__ */ jsx2(Text2, { children: evalAvg != null ? `${evalAvg}ms` : "\u2014" }),
937
+ /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: " \u2502 first " }),
938
+ /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: first ? `${hhmmss(first)} (${ago(first)} ago)` : "\u2014" }),
939
+ /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: " \u2502 last " }),
940
+ /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: last ? `${hhmmss(last)} (${ago(last)} ago)` : "\u2014" }),
941
+ /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: " \u2502" })
942
+ ] }),
943
+ /* @__PURE__ */ jsxs2(Text2, { wrap: "truncate", children: [
944
+ /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: "\u2502 tools " }),
945
+ /* @__PURE__ */ jsx2(Text2, { color: theme.accent, children: top.map(([t, c2]) => `${t}\xD7${c2}`).join(" ") || "\u2014" }),
946
+ /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: " \u2502 perms " }),
947
+ /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: perms.map(([p, c2]) => `${p}\xD7${c2}`).join(" ") || "\u2014" }),
948
+ /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: " \u2502" })
949
+ ] }),
950
+ /* @__PURE__ */ jsx2(PaneTitle, { label: "TIMELINE", extra: `${d.length} calls \xB7 newest first${dScroll ? ` \xB7 \u25BC${dScroll} older` : ""} \xB7 \u2191\u2193 scroll \xB7 \u2190 back`, width: innerW }),
951
+ /* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", height: tlRows, overflow: "hidden", children: [
952
+ tl.length === 0 ? /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: detailCloud.length === 0 ? spin + " loading history\u2026" : "no entries" }) : null,
953
+ tl.map((e) => /* @__PURE__ */ jsx2(StreamLine, { e, loc: isLoc(e) }, e.id))
972
954
  ] }),
973
955
  /* @__PURE__ */ jsxs2(Text2, { wrap: "truncate", children: [
974
- /* @__PURE__ */ jsx2(Text2, { backgroundColor: BG, color: "#82AAF0", bold: true, children: " SESSIONS " }),
975
- /* @__PURE__ */ jsx2(Text2, { backgroundColor: "#0b1530", color: "#4f6db8", children: ` ${combinedSess.length} sessions \xB7 space copy-mode ` }),
976
- /* @__PURE__ */ jsx2(Text2, { backgroundColor: BG, color: "#82AAF0", children: " \u2191\u2193 select \xB7 f status \xB7 \u2190 back \xB7 esc menu " })
956
+ /* @__PURE__ */ jsx2(Text2, { backgroundColor: BG, color: "#82AAF0", bold: true, children: " SESSION " }),
957
+ /* @__PURE__ */ jsx2(Text2, { backgroundColor: "#0b1530", color: "#4f6db8", children: ` ${detail.id} ` }),
958
+ /* @__PURE__ */ jsx2(Text2, { backgroundColor: BG, color: "#82AAF0", children: " \u2191\u2193 scroll \xB7 \u2190 back \xB7 esc menu " })
977
959
  ] })
978
960
  ] });
979
961
  }
@@ -995,7 +977,7 @@ function LivePanel({ active: active2 }) {
995
977
  /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: " \u2502 ghost " }),
996
978
  /* @__PURE__ */ jsx2(Text2, { color: gh?.mode === "on" ? theme.ok : theme.dim, children: gh?.mode ?? "?" }),
997
979
  /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: " \u2502 sess " }),
998
- /* @__PURE__ */ jsx2(Text2, { color: theme.accent, children: String(combinedSessAll.length) }),
980
+ /* @__PURE__ */ jsx2(Text2, { color: theme.accent, children: `${combinedSess.length} (${sessCounts?.active ?? 0} live)` }),
999
981
  /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: " \u2502 hooks " }),
1000
982
  /* @__PURE__ */ jsx2(Text2, { color: guard.data?.up_to_date ? theme.ok : theme.warn, children: guard.data ? `v${guard.data.installed ?? "?"}${guard.data.up_to_date ? "" : "\u2192v" + guard.data.latest} ${guard.data.device_count}dev` : "\xB7" }),
1001
983
  /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: " \u2502" })
@@ -1034,13 +1016,14 @@ function LivePanel({ active: active2 }) {
1034
1016
  ] })
1035
1017
  ] }),
1036
1018
  /* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", width: colW, marginRight: 2, overflow: "hidden", children: [
1037
- /* @__PURE__ */ jsx2(PaneTitle, { label: "SESSIONS", extra: "press s to inspect", width: colW }),
1038
- combinedSessAll.slice(0, colH - 1).map((r) => {
1039
- const st = sessStatus(r.lastAt, nowMs);
1040
- const dot = st === "active" ? { ch: "\u25CF", color: theme.ok } : st === "idle" ? { ch: "\u25CC", color: theme.warn } : { ch: "\u25CB", color: theme.dim };
1041
- return /* @__PURE__ */ jsxs2(Text2, { wrap: "truncate", bold: r.isMe, children: [
1042
- /* @__PURE__ */ jsxs2(Text2, { color: dot.color, children: [
1043
- dot.ch,
1019
+ /* @__PURE__ */ jsx2(PaneTitle, { label: "SESSIONS", extra: mode === "pick" ? "\u2191\u2193 pick \xB7 enter open" : `${combinedSess.length} \xB7 s = inspect`, width: colW }),
1020
+ pickable.length === 0 ? /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: "scanning\u2026" }) : null,
1021
+ pickable.map((r, i) => {
1022
+ const freshDot = nowMs - r.lastAt < 9e4;
1023
+ const sel = mode === "pick" && i === pickIdx;
1024
+ return /* @__PURE__ */ jsxs2(Text2, { wrap: "truncate", backgroundColor: sel ? "#1c2f63" : void 0, bold: sel || r.isMe, children: [
1025
+ /* @__PURE__ */ jsxs2(Text2, { color: sel ? theme.accentBright : freshDot ? theme.ok : theme.dim, children: [
1026
+ sel ? "\u25B8" : freshDot ? "\u25CF" : "\u25CB",
1044
1027
  " "
1045
1028
  ] }),
1046
1029
  /* @__PURE__ */ jsx2(Text2, { color: r.source === "local" ? theme.ok : "#4f6db8", children: r.source === "local" ? "LOC " : "CLD " }),
@@ -1053,7 +1036,7 @@ function LivePanel({ active: active2 }) {
1053
1036
  String(r.denies).padStart(3),
1054
1037
  "d "
1055
1038
  ] }),
1056
- /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: ago(r.lastAt) })
1039
+ /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: (r.trust != null ? `t${r.trust} ` : "") + ago(r.lastAt) })
1057
1040
  ] }, r.id);
1058
1041
  })
1059
1042
  ] }),
@@ -1090,7 +1073,7 @@ function LivePanel({ active: active2 }) {
1090
1073
  ] }),
1091
1074
  /* @__PURE__ */ jsxs2(Text2, { wrap: "truncate", children: [
1092
1075
  /* @__PURE__ */ jsx2(Text2, { backgroundColor: BG, color: "#82AAF0", bold: true, children: " LIVE " }),
1093
- /* @__PURE__ */ jsx2(Text2, { backgroundColor: "#0b1530", color: "#4f6db8", children: ` local ${localOn ? "on" : "off"} \xB7 ${localBuf.length} loc/${cloudDeduped.length} cld \xB7 top ${topTools.map(([t, c2]) => `${t}\xD7${c2}`).join(" ") || "\u2014"} ` }),
1076
+ /* @__PURE__ */ jsx2(Text2, { backgroundColor: "#0b1530", color: "#4f6db8", children: ` local ${localOn ? "on" : "off"} \xB7 ${localBuf.length} loc/${cloudBuf.length} cld \xB7 top ${topTools.map(([t, c2]) => `${t}\xD7${c2}`).join(" ") || "\u2014"} ` }),
1094
1077
  /* @__PURE__ */ jsx2(Text2, { backgroundColor: BG, color: "#82AAF0", children: " space copy-mode \xB7 f filter \xB7 s sessions \xB7 esc menu \xB7 q quit " })
1095
1078
  ] })
1096
1079
  ] });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@solongate/proxy",
3
- "version": "0.73.0",
3
+ "version": "0.74.0",
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": {