@solongate/proxy 0.73.0 → 0.75.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 +184 -187
  2. package/dist/tui/index.js +183 -186
  3. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -7030,11 +7030,12 @@ var init_hooks = __esm({
7030
7030
 
7031
7031
  // src/tui/panels/Live.tsx
7032
7032
  import { Box as Box2, Text as Text2, useInput } from "ink";
7033
+ import TextInput from "ink-text-input";
7033
7034
  import { closeSync, openSync, readSync, statSync } from "fs";
7034
7035
  import { homedir as homedir3 } from "os";
7035
7036
  import { join as join5 } from "path";
7036
7037
  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";
7038
+ import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
7038
7039
  function tailLines(file, maxBytes = 131072) {
7039
7040
  try {
7040
7041
  const size = statSync(file).size;
@@ -7059,7 +7060,6 @@ function cloudItem(e) {
7059
7060
  permission: (e.permission ?? "").slice(0, 4),
7060
7061
  detail: (e.arguments_summary ? JSON.stringify(e.arguments_summary) : e.reason ?? "").replace(/\s+/g, " "),
7061
7062
  dlp: !!e.dlp_matches?.length,
7062
- burst: !!e.rate_limit_burst,
7063
7063
  source: "cloud",
7064
7064
  session: e.session_id ?? void 0,
7065
7065
  agent: e.agent_name ?? void 0,
@@ -7115,12 +7115,6 @@ function PaneTitle({ label, extra, width: width2 }) {
7115
7115
  /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: tail + "\u2500".repeat(Math.max(0, width2 - used)) })
7116
7116
  ] });
7117
7117
  }
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
7118
  function StreamLine({ e, loc }) {
7125
7119
  return /* @__PURE__ */ jsxs2(Text2, { wrap: "truncate", children: [
7126
7120
  /* @__PURE__ */ jsxs2(Text2, { color: theme.dim, children: [
@@ -7136,14 +7130,12 @@ function StreamLine({ e, loc }) {
7136
7130
  /* @__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
7131
  /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: truncate2(e.agent ?? "-", 11).padEnd(12) }),
7138
7132
  e.dlp ? /* @__PURE__ */ jsx2(Text2, { color: theme.bad, children: "DLP! " }) : null,
7139
- e.burst ? /* @__PURE__ */ jsx2(Text2, { color: theme.warn, children: "BURST " }) : null,
7140
7133
  e.rule && e.decision !== "ALLOW" ? /* @__PURE__ */ jsx2(Text2, { color: theme.bad, children: truncate2(e.rule, 14) + " " }) : null,
7141
7134
  /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: e.detail })
7142
7135
  ] });
7143
7136
  }
7144
7137
  function LivePanel({ active: active2 }) {
7145
7138
  const [s, setS] = useState2(null);
7146
- const [tsData, setTsData] = useState2(null);
7147
7139
  const [lat, setLat] = useState2([]);
7148
7140
  const [cloudBuf, setCloudBuf] = useState2([]);
7149
7141
  const [localBuf, setLocalBuf] = useState2([]);
@@ -7151,12 +7143,14 @@ function LivePanel({ active: active2 }) {
7151
7143
  const [ring, setRing] = useState2(null);
7152
7144
  const [log6, setLog] = useState2([]);
7153
7145
  const [filter, setFilter] = useState2("all");
7146
+ const [search, setSearch] = useState2("");
7147
+ const [editingSearch, setEditingSearch] = useState2(false);
7154
7148
  const [scroll, setScroll] = useState2(0);
7155
7149
  const [mode, setMode] = useState2("stream");
7156
- const [sessFilter, setSessFilter] = useState2("all");
7157
7150
  const [pickIdx, setPickIdx] = useState2(0);
7151
+ const [detail, setDetail] = useState2(null);
7158
7152
  const [detailCloud, setDetailCloud] = useState2([]);
7159
- const [tlScroll, setTlScroll] = useState2(0);
7153
+ const [detailScroll, setDetailScroll] = useState2(0);
7160
7154
  const seenRef = useRef2(/* @__PURE__ */ new Set());
7161
7155
  const lastLocalTs = useRef2(0);
7162
7156
  const pausedUntil = useRef2(0);
@@ -7201,7 +7195,6 @@ function LivePanel({ active: active2 }) {
7201
7195
  permission: (j.permission ?? "").slice(0, 4),
7202
7196
  detail: (j.arguments ? JSON.stringify(j.arguments) : j.reason ?? "").replace(/\s+/g, " "),
7203
7197
  dlp: !!j.dlp,
7204
- burst: !!j.rate_limit_burst,
7205
7198
  source: "local",
7206
7199
  session: j.session_id,
7207
7200
  agent: j.agent_name,
@@ -7271,41 +7264,42 @@ function LivePanel({ active: active2 }) {
7271
7264
  onApiError(e);
7272
7265
  }
7273
7266
  }, [onApiError]);
7274
- const pollSlow = useCallback2(async () => {
7275
- if (paused()) return;
7276
- try {
7277
- const td = await api.stats.timeseries({ period: "24h" });
7278
- if (!frozenRef.current) setTsData(td);
7279
- } catch (e) {
7280
- onApiError(e);
7281
- }
7282
- }, [onApiError]);
7283
7267
  useEffect2(() => {
7284
7268
  if (!active2) return;
7285
7269
  void pollFeed();
7286
7270
  void pollMedium();
7287
- void pollSlow();
7288
- const t1 = setInterval(() => void pollFeed(), 4e3);
7289
- const t2 = setInterval(() => void pollMedium(), 1e4);
7290
- const t3 = setInterval(() => void pollSlow(), 3e4);
7271
+ const t1 = setInterval(() => void pollFeed(), 3e3);
7272
+ const t2 = setInterval(() => void pollMedium(), 8e3);
7291
7273
  return () => {
7292
7274
  clearInterval(t1);
7293
7275
  clearInterval(t2);
7294
- clearInterval(t3);
7295
7276
  };
7296
- }, [active2, pollFeed, pollMedium, pollSlow]);
7277
+ }, [active2, pollFeed, pollMedium]);
7297
7278
  const sessions = useLoader(() => api.agents.live({ limit: 30, includeDeactivated: true }));
7298
7279
  const insights = useLoader(() => api.stats.securityInsights(7));
7299
7280
  const guard = useLoader(() => api.settings.getGuardStatus());
7300
7281
  usePoll(() => {
7301
7282
  if (!paused()) sessions.reload();
7302
- }, 1e4, active2);
7283
+ }, 8e3, active2);
7303
7284
  usePoll(() => {
7304
7285
  if (!paused()) insights.reload();
7305
- }, 3e4, active2);
7286
+ }, 2e4, active2);
7306
7287
  usePoll(() => {
7307
7288
  if (!paused()) guard.reload();
7308
7289
  }, 6e4, active2);
7290
+ useEffect2(() => {
7291
+ if (!detail) return;
7292
+ setDetailCloud([]);
7293
+ setDetailScroll(0);
7294
+ let alive = true;
7295
+ api.audit.list({ session_id: detail.id, limit: 100 }).then((r) => {
7296
+ if (alive) setDetailCloud(r.entries.map(cloudItem));
7297
+ }).catch(() => {
7298
+ });
7299
+ return () => {
7300
+ alive = false;
7301
+ };
7302
+ }, [detail]);
7309
7303
  const [tick, setTick] = useState2(0);
7310
7304
  useEffect2(() => {
7311
7305
  if (!active2 || frozen) return;
@@ -7325,68 +7319,65 @@ function LivePanel({ active: active2 }) {
7325
7319
  }
7326
7320
  const cloudDeduped = cloudBuf.filter((e) => !localKeys.has(`${e.tool}|${e.decision}|${Math.round(e.at / 6e4)}`));
7327
7321
  const mergedAll = [...cloudDeduped, ...localBuf].sort((a, b) => a.at - b.at);
7322
+ const nowSlot = Math.floor(nowMs / 1e4);
7323
+ const traffic = new Array(60).fill(0);
7324
+ const trafficHot = new Array(60).fill(false);
7325
+ for (const e of mergedAll) {
7326
+ const idx = 59 - (nowSlot - Math.floor(e.at / 1e4));
7327
+ if (idx >= 0 && idx < 60) {
7328
+ traffic[idx]++;
7329
+ if (e.decision !== "ALLOW") trafficHot[idx] = true;
7330
+ }
7331
+ }
7332
+ const trafficLabel = "calls/10s \xB7 last 10m \xB7 live";
7333
+ const rl = ins.layers?.rateLimit;
7334
+ const dl = ins.layers?.dlp;
7335
+ const gh = ins.layers?.ghost;
7336
+ const minuteNow = mergedAll.filter((e) => nowMs - e.at < 6e4).length;
7337
+ const dlpBars = (ins.dlpByPattern ?? []).slice(0, 2);
7338
+ const maxDlpBar = dlpBars[0]?.count ?? 1;
7339
+ const denialsAll = mergedAll.filter((e) => e.decision !== "ALLOW");
7340
+ const lastDeny = denialsAll[denialsAll.length - 1];
7341
+ const toolCounts = /* @__PURE__ */ new Map();
7342
+ for (const e of mergedAll) toolCounts.set(e.tool, (toolCounts.get(e.tool) ?? 0) + 1);
7343
+ const topTools = [...toolCounts.entries()].sort((a, b) => b[1] - a[1]).slice(0, 3);
7344
+ const latNow = lat[lat.length - 1] ?? 0;
7345
+ const sortedLat = [...lat].sort((a, b) => a - b);
7346
+ const latMed = sortedLat.length ? sortedLat[Math.floor(sortedLat.length / 2)] : 0;
7347
+ const latHotAt = Math.max(2e3, latMed * 2.5);
7348
+ const backingOff = Date.now() < pausedUntil.current;
7349
+ const sess = (sessions.data?.agents ?? []).slice().sort((a, b) => Date.parse(b.last_seen_at) - Date.parse(a.last_seen_at));
7350
+ const sessCounts = sessions.data?.counts;
7328
7351
  const localSess = /* @__PURE__ */ new Map();
7329
7352
  for (const e of localBuf) {
7330
7353
  if (!e.session) continue;
7331
- const cur = localSess.get(e.session) ?? { agent: e.agent ?? "local agent", calls: 0, denies: 0, dlp: 0, lastAt: 0 };
7354
+ const cur = localSess.get(e.session) ?? { agent: e.agent ?? "local agent", calls: 0, denies: 0, lastAt: 0 };
7332
7355
  cur.calls++;
7333
7356
  if (e.decision !== "ALLOW") cur.denies++;
7334
- if (e.dlp) cur.dlp++;
7335
7357
  cur.lastAt = Math.max(cur.lastAt, e.at);
7336
7358
  if (e.agent) cur.agent = e.agent;
7337
7359
  localSess.set(e.session, cur);
7338
7360
  }
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) => ({
7361
+ const combinedSess = [
7362
+ ...[...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 })),
7363
+ ...sess.filter((a) => !localSess.has(a.session_id)).map((a) => ({
7342
7364
  source: "cloud",
7343
7365
  id: a.session_id,
7344
7366
  agent: a.agent_name ?? a.session_id,
7345
7367
  calls: a.total_calls,
7346
7368
  denies: a.denied_calls,
7347
- dlp: a.dlp_events,
7348
7369
  lastAt: Date.parse(a.last_seen_at),
7349
7370
  trust: a.trust_score,
7350
7371
  isMe: false
7351
7372
  }))
7352
7373
  ];
7353
- const combinedSess = sessFilter === "all" ? combinedSessAll : combinedSessAll.filter((r) => sessStatus(r.lastAt, nowMs) === sessFilter);
7354
7374
  const localSessIds = new Set(localSess.keys());
7355
7375
  if (ring?.session) localSessIds.add(ring.session);
7356
7376
  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));
7377
+ const q = search.trim().toLowerCase();
7378
+ const matches = (e) => !q || `${e.tool} ${e.agent ?? ""} ${e.decision} ${e.permission} ${e.rule ?? ""} ${e.detail}`.toLowerCase().includes(q);
7379
+ const filtered = (filter === "all" ? mergedAll : mergedAll.filter((e) => filter === "local" ? isLoc(e) : !isLoc(e))).filter(matches);
7358
7380
  const visibleDesc = filtered.slice(-STREAM_KEEP).reverse();
7359
- const points = tsData?.timeseries ?? [];
7360
- const nowSlot = Math.floor(nowMs / 1e4);
7361
- const slotCounts = new Array(60).fill(0);
7362
- const slotHot = new Array(60).fill(false);
7363
- for (const e of mergedAll) {
7364
- const idx = 59 - (nowSlot - Math.floor(e.at / 1e4));
7365
- if (idx >= 0 && idx < 60) {
7366
- slotCounts[idx]++;
7367
- if (e.decision !== "ALLOW") slotHot[idx] = true;
7368
- }
7369
- }
7370
- const localTraffic = localOn === true && slotCounts.some((c2) => c2 > 0);
7371
- const traffic = localTraffic ? slotCounts : points.map((p) => p.total);
7372
- const trafficHot = localTraffic ? slotHot : points.map((p) => p.denied > 0);
7373
- const trafficLabel = localTraffic ? "calls/10s \xB7 last 10m" : "24h \xB7 cloud";
7374
- const rl = ins.layers?.rateLimit;
7375
- const dl = ins.layers?.dlp;
7376
- const gh = ins.layers?.ghost;
7377
- const minuteNow = mergedAll.filter((e) => nowMs - e.at < 6e4).length;
7378
- const dlpBars = (ins.dlpByPattern ?? []).slice(0, 2);
7379
- const maxDlpBar = dlpBars[0]?.count ?? 1;
7380
- const denialsAll = mergedAll.filter((e) => e.decision !== "ALLOW");
7381
- const lastDeny = denialsAll[denialsAll.length - 1];
7382
- const toolCounts = /* @__PURE__ */ new Map();
7383
- for (const e of mergedAll) toolCounts.set(e.tool, (toolCounts.get(e.tool) ?? 0) + 1);
7384
- const topTools = [...toolCounts.entries()].sort((a, b) => b[1] - a[1]).slice(0, 3);
7385
- const latNow = lat[lat.length - 1] ?? 0;
7386
- const sortedLat = [...lat].sort((a, b) => a - b);
7387
- const latMed = sortedLat.length ? sortedLat[Math.floor(sortedLat.length / 2)] : 0;
7388
- const latHotAt = Math.max(2e3, latMed * 2.5);
7389
- const backingOff = Date.now() < pausedUntil.current;
7390
7381
  const chartH = rows >= 36 ? 6 : 4;
7391
7382
  const colH = rows >= 32 ? 7 : 5;
7392
7383
  const streamRows = Math.max(4, rows - 5 - (1 + chartH) - colH);
@@ -7394,29 +7385,18 @@ function LivePanel({ active: active2 }) {
7394
7385
  const leftW = Math.floor(innerW * 0.55);
7395
7386
  const rightW = innerW - leftW - 2;
7396
7387
  const colW = Math.max(20, Math.floor((innerW - 4) / 3));
7388
+ const pickable = combinedSess.slice(0, colH - 1);
7397
7389
  const maxScroll = Math.max(0, visibleDesc.length - streamRows);
7398
7390
  const clampedScroll = Math.min(scroll, maxScroll);
7399
7391
  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 ? (() => {
7392
+ const detailEntries = detail ? (() => {
7413
7393
  const seen = /* @__PURE__ */ new Set();
7414
7394
  const all = [];
7415
- for (const e of [...detailCloud, ...mergedAll.filter((x) => x.session === selected.id)]) {
7395
+ for (const e of [...detailCloud, ...mergedAll.filter((x) => x.session === detail.id)]) {
7416
7396
  const key = `${e.at}:${e.tool}:${e.decision}`;
7417
7397
  if (seen.has(key)) continue;
7418
7398
  seen.add(key);
7419
- all.push(e);
7399
+ if (matches(e)) all.push(e);
7420
7400
  }
7421
7401
  return all.sort((a, b) => b.at - a.at);
7422
7402
  })() : [];
@@ -7427,17 +7407,31 @@ function LivePanel({ active: active2 }) {
7427
7407
  return;
7428
7408
  }
7429
7409
  if (frozen) return;
7430
- if (mode === "sessions") {
7410
+ if (input === "/" && mode !== "pick") {
7411
+ setEditingSearch(true);
7412
+ return;
7413
+ }
7414
+ if (mode === "detail") {
7415
+ const maxD = Math.max(0, detailEntries.length - (rows - 10));
7431
7416
  if (key.leftArrow) {
7432
7417
  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);
7418
+ setDetail(null);
7419
+ } else if (key.upArrow) setDetailScroll((n) => Math.max(0, n - 1));
7420
+ else if (key.downArrow) setDetailScroll((n) => Math.min(maxD, n + 1));
7421
+ else if (key.pageUp) setDetailScroll((n) => Math.max(0, n - 10));
7422
+ else if (key.pageDown) setDetailScroll((n) => Math.min(maxD, n + 10));
7423
+ return;
7424
+ }
7425
+ if (mode === "pick") {
7426
+ if (key.upArrow) setPickIdx((n) => Math.max(0, n - 1));
7427
+ else if (key.downArrow) setPickIdx((n) => Math.min(pickable.length - 1, n + 1));
7428
+ else if (key.return) {
7429
+ const row = pickable[pickIdx];
7430
+ if (row) {
7431
+ setDetail(row);
7432
+ setMode("detail");
7433
+ }
7434
+ } else if (input === "s" || key.leftArrow) setMode("stream");
7441
7435
  return;
7442
7436
  }
7443
7437
  if (input === "f") {
@@ -7445,14 +7439,36 @@ function LivePanel({ active: active2 }) {
7445
7439
  setScroll(0);
7446
7440
  } else if (input === "s") {
7447
7441
  setPickIdx(0);
7448
- setMode("sessions");
7442
+ setMode("pick");
7449
7443
  } else if (key.downArrow) setScroll((n) => Math.min(maxScroll, n + 1));
7450
7444
  else if (key.upArrow) setScroll((n) => Math.max(0, n - 1));
7451
7445
  else if (key.pageDown) setScroll((n) => Math.min(maxScroll, n + streamRows));
7452
7446
  else if (key.pageUp) setScroll((n) => Math.max(0, n - streamRows));
7453
7447
  },
7454
- { isActive: active2 }
7448
+ { isActive: active2 && !editingSearch }
7455
7449
  );
7450
+ const searchRow = editingSearch ? /* @__PURE__ */ jsxs2(Text2, { wrap: "truncate", children: [
7451
+ /* @__PURE__ */ jsx2(Text2, { color: theme.warn, children: "search: " }),
7452
+ /* @__PURE__ */ jsx2(
7453
+ TextInput,
7454
+ {
7455
+ value: search,
7456
+ onChange: (v) => {
7457
+ setSearch(v);
7458
+ setScroll(0);
7459
+ setDetailScroll(0);
7460
+ },
7461
+ onSubmit: () => setEditingSearch(false)
7462
+ }
7463
+ ),
7464
+ /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: " (enter done \xB7 empty = clear)" })
7465
+ ] }) : search ? /* @__PURE__ */ jsxs2(Text2, { wrap: "truncate", children: [
7466
+ /* @__PURE__ */ jsx2(Text2, { color: theme.warn, children: "search: " }),
7467
+ /* @__PURE__ */ jsx2(Text2, { color: theme.accentBright, bold: true, children: search }),
7468
+ /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: ` ${filtered.length} match \xB7 / edit` })
7469
+ ] }) : null;
7470
+ const sessionDot = (st) => st === "active" ? { ch: "\u25CF", color: theme.ok } : st === "idle" ? { ch: "\u25CC", color: theme.warn } : { ch: "\u25CB", color: theme.dim };
7471
+ void sessionDot;
7456
7472
  if (s === null && localOn === null) {
7457
7473
  return /* @__PURE__ */ jsxs2(Text2, { color: theme.dim, children: [
7458
7474
  spin,
@@ -7464,89 +7480,70 @@ function LivePanel({ active: active2 }) {
7464
7480
  /* @__PURE__ */ jsx2(Text2, { backgroundColor: BG, color: "#82AAF0", children: ` ${spin} up ${fmtUp(nowMs - startRef.current)} \xB7 ${hhmmss(nowMs)} \xB7 api ${latNow}ms ` }),
7465
7481
  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
7482
  ] });
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;
7483
+ if (mode === "detail" && detail) {
7472
7484
  const d = detailEntries;
7473
7485
  const allow = d.filter((e) => e.decision === "ALLOW").length;
7474
7486
  const deny = d.length - allow;
7475
7487
  const dlpN = d.filter((e) => e.dlp).length;
7476
- const burstN = d.filter((e) => e.burst).length;
7477
7488
  const evals = d.filter((e) => e.evalMs != null).map((e) => e.evalMs);
7478
7489
  const evalAvg = evals.length ? Math.round(evals.reduce((a, b) => a + b, 0) / evals.length) : null;
7479
7490
  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);
7491
+ const pCounts = /* @__PURE__ */ new Map();
7492
+ for (const e of d) {
7493
+ tCounts.set(e.tool, (tCounts.get(e.tool) ?? 0) + 1);
7494
+ if (e.permission) pCounts.set(e.permission, (pCounts.get(e.permission) ?? 0) + 1);
7495
+ }
7496
+ const top = [...tCounts.entries()].sort((a, b) => b[1] - a[1]).slice(0, 6);
7497
+ const perms = [...pCounts.entries()].sort((a, b) => b[1] - a[1]);
7482
7498
  const first = d[d.length - 1]?.at;
7483
7499
  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);
7500
+ const tlRows = Math.max(4, rows - 10);
7501
+ const maxD = Math.max(0, d.length - tlRows);
7502
+ const dScroll = Math.min(detailScroll, maxD);
7503
+ const tl = d.slice(dScroll, dScroll + tlRows);
7488
7504
  return /* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", paddingX: 1, children: [
7489
7505
  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
- ] }) })
7506
+ /* @__PURE__ */ jsxs2(Text2, { wrap: "truncate", children: [
7507
+ /* @__PURE__ */ jsx2(Text2, { backgroundColor: BG, color: "#AAC8FA", bold: true, children: ` SESSION ${detail.id.slice(0, 8)} ` }),
7508
+ /* @__PURE__ */ jsx2(Text2, { color: detail.source === "local" ? theme.ok : "#4f6db8", children: " " + detail.source.toUpperCase() }),
7509
+ /* @__PURE__ */ jsx2(Text2, { bold: true, children: " " + truncate2(detail.isMe ? detail.agent + " (this machine)" : detail.agent, 34) }),
7510
+ detail.trust != null ? /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: ` trust ${detail.trust}/100` }) : null,
7511
+ /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: " \u2190 back" })
7545
7512
  ] }),
7546
7513
  /* @__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 " })
7514
+ /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: "\u2502 calls " }),
7515
+ /* @__PURE__ */ jsx2(Text2, { bold: true, children: d.length }),
7516
+ /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: " \u2502 allow " }),
7517
+ /* @__PURE__ */ jsx2(Text2, { color: theme.ok, children: allow }),
7518
+ /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: " \u2502 deny " }),
7519
+ /* @__PURE__ */ jsx2(Text2, { color: deny ? theme.bad : theme.dim, bold: deny > 0, children: deny }),
7520
+ /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: " \u2502 dlp " }),
7521
+ /* @__PURE__ */ jsx2(Text2, { color: dlpN ? theme.bad : theme.dim, children: dlpN }),
7522
+ /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: " \u2502 avg eval " }),
7523
+ /* @__PURE__ */ jsx2(Text2, { children: evalAvg != null ? `${evalAvg}ms` : "\u2014" }),
7524
+ /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: " \u2502 first " }),
7525
+ /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: first ? `${hhmmss(first)} (${ago(first)} ago)` : "\u2014" }),
7526
+ /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: " \u2502 last " }),
7527
+ /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: last ? `${hhmmss(last)} (${ago(last)} ago)` : "\u2014" }),
7528
+ /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: " \u2502" })
7529
+ ] }),
7530
+ /* @__PURE__ */ jsxs2(Text2, { wrap: "truncate", children: [
7531
+ /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: "\u2502 tools " }),
7532
+ /* @__PURE__ */ jsx2(Text2, { color: theme.accent, children: top.map(([t, c2]) => `${t}\xD7${c2}`).join(" ") || "\u2014" }),
7533
+ /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: " \u2502 perms " }),
7534
+ /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: perms.map(([p, c2]) => `${p}\xD7${c2}`).join(" ") || "\u2014" }),
7535
+ /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: " \u2502" })
7536
+ ] }),
7537
+ /* @__PURE__ */ jsx2(PaneTitle, { label: "TIMELINE", extra: `${d.length} calls \xB7 newest first${dScroll ? ` \xB7 \u25BC${dScroll} older` : ""} \xB7 \u2191\u2193 scroll \xB7 / search \xB7 \u2190 back`, width: innerW }),
7538
+ searchRow,
7539
+ /* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", height: tlRows, overflow: "hidden", children: [
7540
+ tl.length === 0 ? /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: detailCloud.length === 0 ? spin + " loading history\u2026" : "no entries" }) : null,
7541
+ tl.map((e) => /* @__PURE__ */ jsx2(StreamLine, { e, loc: isLoc(e) }, e.id))
7542
+ ] }),
7543
+ /* @__PURE__ */ jsxs2(Text2, { wrap: "truncate", children: [
7544
+ /* @__PURE__ */ jsx2(Text2, { backgroundColor: BG, color: "#82AAF0", bold: true, children: " SESSION " }),
7545
+ /* @__PURE__ */ jsx2(Text2, { backgroundColor: "#0b1530", color: "#4f6db8", children: ` ${detail.id} ` }),
7546
+ /* @__PURE__ */ jsx2(Text2, { backgroundColor: BG, color: "#82AAF0", children: " \u2191\u2193 scroll \xB7 \u2190 back \xB7 esc menu " })
7550
7547
  ] })
7551
7548
  ] });
7552
7549
  }
@@ -7568,7 +7565,7 @@ function LivePanel({ active: active2 }) {
7568
7565
  /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: " \u2502 ghost " }),
7569
7566
  /* @__PURE__ */ jsx2(Text2, { color: gh?.mode === "on" ? theme.ok : theme.dim, children: gh?.mode ?? "?" }),
7570
7567
  /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: " \u2502 sess " }),
7571
- /* @__PURE__ */ jsx2(Text2, { color: theme.accent, children: String(combinedSessAll.length) }),
7568
+ /* @__PURE__ */ jsx2(Text2, { color: theme.accent, children: `${combinedSess.length} (${sessCounts?.active ?? 0} live)` }),
7572
7569
  /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: " \u2502 hooks " }),
7573
7570
  /* @__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
7571
  /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: " \u2502" })
@@ -7607,13 +7604,14 @@ function LivePanel({ active: active2 }) {
7607
7604
  ] })
7608
7605
  ] }),
7609
7606
  /* @__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,
7607
+ /* @__PURE__ */ jsx2(PaneTitle, { label: "SESSIONS", extra: mode === "pick" ? "\u2191\u2193 pick \xB7 enter open" : `${combinedSess.length} \xB7 s = inspect`, width: colW }),
7608
+ pickable.length === 0 ? /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: "scanning\u2026" }) : null,
7609
+ pickable.map((r, i) => {
7610
+ const freshDot = nowMs - r.lastAt < 9e4;
7611
+ const sel = mode === "pick" && i === pickIdx;
7612
+ return /* @__PURE__ */ jsxs2(Text2, { wrap: "truncate", backgroundColor: sel ? "#1c2f63" : void 0, bold: sel || r.isMe, children: [
7613
+ /* @__PURE__ */ jsxs2(Text2, { color: sel ? theme.accentBright : freshDot ? theme.ok : theme.dim, children: [
7614
+ sel ? "\u25B8" : freshDot ? "\u25CF" : "\u25CB",
7617
7615
  " "
7618
7616
  ] }),
7619
7617
  /* @__PURE__ */ jsx2(Text2, { color: r.source === "local" ? theme.ok : "#4f6db8", children: r.source === "local" ? "LOC " : "CLD " }),
@@ -7626,7 +7624,7 @@ function LivePanel({ active: active2 }) {
7626
7624
  String(r.denies).padStart(3),
7627
7625
  "d "
7628
7626
  ] }),
7629
- /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: ago(r.lastAt) })
7627
+ /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: (r.trust != null ? `t${r.trust} ` : "") + ago(r.lastAt) })
7630
7628
  ] }, r.id);
7631
7629
  })
7632
7630
  ] }),
@@ -7646,11 +7644,12 @@ function LivePanel({ active: active2 }) {
7646
7644
  PaneTitle,
7647
7645
  {
7648
7646
  label: "TOOL STREAM",
7649
- extra: `newest first \xB7 last ${STREAM_KEEP} \xB7 filter ${filter}${clampedScroll > 0 ? ` \xB7 \u25BC${clampedScroll} older` : ""} \xB7 f filter \xB7 \u2191\u2193 scroll \xB7 s sessions`,
7647
+ extra: `newest first \xB7 last ${STREAM_KEEP} \xB7 filter ${filter}${clampedScroll > 0 ? ` \xB7 \u25BC${clampedScroll} older` : ""} \xB7 f filter \xB7 / search \xB7 \u2191\u2193 scroll \xB7 s sessions`,
7650
7648
  width: innerW
7651
7649
  }
7652
7650
  ),
7653
7651
  /* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", height: streamRows, overflow: "hidden", children: [
7652
+ searchRow,
7654
7653
  localOn === false ? /* @__PURE__ */ jsxs2(Text2, { wrap: "truncate", children: [
7655
7654
  /* @__PURE__ */ jsx2(Text2, { color: theme.warn, children: "local logs off" }),
7656
7655
  /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: " \u2014 enable: dashboard \u2192 Settings \u2192 Local logs \xB7 hooks write ~/.solongate/local-logs \xB7 view: `solongate logs-server`" })
@@ -7663,12 +7662,12 @@ function LivePanel({ active: active2 }) {
7663
7662
  ] }),
7664
7663
  /* @__PURE__ */ jsxs2(Text2, { wrap: "truncate", children: [
7665
7664
  /* @__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"} ` }),
7667
- /* @__PURE__ */ jsx2(Text2, { backgroundColor: BG, color: "#82AAF0", children: " space copy-mode \xB7 f filter \xB7 s sessions \xB7 esc menu \xB7 q quit " })
7665
+ /* @__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"} ` }),
7666
+ /* @__PURE__ */ jsx2(Text2, { backgroundColor: BG, color: "#82AAF0", children: " space copy-mode \xB7 / search \xB7 f filter \xB7 s sessions \xB7 esc menu \xB7 q quit " })
7668
7667
  ] })
7669
7668
  ] });
7670
7669
  }
7671
- var SPIN, BG, DIM_FLOOR, LOCAL_LOG, RING, STREAM_KEEP, hhmmss, fmtUp, FILTERS, SESS_FILTERS, sessStatus;
7670
+ var SPIN, BG, DIM_FLOOR, LOCAL_LOG, RING, STREAM_KEEP, hhmmss, fmtUp, FILTERS;
7672
7671
  var init_Live = __esm({
7673
7672
  "src/tui/panels/Live.tsx"() {
7674
7673
  "use strict";
@@ -7691,14 +7690,12 @@ var init_Live = __esm({
7691
7690
  return `${p(Math.floor(s / 3600))}:${p(Math.floor(s % 3600 / 60))}:${p(s % 60)}`;
7692
7691
  };
7693
7692
  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
7693
  }
7697
7694
  });
7698
7695
 
7699
7696
  // src/tui/panels/Policies.tsx
7700
7697
  import { Box as Box3, Text as Text3, useInput as useInput2 } from "ink";
7701
- import TextInput from "ink-text-input";
7698
+ import TextInput2 from "ink-text-input";
7702
7699
  import { useEffect as useEffect3, useState as useState3 } from "react";
7703
7700
  import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
7704
7701
  function ruleSummary(r) {
@@ -7887,7 +7884,7 @@ function PoliciesPanel({ focused }) {
7887
7884
  return /* @__PURE__ */ jsxs3(Box3, { children: [
7888
7885
  /* @__PURE__ */ jsx3(Text3, { color: active2 ? theme.accentBright : void 0, children: (active2 ? "\u25B8 " : " ") + f.label.padEnd(13) }),
7889
7886
  active2 && editing && f.kind === "text" ? /* @__PURE__ */ jsx3(
7890
- TextInput,
7887
+ TextInput2,
7891
7888
  {
7892
7889
  value: editVal,
7893
7890
  onChange: setEditVal,
@@ -8083,7 +8080,7 @@ var init_RateLimit = __esm({
8083
8080
 
8084
8081
  // src/tui/panels/Dlp.tsx
8085
8082
  import { Box as Box5, Text as Text5, useInput as useInput4 } from "ink";
8086
- import TextInput2 from "ink-text-input";
8083
+ import TextInput3 from "ink-text-input";
8087
8084
  import { useEffect as useEffect5, useState as useState5 } from "react";
8088
8085
  import { jsx as jsx5, jsxs as jsxs5 } from "react/jsx-runtime";
8089
8086
  function DlpPanel({ focused }) {
@@ -8185,7 +8182,7 @@ function DlpPanel({ focused }) {
8185
8182
  adding ? /* @__PURE__ */ jsxs5(Box5, { marginTop: 1, children: [
8186
8183
  /* @__PURE__ */ jsx5(Text5, { color: theme.warn, children: adding === "name" ? "New pattern name: " : `Regex for "${newName}": ` }),
8187
8184
  adding === "name" ? /* @__PURE__ */ jsx5(
8188
- TextInput2,
8185
+ TextInput3,
8189
8186
  {
8190
8187
  value: newName,
8191
8188
  onChange: setNewName,
@@ -8196,7 +8193,7 @@ function DlpPanel({ focused }) {
8196
8193
  }
8197
8194
  }
8198
8195
  ) : /* @__PURE__ */ jsx5(
8199
- TextInput2,
8196
+ TextInput3,
8200
8197
  {
8201
8198
  value: newRe,
8202
8199
  onChange: setNewRe,
@@ -8309,7 +8306,7 @@ var init_Stats = __esm({
8309
8306
 
8310
8307
  // src/tui/panels/Audit.tsx
8311
8308
  import { Box as Box7, Text as Text7, useInput as useInput5 } from "ink";
8312
- import TextInput3 from "ink-text-input";
8309
+ import TextInput4 from "ink-text-input";
8313
8310
  import { useState as useState6 } from "react";
8314
8311
  import { jsx as jsx7, jsxs as jsxs7 } from "react/jsx-runtime";
8315
8312
  function AuditPanel({ active: active2, focused }) {
@@ -8426,7 +8423,7 @@ function AuditPanel({ active: active2, focused }) {
8426
8423
  ": "
8427
8424
  ] }),
8428
8425
  /* @__PURE__ */ jsx7(
8429
- TextInput3,
8426
+ TextInput4,
8430
8427
  {
8431
8428
  value: editing === "tool" ? tool : editing === "agent" ? agent : search,
8432
8429
  onChange: editing === "tool" ? setTool : editing === "agent" ? setAgent : setSearch,
package/dist/tui/index.js CHANGED
@@ -126,6 +126,7 @@ function KeyHints({ hints }) {
126
126
 
127
127
  // src/tui/panels/Live.tsx
128
128
  import { Box as Box2, Text as Text2, useInput } from "ink";
129
+ import TextInput from "ink-text-input";
129
130
  import { closeSync, openSync, readSync, statSync } from "fs";
130
131
  import { homedir as homedir2 } from "os";
131
132
  import { join as join2 } from "path";
@@ -443,7 +444,7 @@ function usePoll(reload, intervalMs, enabled = true) {
443
444
  }
444
445
 
445
446
  // src/tui/panels/Live.tsx
446
- import { Fragment as Fragment2, jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
447
+ import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
447
448
  var SPIN = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
448
449
  var BG = "#12234f";
449
450
  var DIM_FLOOR = "#233457";
@@ -483,7 +484,6 @@ function cloudItem(e) {
483
484
  permission: (e.permission ?? "").slice(0, 4),
484
485
  detail: (e.arguments_summary ? JSON.stringify(e.arguments_summary) : e.reason ?? "").replace(/\s+/g, " "),
485
486
  dlp: !!e.dlp_matches?.length,
486
- burst: !!e.rate_limit_burst,
487
487
  source: "cloud",
488
488
  session: e.session_id ?? void 0,
489
489
  agent: e.agent_name ?? void 0,
@@ -539,12 +539,6 @@ function PaneTitle({ label, extra, width }) {
539
539
  /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: tail + "\u2500".repeat(Math.max(0, width - used)) })
540
540
  ] });
541
541
  }
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
542
  function StreamLine({ e, loc }) {
549
543
  return /* @__PURE__ */ jsxs2(Text2, { wrap: "truncate", children: [
550
544
  /* @__PURE__ */ jsxs2(Text2, { color: theme.dim, children: [
@@ -560,17 +554,13 @@ function StreamLine({ e, loc }) {
560
554
  /* @__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
555
  /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: truncate(e.agent ?? "-", 11).padEnd(12) }),
562
556
  e.dlp ? /* @__PURE__ */ jsx2(Text2, { color: theme.bad, children: "DLP! " }) : null,
563
- e.burst ? /* @__PURE__ */ jsx2(Text2, { color: theme.warn, children: "BURST " }) : null,
564
557
  e.rule && e.decision !== "ALLOW" ? /* @__PURE__ */ jsx2(Text2, { color: theme.bad, children: truncate(e.rule, 14) + " " }) : null,
565
558
  /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: e.detail })
566
559
  ] });
567
560
  }
568
561
  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
562
  function LivePanel({ active: active2 }) {
572
563
  const [s, setS] = useState2(null);
573
- const [tsData, setTsData] = useState2(null);
574
564
  const [lat, setLat] = useState2([]);
575
565
  const [cloudBuf, setCloudBuf] = useState2([]);
576
566
  const [localBuf, setLocalBuf] = useState2([]);
@@ -578,12 +568,14 @@ function LivePanel({ active: active2 }) {
578
568
  const [ring, setRing] = useState2(null);
579
569
  const [log, setLog] = useState2([]);
580
570
  const [filter, setFilter] = useState2("all");
571
+ const [search, setSearch] = useState2("");
572
+ const [editingSearch, setEditingSearch] = useState2(false);
581
573
  const [scroll, setScroll] = useState2(0);
582
574
  const [mode, setMode] = useState2("stream");
583
- const [sessFilter, setSessFilter] = useState2("all");
584
575
  const [pickIdx, setPickIdx] = useState2(0);
576
+ const [detail, setDetail] = useState2(null);
585
577
  const [detailCloud, setDetailCloud] = useState2([]);
586
- const [tlScroll, setTlScroll] = useState2(0);
578
+ const [detailScroll, setDetailScroll] = useState2(0);
587
579
  const seenRef = useRef2(/* @__PURE__ */ new Set());
588
580
  const lastLocalTs = useRef2(0);
589
581
  const pausedUntil = useRef2(0);
@@ -628,7 +620,6 @@ function LivePanel({ active: active2 }) {
628
620
  permission: (j.permission ?? "").slice(0, 4),
629
621
  detail: (j.arguments ? JSON.stringify(j.arguments) : j.reason ?? "").replace(/\s+/g, " "),
630
622
  dlp: !!j.dlp,
631
- burst: !!j.rate_limit_burst,
632
623
  source: "local",
633
624
  session: j.session_id,
634
625
  agent: j.agent_name,
@@ -698,41 +689,42 @@ function LivePanel({ active: active2 }) {
698
689
  onApiError(e);
699
690
  }
700
691
  }, [onApiError]);
701
- const pollSlow = useCallback2(async () => {
702
- if (paused()) return;
703
- try {
704
- const td = await api.stats.timeseries({ period: "24h" });
705
- if (!frozenRef.current) setTsData(td);
706
- } catch (e) {
707
- onApiError(e);
708
- }
709
- }, [onApiError]);
710
692
  useEffect2(() => {
711
693
  if (!active2) return;
712
694
  void pollFeed();
713
695
  void pollMedium();
714
- void pollSlow();
715
- const t1 = setInterval(() => void pollFeed(), 4e3);
716
- const t2 = setInterval(() => void pollMedium(), 1e4);
717
- const t3 = setInterval(() => void pollSlow(), 3e4);
696
+ const t1 = setInterval(() => void pollFeed(), 3e3);
697
+ const t2 = setInterval(() => void pollMedium(), 8e3);
718
698
  return () => {
719
699
  clearInterval(t1);
720
700
  clearInterval(t2);
721
- clearInterval(t3);
722
701
  };
723
- }, [active2, pollFeed, pollMedium, pollSlow]);
702
+ }, [active2, pollFeed, pollMedium]);
724
703
  const sessions = useLoader(() => api.agents.live({ limit: 30, includeDeactivated: true }));
725
704
  const insights = useLoader(() => api.stats.securityInsights(7));
726
705
  const guard = useLoader(() => api.settings.getGuardStatus());
727
706
  usePoll(() => {
728
707
  if (!paused()) sessions.reload();
729
- }, 1e4, active2);
708
+ }, 8e3, active2);
730
709
  usePoll(() => {
731
710
  if (!paused()) insights.reload();
732
- }, 3e4, active2);
711
+ }, 2e4, active2);
733
712
  usePoll(() => {
734
713
  if (!paused()) guard.reload();
735
714
  }, 6e4, active2);
715
+ useEffect2(() => {
716
+ if (!detail) return;
717
+ setDetailCloud([]);
718
+ setDetailScroll(0);
719
+ let alive = true;
720
+ api.audit.list({ session_id: detail.id, limit: 100 }).then((r) => {
721
+ if (alive) setDetailCloud(r.entries.map(cloudItem));
722
+ }).catch(() => {
723
+ });
724
+ return () => {
725
+ alive = false;
726
+ };
727
+ }, [detail]);
736
728
  const [tick, setTick] = useState2(0);
737
729
  useEffect2(() => {
738
730
  if (!active2 || frozen) return;
@@ -752,68 +744,65 @@ function LivePanel({ active: active2 }) {
752
744
  }
753
745
  const cloudDeduped = cloudBuf.filter((e) => !localKeys.has(`${e.tool}|${e.decision}|${Math.round(e.at / 6e4)}`));
754
746
  const mergedAll = [...cloudDeduped, ...localBuf].sort((a, b) => a.at - b.at);
747
+ const nowSlot = Math.floor(nowMs / 1e4);
748
+ const traffic = new Array(60).fill(0);
749
+ const trafficHot = new Array(60).fill(false);
750
+ for (const e of mergedAll) {
751
+ const idx = 59 - (nowSlot - Math.floor(e.at / 1e4));
752
+ if (idx >= 0 && idx < 60) {
753
+ traffic[idx]++;
754
+ if (e.decision !== "ALLOW") trafficHot[idx] = true;
755
+ }
756
+ }
757
+ const trafficLabel = "calls/10s \xB7 last 10m \xB7 live";
758
+ const rl = ins.layers?.rateLimit;
759
+ const dl = ins.layers?.dlp;
760
+ const gh = ins.layers?.ghost;
761
+ const minuteNow = mergedAll.filter((e) => nowMs - e.at < 6e4).length;
762
+ const dlpBars = (ins.dlpByPattern ?? []).slice(0, 2);
763
+ const maxDlpBar = dlpBars[0]?.count ?? 1;
764
+ const denialsAll = mergedAll.filter((e) => e.decision !== "ALLOW");
765
+ const lastDeny = denialsAll[denialsAll.length - 1];
766
+ const toolCounts = /* @__PURE__ */ new Map();
767
+ for (const e of mergedAll) toolCounts.set(e.tool, (toolCounts.get(e.tool) ?? 0) + 1);
768
+ const topTools = [...toolCounts.entries()].sort((a, b) => b[1] - a[1]).slice(0, 3);
769
+ const latNow = lat[lat.length - 1] ?? 0;
770
+ const sortedLat = [...lat].sort((a, b) => a - b);
771
+ const latMed = sortedLat.length ? sortedLat[Math.floor(sortedLat.length / 2)] : 0;
772
+ const latHotAt = Math.max(2e3, latMed * 2.5);
773
+ const backingOff = Date.now() < pausedUntil.current;
774
+ const sess = (sessions.data?.agents ?? []).slice().sort((a, b) => Date.parse(b.last_seen_at) - Date.parse(a.last_seen_at));
775
+ const sessCounts = sessions.data?.counts;
755
776
  const localSess = /* @__PURE__ */ new Map();
756
777
  for (const e of localBuf) {
757
778
  if (!e.session) continue;
758
- const cur = localSess.get(e.session) ?? { agent: e.agent ?? "local agent", calls: 0, denies: 0, dlp: 0, lastAt: 0 };
779
+ const cur = localSess.get(e.session) ?? { agent: e.agent ?? "local agent", calls: 0, denies: 0, lastAt: 0 };
759
780
  cur.calls++;
760
781
  if (e.decision !== "ALLOW") cur.denies++;
761
- if (e.dlp) cur.dlp++;
762
782
  cur.lastAt = Math.max(cur.lastAt, e.at);
763
783
  if (e.agent) cur.agent = e.agent;
764
784
  localSess.set(e.session, cur);
765
785
  }
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) => ({
786
+ const combinedSess = [
787
+ ...[...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 })),
788
+ ...sess.filter((a) => !localSess.has(a.session_id)).map((a) => ({
769
789
  source: "cloud",
770
790
  id: a.session_id,
771
791
  agent: a.agent_name ?? a.session_id,
772
792
  calls: a.total_calls,
773
793
  denies: a.denied_calls,
774
- dlp: a.dlp_events,
775
794
  lastAt: Date.parse(a.last_seen_at),
776
795
  trust: a.trust_score,
777
796
  isMe: false
778
797
  }))
779
798
  ];
780
- const combinedSess = sessFilter === "all" ? combinedSessAll : combinedSessAll.filter((r) => sessStatus(r.lastAt, nowMs) === sessFilter);
781
799
  const localSessIds = new Set(localSess.keys());
782
800
  if (ring?.session) localSessIds.add(ring.session);
783
801
  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));
802
+ const q = search.trim().toLowerCase();
803
+ const matches = (e) => !q || `${e.tool} ${e.agent ?? ""} ${e.decision} ${e.permission} ${e.rule ?? ""} ${e.detail}`.toLowerCase().includes(q);
804
+ const filtered = (filter === "all" ? mergedAll : mergedAll.filter((e) => filter === "local" ? isLoc(e) : !isLoc(e))).filter(matches);
785
805
  const visibleDesc = filtered.slice(-STREAM_KEEP).reverse();
786
- const points = tsData?.timeseries ?? [];
787
- const nowSlot = Math.floor(nowMs / 1e4);
788
- const slotCounts = new Array(60).fill(0);
789
- const slotHot = new Array(60).fill(false);
790
- for (const e of mergedAll) {
791
- const idx = 59 - (nowSlot - Math.floor(e.at / 1e4));
792
- if (idx >= 0 && idx < 60) {
793
- slotCounts[idx]++;
794
- if (e.decision !== "ALLOW") slotHot[idx] = true;
795
- }
796
- }
797
- const localTraffic = localOn === true && slotCounts.some((c2) => c2 > 0);
798
- const traffic = localTraffic ? slotCounts : points.map((p) => p.total);
799
- const trafficHot = localTraffic ? slotHot : points.map((p) => p.denied > 0);
800
- const trafficLabel = localTraffic ? "calls/10s \xB7 last 10m" : "24h \xB7 cloud";
801
- const rl = ins.layers?.rateLimit;
802
- const dl = ins.layers?.dlp;
803
- const gh = ins.layers?.ghost;
804
- const minuteNow = mergedAll.filter((e) => nowMs - e.at < 6e4).length;
805
- const dlpBars = (ins.dlpByPattern ?? []).slice(0, 2);
806
- const maxDlpBar = dlpBars[0]?.count ?? 1;
807
- const denialsAll = mergedAll.filter((e) => e.decision !== "ALLOW");
808
- const lastDeny = denialsAll[denialsAll.length - 1];
809
- const toolCounts = /* @__PURE__ */ new Map();
810
- for (const e of mergedAll) toolCounts.set(e.tool, (toolCounts.get(e.tool) ?? 0) + 1);
811
- const topTools = [...toolCounts.entries()].sort((a, b) => b[1] - a[1]).slice(0, 3);
812
- const latNow = lat[lat.length - 1] ?? 0;
813
- const sortedLat = [...lat].sort((a, b) => a - b);
814
- const latMed = sortedLat.length ? sortedLat[Math.floor(sortedLat.length / 2)] : 0;
815
- const latHotAt = Math.max(2e3, latMed * 2.5);
816
- const backingOff = Date.now() < pausedUntil.current;
817
806
  const chartH = rows >= 36 ? 6 : 4;
818
807
  const colH = rows >= 32 ? 7 : 5;
819
808
  const streamRows = Math.max(4, rows - 5 - (1 + chartH) - colH);
@@ -821,29 +810,18 @@ function LivePanel({ active: active2 }) {
821
810
  const leftW = Math.floor(innerW * 0.55);
822
811
  const rightW = innerW - leftW - 2;
823
812
  const colW = Math.max(20, Math.floor((innerW - 4) / 3));
813
+ const pickable = combinedSess.slice(0, colH - 1);
824
814
  const maxScroll = Math.max(0, visibleDesc.length - streamRows);
825
815
  const clampedScroll = Math.min(scroll, maxScroll);
826
816
  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 ? (() => {
817
+ const detailEntries = detail ? (() => {
840
818
  const seen = /* @__PURE__ */ new Set();
841
819
  const all = [];
842
- for (const e of [...detailCloud, ...mergedAll.filter((x) => x.session === selected.id)]) {
820
+ for (const e of [...detailCloud, ...mergedAll.filter((x) => x.session === detail.id)]) {
843
821
  const key = `${e.at}:${e.tool}:${e.decision}`;
844
822
  if (seen.has(key)) continue;
845
823
  seen.add(key);
846
- all.push(e);
824
+ if (matches(e)) all.push(e);
847
825
  }
848
826
  return all.sort((a, b) => b.at - a.at);
849
827
  })() : [];
@@ -854,17 +832,31 @@ function LivePanel({ active: active2 }) {
854
832
  return;
855
833
  }
856
834
  if (frozen) return;
857
- if (mode === "sessions") {
835
+ if (input === "/" && mode !== "pick") {
836
+ setEditingSearch(true);
837
+ return;
838
+ }
839
+ if (mode === "detail") {
840
+ const maxD = Math.max(0, detailEntries.length - (rows - 10));
858
841
  if (key.leftArrow) {
859
842
  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);
843
+ setDetail(null);
844
+ } else if (key.upArrow) setDetailScroll((n) => Math.max(0, n - 1));
845
+ else if (key.downArrow) setDetailScroll((n) => Math.min(maxD, n + 1));
846
+ else if (key.pageUp) setDetailScroll((n) => Math.max(0, n - 10));
847
+ else if (key.pageDown) setDetailScroll((n) => Math.min(maxD, n + 10));
848
+ return;
849
+ }
850
+ if (mode === "pick") {
851
+ if (key.upArrow) setPickIdx((n) => Math.max(0, n - 1));
852
+ else if (key.downArrow) setPickIdx((n) => Math.min(pickable.length - 1, n + 1));
853
+ else if (key.return) {
854
+ const row = pickable[pickIdx];
855
+ if (row) {
856
+ setDetail(row);
857
+ setMode("detail");
858
+ }
859
+ } else if (input === "s" || key.leftArrow) setMode("stream");
868
860
  return;
869
861
  }
870
862
  if (input === "f") {
@@ -872,14 +864,36 @@ function LivePanel({ active: active2 }) {
872
864
  setScroll(0);
873
865
  } else if (input === "s") {
874
866
  setPickIdx(0);
875
- setMode("sessions");
867
+ setMode("pick");
876
868
  } else if (key.downArrow) setScroll((n) => Math.min(maxScroll, n + 1));
877
869
  else if (key.upArrow) setScroll((n) => Math.max(0, n - 1));
878
870
  else if (key.pageDown) setScroll((n) => Math.min(maxScroll, n + streamRows));
879
871
  else if (key.pageUp) setScroll((n) => Math.max(0, n - streamRows));
880
872
  },
881
- { isActive: active2 }
873
+ { isActive: active2 && !editingSearch }
882
874
  );
875
+ const searchRow = editingSearch ? /* @__PURE__ */ jsxs2(Text2, { wrap: "truncate", children: [
876
+ /* @__PURE__ */ jsx2(Text2, { color: theme.warn, children: "search: " }),
877
+ /* @__PURE__ */ jsx2(
878
+ TextInput,
879
+ {
880
+ value: search,
881
+ onChange: (v) => {
882
+ setSearch(v);
883
+ setScroll(0);
884
+ setDetailScroll(0);
885
+ },
886
+ onSubmit: () => setEditingSearch(false)
887
+ }
888
+ ),
889
+ /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: " (enter done \xB7 empty = clear)" })
890
+ ] }) : search ? /* @__PURE__ */ jsxs2(Text2, { wrap: "truncate", children: [
891
+ /* @__PURE__ */ jsx2(Text2, { color: theme.warn, children: "search: " }),
892
+ /* @__PURE__ */ jsx2(Text2, { color: theme.accentBright, bold: true, children: search }),
893
+ /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: ` ${filtered.length} match \xB7 / edit` })
894
+ ] }) : null;
895
+ const sessionDot = (st) => st === "active" ? { ch: "\u25CF", color: theme.ok } : st === "idle" ? { ch: "\u25CC", color: theme.warn } : { ch: "\u25CB", color: theme.dim };
896
+ void sessionDot;
883
897
  if (s === null && localOn === null) {
884
898
  return /* @__PURE__ */ jsxs2(Text2, { color: theme.dim, children: [
885
899
  spin,
@@ -891,89 +905,70 @@ function LivePanel({ active: active2 }) {
891
905
  /* @__PURE__ */ jsx2(Text2, { backgroundColor: BG, color: "#82AAF0", children: ` ${spin} up ${fmtUp(nowMs - startRef.current)} \xB7 ${hhmmss(nowMs)} \xB7 api ${latNow}ms ` }),
892
906
  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
907
  ] });
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;
908
+ if (mode === "detail" && detail) {
899
909
  const d = detailEntries;
900
910
  const allow = d.filter((e) => e.decision === "ALLOW").length;
901
911
  const deny = d.length - allow;
902
912
  const dlpN = d.filter((e) => e.dlp).length;
903
- const burstN = d.filter((e) => e.burst).length;
904
913
  const evals = d.filter((e) => e.evalMs != null).map((e) => e.evalMs);
905
914
  const evalAvg = evals.length ? Math.round(evals.reduce((a, b) => a + b, 0) / evals.length) : null;
906
915
  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);
916
+ const pCounts = /* @__PURE__ */ new Map();
917
+ for (const e of d) {
918
+ tCounts.set(e.tool, (tCounts.get(e.tool) ?? 0) + 1);
919
+ if (e.permission) pCounts.set(e.permission, (pCounts.get(e.permission) ?? 0) + 1);
920
+ }
921
+ const top = [...tCounts.entries()].sort((a, b) => b[1] - a[1]).slice(0, 6);
922
+ const perms = [...pCounts.entries()].sort((a, b) => b[1] - a[1]);
909
923
  const first = d[d.length - 1]?.at;
910
924
  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);
925
+ const tlRows = Math.max(4, rows - 10);
926
+ const maxD = Math.max(0, d.length - tlRows);
927
+ const dScroll = Math.min(detailScroll, maxD);
928
+ const tl = d.slice(dScroll, dScroll + tlRows);
915
929
  return /* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", paddingX: 1, children: [
916
930
  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
- ] }) })
931
+ /* @__PURE__ */ jsxs2(Text2, { wrap: "truncate", children: [
932
+ /* @__PURE__ */ jsx2(Text2, { backgroundColor: BG, color: "#AAC8FA", bold: true, children: ` SESSION ${detail.id.slice(0, 8)} ` }),
933
+ /* @__PURE__ */ jsx2(Text2, { color: detail.source === "local" ? theme.ok : "#4f6db8", children: " " + detail.source.toUpperCase() }),
934
+ /* @__PURE__ */ jsx2(Text2, { bold: true, children: " " + truncate(detail.isMe ? detail.agent + " (this machine)" : detail.agent, 34) }),
935
+ detail.trust != null ? /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: ` trust ${detail.trust}/100` }) : null,
936
+ /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: " \u2190 back" })
972
937
  ] }),
973
938
  /* @__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 " })
939
+ /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: "\u2502 calls " }),
940
+ /* @__PURE__ */ jsx2(Text2, { bold: true, children: d.length }),
941
+ /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: " \u2502 allow " }),
942
+ /* @__PURE__ */ jsx2(Text2, { color: theme.ok, children: allow }),
943
+ /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: " \u2502 deny " }),
944
+ /* @__PURE__ */ jsx2(Text2, { color: deny ? theme.bad : theme.dim, bold: deny > 0, children: deny }),
945
+ /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: " \u2502 dlp " }),
946
+ /* @__PURE__ */ jsx2(Text2, { color: dlpN ? theme.bad : theme.dim, children: dlpN }),
947
+ /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: " \u2502 avg eval " }),
948
+ /* @__PURE__ */ jsx2(Text2, { children: evalAvg != null ? `${evalAvg}ms` : "\u2014" }),
949
+ /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: " \u2502 first " }),
950
+ /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: first ? `${hhmmss(first)} (${ago(first)} ago)` : "\u2014" }),
951
+ /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: " \u2502 last " }),
952
+ /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: last ? `${hhmmss(last)} (${ago(last)} ago)` : "\u2014" }),
953
+ /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: " \u2502" })
954
+ ] }),
955
+ /* @__PURE__ */ jsxs2(Text2, { wrap: "truncate", children: [
956
+ /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: "\u2502 tools " }),
957
+ /* @__PURE__ */ jsx2(Text2, { color: theme.accent, children: top.map(([t, c2]) => `${t}\xD7${c2}`).join(" ") || "\u2014" }),
958
+ /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: " \u2502 perms " }),
959
+ /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: perms.map(([p, c2]) => `${p}\xD7${c2}`).join(" ") || "\u2014" }),
960
+ /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: " \u2502" })
961
+ ] }),
962
+ /* @__PURE__ */ jsx2(PaneTitle, { label: "TIMELINE", extra: `${d.length} calls \xB7 newest first${dScroll ? ` \xB7 \u25BC${dScroll} older` : ""} \xB7 \u2191\u2193 scroll \xB7 / search \xB7 \u2190 back`, width: innerW }),
963
+ searchRow,
964
+ /* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", height: tlRows, overflow: "hidden", children: [
965
+ tl.length === 0 ? /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: detailCloud.length === 0 ? spin + " loading history\u2026" : "no entries" }) : null,
966
+ tl.map((e) => /* @__PURE__ */ jsx2(StreamLine, { e, loc: isLoc(e) }, e.id))
967
+ ] }),
968
+ /* @__PURE__ */ jsxs2(Text2, { wrap: "truncate", children: [
969
+ /* @__PURE__ */ jsx2(Text2, { backgroundColor: BG, color: "#82AAF0", bold: true, children: " SESSION " }),
970
+ /* @__PURE__ */ jsx2(Text2, { backgroundColor: "#0b1530", color: "#4f6db8", children: ` ${detail.id} ` }),
971
+ /* @__PURE__ */ jsx2(Text2, { backgroundColor: BG, color: "#82AAF0", children: " \u2191\u2193 scroll \xB7 \u2190 back \xB7 esc menu " })
977
972
  ] })
978
973
  ] });
979
974
  }
@@ -995,7 +990,7 @@ function LivePanel({ active: active2 }) {
995
990
  /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: " \u2502 ghost " }),
996
991
  /* @__PURE__ */ jsx2(Text2, { color: gh?.mode === "on" ? theme.ok : theme.dim, children: gh?.mode ?? "?" }),
997
992
  /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: " \u2502 sess " }),
998
- /* @__PURE__ */ jsx2(Text2, { color: theme.accent, children: String(combinedSessAll.length) }),
993
+ /* @__PURE__ */ jsx2(Text2, { color: theme.accent, children: `${combinedSess.length} (${sessCounts?.active ?? 0} live)` }),
999
994
  /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: " \u2502 hooks " }),
1000
995
  /* @__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
996
  /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: " \u2502" })
@@ -1034,13 +1029,14 @@ function LivePanel({ active: active2 }) {
1034
1029
  ] })
1035
1030
  ] }),
1036
1031
  /* @__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,
1032
+ /* @__PURE__ */ jsx2(PaneTitle, { label: "SESSIONS", extra: mode === "pick" ? "\u2191\u2193 pick \xB7 enter open" : `${combinedSess.length} \xB7 s = inspect`, width: colW }),
1033
+ pickable.length === 0 ? /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: "scanning\u2026" }) : null,
1034
+ pickable.map((r, i) => {
1035
+ const freshDot = nowMs - r.lastAt < 9e4;
1036
+ const sel = mode === "pick" && i === pickIdx;
1037
+ return /* @__PURE__ */ jsxs2(Text2, { wrap: "truncate", backgroundColor: sel ? "#1c2f63" : void 0, bold: sel || r.isMe, children: [
1038
+ /* @__PURE__ */ jsxs2(Text2, { color: sel ? theme.accentBright : freshDot ? theme.ok : theme.dim, children: [
1039
+ sel ? "\u25B8" : freshDot ? "\u25CF" : "\u25CB",
1044
1040
  " "
1045
1041
  ] }),
1046
1042
  /* @__PURE__ */ jsx2(Text2, { color: r.source === "local" ? theme.ok : "#4f6db8", children: r.source === "local" ? "LOC " : "CLD " }),
@@ -1053,7 +1049,7 @@ function LivePanel({ active: active2 }) {
1053
1049
  String(r.denies).padStart(3),
1054
1050
  "d "
1055
1051
  ] }),
1056
- /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: ago(r.lastAt) })
1052
+ /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: (r.trust != null ? `t${r.trust} ` : "") + ago(r.lastAt) })
1057
1053
  ] }, r.id);
1058
1054
  })
1059
1055
  ] }),
@@ -1073,11 +1069,12 @@ function LivePanel({ active: active2 }) {
1073
1069
  PaneTitle,
1074
1070
  {
1075
1071
  label: "TOOL STREAM",
1076
- extra: `newest first \xB7 last ${STREAM_KEEP} \xB7 filter ${filter}${clampedScroll > 0 ? ` \xB7 \u25BC${clampedScroll} older` : ""} \xB7 f filter \xB7 \u2191\u2193 scroll \xB7 s sessions`,
1072
+ extra: `newest first \xB7 last ${STREAM_KEEP} \xB7 filter ${filter}${clampedScroll > 0 ? ` \xB7 \u25BC${clampedScroll} older` : ""} \xB7 f filter \xB7 / search \xB7 \u2191\u2193 scroll \xB7 s sessions`,
1077
1073
  width: innerW
1078
1074
  }
1079
1075
  ),
1080
1076
  /* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", height: streamRows, overflow: "hidden", children: [
1077
+ searchRow,
1081
1078
  localOn === false ? /* @__PURE__ */ jsxs2(Text2, { wrap: "truncate", children: [
1082
1079
  /* @__PURE__ */ jsx2(Text2, { color: theme.warn, children: "local logs off" }),
1083
1080
  /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: " \u2014 enable: dashboard \u2192 Settings \u2192 Local logs \xB7 hooks write ~/.solongate/local-logs \xB7 view: `solongate logs-server`" })
@@ -1090,15 +1087,15 @@ function LivePanel({ active: active2 }) {
1090
1087
  ] }),
1091
1088
  /* @__PURE__ */ jsxs2(Text2, { wrap: "truncate", children: [
1092
1089
  /* @__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"} ` }),
1094
- /* @__PURE__ */ jsx2(Text2, { backgroundColor: BG, color: "#82AAF0", children: " space copy-mode \xB7 f filter \xB7 s sessions \xB7 esc menu \xB7 q quit " })
1090
+ /* @__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"} ` }),
1091
+ /* @__PURE__ */ jsx2(Text2, { backgroundColor: BG, color: "#82AAF0", children: " space copy-mode \xB7 / search \xB7 f filter \xB7 s sessions \xB7 esc menu \xB7 q quit " })
1095
1092
  ] })
1096
1093
  ] });
1097
1094
  }
1098
1095
 
1099
1096
  // src/tui/panels/Policies.tsx
1100
1097
  import { Box as Box3, Text as Text3, useInput as useInput2 } from "ink";
1101
- import TextInput from "ink-text-input";
1098
+ import TextInput2 from "ink-text-input";
1102
1099
  import { useEffect as useEffect3, useState as useState3 } from "react";
1103
1100
  import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
1104
1101
  var constr = (r, g, side) => (r[g]?.[side] ?? []).join(", ");
@@ -1308,7 +1305,7 @@ function PoliciesPanel({ focused }) {
1308
1305
  return /* @__PURE__ */ jsxs3(Box3, { children: [
1309
1306
  /* @__PURE__ */ jsx3(Text3, { color: active2 ? theme.accentBright : void 0, children: (active2 ? "\u25B8 " : " ") + f.label.padEnd(13) }),
1310
1307
  active2 && editing && f.kind === "text" ? /* @__PURE__ */ jsx3(
1311
- TextInput,
1308
+ TextInput2,
1312
1309
  {
1313
1310
  value: editVal,
1314
1311
  onChange: setEditVal,
@@ -1463,7 +1460,7 @@ function FieldRow({ label, active: active2, children }) {
1463
1460
 
1464
1461
  // src/tui/panels/Dlp.tsx
1465
1462
  import { Box as Box5, Text as Text5, useInput as useInput4 } from "ink";
1466
- import TextInput2 from "ink-text-input";
1463
+ import TextInput3 from "ink-text-input";
1467
1464
  import { useEffect as useEffect5, useState as useState5 } from "react";
1468
1465
  import { jsx as jsx5, jsxs as jsxs5 } from "react/jsx-runtime";
1469
1466
  var MODES2 = ["off", "detect", "block"];
@@ -1566,7 +1563,7 @@ function DlpPanel({ focused }) {
1566
1563
  adding ? /* @__PURE__ */ jsxs5(Box5, { marginTop: 1, children: [
1567
1564
  /* @__PURE__ */ jsx5(Text5, { color: theme.warn, children: adding === "name" ? "New pattern name: " : `Regex for "${newName}": ` }),
1568
1565
  adding === "name" ? /* @__PURE__ */ jsx5(
1569
- TextInput2,
1566
+ TextInput3,
1570
1567
  {
1571
1568
  value: newName,
1572
1569
  onChange: setNewName,
@@ -1577,7 +1574,7 @@ function DlpPanel({ focused }) {
1577
1574
  }
1578
1575
  }
1579
1576
  ) : /* @__PURE__ */ jsx5(
1580
- TextInput2,
1577
+ TextInput3,
1581
1578
  {
1582
1579
  value: newRe,
1583
1580
  onChange: setNewRe,
@@ -1670,7 +1667,7 @@ function StatsPanel({ active: active2 }) {
1670
1667
 
1671
1668
  // src/tui/panels/Audit.tsx
1672
1669
  import { Box as Box7, Text as Text7, useInput as useInput5 } from "ink";
1673
- import TextInput3 from "ink-text-input";
1670
+ import TextInput4 from "ink-text-input";
1674
1671
  import { useState as useState6 } from "react";
1675
1672
  import { jsx as jsx7, jsxs as jsxs7 } from "react/jsx-runtime";
1676
1673
  var DECISIONS = [void 0, "DENY", "ALLOW"];
@@ -1789,7 +1786,7 @@ function AuditPanel({ active: active2, focused }) {
1789
1786
  ": "
1790
1787
  ] }),
1791
1788
  /* @__PURE__ */ jsx7(
1792
- TextInput3,
1789
+ TextInput4,
1793
1790
  {
1794
1791
  value: editing === "tool" ? tool : editing === "agent" ? agent : search,
1795
1792
  onChange: editing === "tool" ? setTool : editing === "agent" ? setAgent : setSearch,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@solongate/proxy",
3
- "version": "0.73.0",
3
+ "version": "0.75.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": {