@solongate/proxy 0.75.0 → 0.76.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.
- package/dist/commands/index.js +11 -11
- package/dist/index.js +77 -25
- package/dist/tui/index.js +72 -20
- package/package.json +1 -1
package/dist/commands/index.js
CHANGED
|
@@ -86,26 +86,26 @@ async function request(method, path, opts = {}) {
|
|
|
86
86
|
}
|
|
87
87
|
const attempt = () => fetch(url, {
|
|
88
88
|
method,
|
|
89
|
-
headers,
|
|
89
|
+
headers: { ...headers, Connection: "close" },
|
|
90
90
|
body: bodyInit,
|
|
91
91
|
signal: AbortSignal.timeout(opts.timeoutMs ?? 15e3)
|
|
92
92
|
});
|
|
93
|
+
const maxTries = method === "GET" ? 3 : 1;
|
|
93
94
|
let res;
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
} catch (firstErr) {
|
|
97
|
-
if (method !== "GET") {
|
|
98
|
-
const msg = firstErr instanceof Error ? firstErr.message : String(firstErr);
|
|
99
|
-
throw new ApiError(0, "NETWORK_ERROR", `Cannot reach SolonGate API: ${msg}`);
|
|
100
|
-
}
|
|
101
|
-
await new Promise((r) => setTimeout(r, 800));
|
|
95
|
+
let lastErr;
|
|
96
|
+
for (let i = 0; i < maxTries; i++) {
|
|
102
97
|
try {
|
|
103
98
|
res = await attempt();
|
|
99
|
+
break;
|
|
104
100
|
} catch (err2) {
|
|
105
|
-
|
|
106
|
-
|
|
101
|
+
lastErr = err2;
|
|
102
|
+
if (i < maxTries - 1) await new Promise((r) => setTimeout(r, 600 * (i + 1)));
|
|
107
103
|
}
|
|
108
104
|
}
|
|
105
|
+
if (!res) {
|
|
106
|
+
const msg = lastErr instanceof Error ? lastErr.message : String(lastErr);
|
|
107
|
+
throw new ApiError(0, "NETWORK_ERROR", `Cannot reach SolonGate API: ${msg}`);
|
|
108
|
+
}
|
|
109
109
|
const text = await res.text().catch(() => "");
|
|
110
110
|
let json = void 0;
|
|
111
111
|
if (text) {
|
package/dist/index.js
CHANGED
|
@@ -6635,26 +6635,26 @@ async function request(method, path, opts = {}) {
|
|
|
6635
6635
|
}
|
|
6636
6636
|
const attempt = () => fetch(url, {
|
|
6637
6637
|
method,
|
|
6638
|
-
headers,
|
|
6638
|
+
headers: { ...headers, Connection: "close" },
|
|
6639
6639
|
body: bodyInit,
|
|
6640
6640
|
signal: AbortSignal.timeout(opts.timeoutMs ?? 15e3)
|
|
6641
6641
|
});
|
|
6642
|
+
const maxTries = method === "GET" ? 3 : 1;
|
|
6642
6643
|
let res;
|
|
6643
|
-
|
|
6644
|
-
|
|
6645
|
-
} catch (firstErr) {
|
|
6646
|
-
if (method !== "GET") {
|
|
6647
|
-
const msg = firstErr instanceof Error ? firstErr.message : String(firstErr);
|
|
6648
|
-
throw new ApiError(0, "NETWORK_ERROR", `Cannot reach SolonGate API: ${msg}`);
|
|
6649
|
-
}
|
|
6650
|
-
await new Promise((r) => setTimeout(r, 800));
|
|
6644
|
+
let lastErr;
|
|
6645
|
+
for (let i = 0; i < maxTries; i++) {
|
|
6651
6646
|
try {
|
|
6652
6647
|
res = await attempt();
|
|
6648
|
+
break;
|
|
6653
6649
|
} catch (err2) {
|
|
6654
|
-
|
|
6655
|
-
|
|
6650
|
+
lastErr = err2;
|
|
6651
|
+
if (i < maxTries - 1) await new Promise((r) => setTimeout(r, 600 * (i + 1)));
|
|
6656
6652
|
}
|
|
6657
6653
|
}
|
|
6654
|
+
if (!res) {
|
|
6655
|
+
const msg = lastErr instanceof Error ? lastErr.message : String(lastErr);
|
|
6656
|
+
throw new ApiError(0, "NETWORK_ERROR", `Cannot reach SolonGate API: ${msg}`);
|
|
6657
|
+
}
|
|
6658
6658
|
const text = await res.text().catch(() => "");
|
|
6659
6659
|
let json = void 0;
|
|
6660
6660
|
if (text) {
|
|
@@ -7031,6 +7031,7 @@ var init_hooks = __esm({
|
|
|
7031
7031
|
// src/tui/panels/Live.tsx
|
|
7032
7032
|
import { Box as Box2, Text as Text2, useInput } from "ink";
|
|
7033
7033
|
import TextInput from "ink-text-input";
|
|
7034
|
+
import { spawn } from "child_process";
|
|
7034
7035
|
import { closeSync, openSync, readSync, statSync } from "fs";
|
|
7035
7036
|
import { homedir as homedir3 } from "os";
|
|
7036
7037
|
import { join as join5 } from "path";
|
|
@@ -7154,18 +7155,26 @@ function LivePanel({ active: active2 }) {
|
|
|
7154
7155
|
const seenRef = useRef2(/* @__PURE__ */ new Set());
|
|
7155
7156
|
const lastLocalTs = useRef2(0);
|
|
7156
7157
|
const pausedUntil = useRef2(0);
|
|
7158
|
+
const sessRef = useRef2([]);
|
|
7159
|
+
const prevStatus = useRef2(/* @__PURE__ */ new Map());
|
|
7160
|
+
const [alerts, setAlerts] = useState2([]);
|
|
7157
7161
|
const [frozen, setFrozen] = useState2(false);
|
|
7158
7162
|
const frozenRef = useRef2(false);
|
|
7159
7163
|
frozenRef.current = frozen;
|
|
7160
7164
|
const pushLog = useCallback2((msg, level = "ok") => {
|
|
7161
7165
|
setLog((prev) => [...prev.slice(-59), { ts: Date.now(), msg, level }]);
|
|
7162
7166
|
}, []);
|
|
7167
|
+
const netFails = useRef2(0);
|
|
7163
7168
|
const onApiError = useCallback2(
|
|
7164
7169
|
(e) => {
|
|
7165
7170
|
const is429 = e instanceof ApiError && e.status === 429;
|
|
7171
|
+
const isNet = e instanceof ApiError && e.status === 0;
|
|
7166
7172
|
if (is429 && Date.now() >= pausedUntil.current) {
|
|
7167
7173
|
pausedUntil.current = Date.now() + 3e4;
|
|
7168
7174
|
pushLog("rate limited by api \xB7 backing off 30s", "warn");
|
|
7175
|
+
} else if (isNet) {
|
|
7176
|
+
netFails.current += 1;
|
|
7177
|
+
if (netFails.current === 3) pushLog("network unreachable \xB7 retrying\u2026", "warn");
|
|
7169
7178
|
} else if (!is429) {
|
|
7170
7179
|
pushLog(`api error \xB7 ${truncate2(e instanceof Error ? e.message : String(e), 40)}`, "bad");
|
|
7171
7180
|
}
|
|
@@ -7241,6 +7250,8 @@ function LivePanel({ active: active2 }) {
|
|
|
7241
7250
|
try {
|
|
7242
7251
|
const fd = await api.audit.list({ limit: 50 });
|
|
7243
7252
|
if (frozenRef.current) return;
|
|
7253
|
+
if (netFails.current >= 3) pushLog("network recovered", "ok");
|
|
7254
|
+
netFails.current = 0;
|
|
7244
7255
|
const ms = Date.now() - t0;
|
|
7245
7256
|
setLat((l) => [...l, ms].slice(-240));
|
|
7246
7257
|
const fresh = fd.entries.filter((e) => !seenRef.current.has(e.id));
|
|
@@ -7307,6 +7318,30 @@ function LivePanel({ active: active2 }) {
|
|
|
7307
7318
|
return () => clearInterval(t);
|
|
7308
7319
|
}, [active2, frozen]);
|
|
7309
7320
|
const startRef = useRef2(Date.now());
|
|
7321
|
+
useEffect2(() => {
|
|
7322
|
+
if (!active2) return;
|
|
7323
|
+
for (const r of sessRef.current) {
|
|
7324
|
+
const prev = prevStatus.current.get(r.id);
|
|
7325
|
+
if (prev && prev === "active" && r.status === "idle") {
|
|
7326
|
+
const name = r.isMe ? `${r.agent} (this machine)` : r.agent;
|
|
7327
|
+
const msg = `${name} went IDLE \u2014 no tool call for 60s`;
|
|
7328
|
+
pushLog(`\u23F8 ${msg}`, "warn");
|
|
7329
|
+
setAlerts((a) => [...a.filter((x) => x.id !== r.id), { id: r.id, msg, until: Date.now() + 15e3 }].slice(-4));
|
|
7330
|
+
try {
|
|
7331
|
+
process.stdout.write("\x07");
|
|
7332
|
+
} catch {
|
|
7333
|
+
}
|
|
7334
|
+
try {
|
|
7335
|
+
const child = spawn("notify-send", ["-a", "SolonGate", "Agent idle", msg], { stdio: "ignore", detached: true });
|
|
7336
|
+
child.on("error", () => {
|
|
7337
|
+
});
|
|
7338
|
+
child.unref();
|
|
7339
|
+
} catch {
|
|
7340
|
+
}
|
|
7341
|
+
}
|
|
7342
|
+
prevStatus.current.set(r.id, r.status);
|
|
7343
|
+
}
|
|
7344
|
+
}, [tick, active2, pushLog]);
|
|
7310
7345
|
const cols = process.stdout.columns ?? 100;
|
|
7311
7346
|
const rows = process.stdout.rows ?? 30;
|
|
7312
7347
|
const ins = insights.data ?? {};
|
|
@@ -7359,7 +7394,7 @@ function LivePanel({ active: active2 }) {
|
|
|
7359
7394
|
localSess.set(e.session, cur);
|
|
7360
7395
|
}
|
|
7361
7396
|
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 })),
|
|
7397
|
+
...[...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, status: sessStatus(v.lastAt, nowMs), isMe: ring?.session === id })),
|
|
7363
7398
|
...sess.filter((a) => !localSess.has(a.session_id)).map((a) => ({
|
|
7364
7399
|
source: "cloud",
|
|
7365
7400
|
id: a.session_id,
|
|
@@ -7367,10 +7402,12 @@ function LivePanel({ active: active2 }) {
|
|
|
7367
7402
|
calls: a.total_calls,
|
|
7368
7403
|
denies: a.denied_calls,
|
|
7369
7404
|
lastAt: Date.parse(a.last_seen_at),
|
|
7405
|
+
status: sessStatus(Date.parse(a.last_seen_at), nowMs),
|
|
7370
7406
|
trust: a.trust_score,
|
|
7371
7407
|
isMe: false
|
|
7372
7408
|
}))
|
|
7373
7409
|
];
|
|
7410
|
+
sessRef.current = combinedSess;
|
|
7374
7411
|
const localSessIds = new Set(localSess.keys());
|
|
7375
7412
|
if (ring?.session) localSessIds.add(ring.session);
|
|
7376
7413
|
const isLoc = (e) => e.source === "local" || !!e.session && localSessIds.has(e.session);
|
|
@@ -7454,6 +7491,10 @@ function LivePanel({ active: active2 }) {
|
|
|
7454
7491
|
{
|
|
7455
7492
|
value: search,
|
|
7456
7493
|
onChange: (v) => {
|
|
7494
|
+
if (v.endsWith("/")) {
|
|
7495
|
+
setEditingSearch(false);
|
|
7496
|
+
return;
|
|
7497
|
+
}
|
|
7457
7498
|
setSearch(v);
|
|
7458
7499
|
setScroll(0);
|
|
7459
7500
|
setDetailScroll(0);
|
|
@@ -7461,7 +7502,7 @@ function LivePanel({ active: active2 }) {
|
|
|
7461
7502
|
onSubmit: () => setEditingSearch(false)
|
|
7462
7503
|
}
|
|
7463
7504
|
),
|
|
7464
|
-
/* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: " (enter done \xB7 empty = clear)" })
|
|
7505
|
+
/* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: " (enter/\u200B/ done \xB7 empty = clear)" })
|
|
7465
7506
|
] }) : search ? /* @__PURE__ */ jsxs2(Text2, { wrap: "truncate", children: [
|
|
7466
7507
|
/* @__PURE__ */ jsx2(Text2, { color: theme.warn, children: "search: " }),
|
|
7467
7508
|
/* @__PURE__ */ jsx2(Text2, { color: theme.accentBright, bold: true, children: search }),
|
|
@@ -7505,7 +7546,11 @@ function LivePanel({ active: active2 }) {
|
|
|
7505
7546
|
titleBar,
|
|
7506
7547
|
/* @__PURE__ */ jsxs2(Text2, { wrap: "truncate", children: [
|
|
7507
7548
|
/* @__PURE__ */ jsx2(Text2, { backgroundColor: BG, color: "#AAC8FA", bold: true, children: ` SESSION ${detail.id.slice(0, 8)} ` }),
|
|
7508
|
-
|
|
7549
|
+
(() => {
|
|
7550
|
+
const st = STATUS_STYLE[sessStatus(detail.lastAt, nowMs)];
|
|
7551
|
+
return /* @__PURE__ */ jsx2(Text2, { color: st.color, bold: true, children: ` ${st.dot} ${st.label}` });
|
|
7552
|
+
})(),
|
|
7553
|
+
/* @__PURE__ */ jsx2(Text2, { color: detail.source === "local" ? theme.ok : "#4f6db8", children: " " + detail.source.toUpperCase() }),
|
|
7509
7554
|
/* @__PURE__ */ jsx2(Text2, { bold: true, children: " " + truncate2(detail.isMe ? detail.agent + " (this machine)" : detail.agent, 34) }),
|
|
7510
7555
|
detail.trust != null ? /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: ` trust ${detail.trust}/100` }) : null,
|
|
7511
7556
|
/* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: " \u2190 back" })
|
|
@@ -7535,8 +7580,8 @@ function LivePanel({ active: active2 }) {
|
|
|
7535
7580
|
/* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: " \u2502" })
|
|
7536
7581
|
] }),
|
|
7537
7582
|
/* @__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
7583
|
/* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", height: tlRows, overflow: "hidden", children: [
|
|
7584
|
+
searchRow,
|
|
7540
7585
|
tl.length === 0 ? /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: detailCloud.length === 0 ? spin + " loading history\u2026" : "no entries" }) : null,
|
|
7541
7586
|
tl.map((e) => /* @__PURE__ */ jsx2(StreamLine, { e, loc: isLoc(e) }, e.id))
|
|
7542
7587
|
] }),
|
|
@@ -7570,6 +7615,7 @@ function LivePanel({ active: active2 }) {
|
|
|
7570
7615
|
/* @__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" }),
|
|
7571
7616
|
/* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: " \u2502" })
|
|
7572
7617
|
] }),
|
|
7618
|
+
alerts.filter((a) => nowMs < a.until).slice(-1).map((a) => /* @__PURE__ */ jsx2(Text2, { wrap: "truncate", backgroundColor: "#3d2a12", color: "#ffb454", bold: true, children: ` \u23F8 IDLE \xB7 ${truncate2(a.msg, innerW - 12)} ` }, a.id)),
|
|
7573
7619
|
/* @__PURE__ */ jsxs2(Box2, { height: 1 + chartH, children: [
|
|
7574
7620
|
/* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", width: leftW, marginRight: 2, children: [
|
|
7575
7621
|
/* @__PURE__ */ jsx2(PaneTitle, { label: "TRAFFIC", extra: `${trafficLabel} \xB7 peak ${Math.max(0, ...traffic)} \xB7 red = denials`, width: leftW }),
|
|
@@ -7607,14 +7653,14 @@ function LivePanel({ active: active2 }) {
|
|
|
7607
7653
|
/* @__PURE__ */ jsx2(PaneTitle, { label: "SESSIONS", extra: mode === "pick" ? "\u2191\u2193 pick \xB7 enter open" : `${combinedSess.length} \xB7 s = inspect`, width: colW }),
|
|
7608
7654
|
pickable.length === 0 ? /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: "scanning\u2026" }) : null,
|
|
7609
7655
|
pickable.map((r, i) => {
|
|
7610
|
-
const freshDot = nowMs - r.lastAt < 9e4;
|
|
7611
7656
|
const sel = mode === "pick" && i === pickIdx;
|
|
7657
|
+
const st = STATUS_STYLE[r.status];
|
|
7612
7658
|
return /* @__PURE__ */ jsxs2(Text2, { wrap: "truncate", backgroundColor: sel ? "#1c2f63" : void 0, bold: sel || r.isMe, children: [
|
|
7613
|
-
/* @__PURE__ */ jsxs2(Text2, { color: sel ? theme.accentBright :
|
|
7614
|
-
sel ? "\u25B8" :
|
|
7659
|
+
/* @__PURE__ */ jsxs2(Text2, { color: sel ? theme.accentBright : st.color, children: [
|
|
7660
|
+
sel ? "\u25B8" : st.dot,
|
|
7615
7661
|
" "
|
|
7616
7662
|
] }),
|
|
7617
|
-
/* @__PURE__ */ jsx2(Text2, { color:
|
|
7663
|
+
/* @__PURE__ */ jsx2(Text2, { color: st.color, children: st.label.padEnd(7) }),
|
|
7618
7664
|
/* @__PURE__ */ jsx2(Text2, { color: theme.accentBright, children: truncate2(r.isMe ? "this machine" : r.agent, 12).padEnd(13) }),
|
|
7619
7665
|
/* @__PURE__ */ jsxs2(Text2, { children: [
|
|
7620
7666
|
String(r.calls).padStart(4),
|
|
@@ -7648,7 +7694,7 @@ function LivePanel({ active: active2 }) {
|
|
|
7648
7694
|
width: innerW
|
|
7649
7695
|
}
|
|
7650
7696
|
),
|
|
7651
|
-
/* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", height: streamRows, overflow: "hidden", children: [
|
|
7697
|
+
/* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", height: searchRow ? streamRows - 1 : streamRows, overflow: "hidden", children: [
|
|
7652
7698
|
searchRow,
|
|
7653
7699
|
localOn === false ? /* @__PURE__ */ jsxs2(Text2, { wrap: "truncate", children: [
|
|
7654
7700
|
/* @__PURE__ */ jsx2(Text2, { color: theme.warn, children: "local logs off" }),
|
|
@@ -7667,7 +7713,7 @@ function LivePanel({ active: active2 }) {
|
|
|
7667
7713
|
] })
|
|
7668
7714
|
] });
|
|
7669
7715
|
}
|
|
7670
|
-
var SPIN, BG, DIM_FLOOR, LOCAL_LOG, RING, STREAM_KEEP, hhmmss, fmtUp, FILTERS;
|
|
7716
|
+
var SPIN, BG, DIM_FLOOR, LOCAL_LOG, RING, STREAM_KEEP, hhmmss, fmtUp, FILTERS, sessStatus, STATUS_STYLE;
|
|
7671
7717
|
var init_Live = __esm({
|
|
7672
7718
|
"src/tui/panels/Live.tsx"() {
|
|
7673
7719
|
"use strict";
|
|
@@ -7690,6 +7736,12 @@ var init_Live = __esm({
|
|
|
7690
7736
|
return `${p(Math.floor(s / 3600))}:${p(Math.floor(s % 3600 / 60))}:${p(s % 60)}`;
|
|
7691
7737
|
};
|
|
7692
7738
|
FILTERS = ["all", "local", "cloud"];
|
|
7739
|
+
sessStatus = (lastAt, now) => now - lastAt < 6e4 ? "active" : now - lastAt < 3e5 ? "idle" : "ended";
|
|
7740
|
+
STATUS_STYLE = {
|
|
7741
|
+
active: { dot: "\u25CF", label: "ACTIVE", color: theme.ok },
|
|
7742
|
+
idle: { dot: "\u25D0", label: "IDLE", color: theme.warn },
|
|
7743
|
+
ended: { dot: "\u25CB", label: "ENDED", color: theme.dim }
|
|
7744
|
+
};
|
|
7693
7745
|
}
|
|
7694
7746
|
});
|
|
7695
7747
|
|
|
@@ -9631,7 +9683,7 @@ var init_global_install = __esm({
|
|
|
9631
9683
|
|
|
9632
9684
|
// src/login.ts
|
|
9633
9685
|
var login_exports = {};
|
|
9634
|
-
import { spawn } from "child_process";
|
|
9686
|
+
import { spawn as spawn2 } from "child_process";
|
|
9635
9687
|
function startSpinner(text) {
|
|
9636
9688
|
if (!process.stderr.isTTY) {
|
|
9637
9689
|
process.stderr.write(` ${text}
|
|
@@ -9667,7 +9719,7 @@ function openBrowser(url) {
|
|
|
9667
9719
|
try {
|
|
9668
9720
|
const cmd = process.platform === "win32" ? "cmd" : process.platform === "darwin" ? "open" : "xdg-open";
|
|
9669
9721
|
const args = process.platform === "win32" ? ["/c", "start", '""', url] : [url];
|
|
9670
|
-
const child =
|
|
9722
|
+
const child = spawn2(cmd, args, { stdio: "ignore", detached: true });
|
|
9671
9723
|
child.on("error", () => {
|
|
9672
9724
|
});
|
|
9673
9725
|
child.unref();
|
|
@@ -9786,7 +9838,7 @@ __export(shield_exports, {
|
|
|
9786
9838
|
});
|
|
9787
9839
|
import { createServer, request as httpRequest } from "http";
|
|
9788
9840
|
import { request as httpsRequest } from "https";
|
|
9789
|
-
import { spawn as
|
|
9841
|
+
import { spawn as spawn3 } from "child_process";
|
|
9790
9842
|
import { URL as URL2 } from "url";
|
|
9791
9843
|
import { readFileSync as readFileSync7, existsSync as existsSync5, readdirSync, statSync as statSync2 } from "fs";
|
|
9792
9844
|
import { resolve as resolve5 } from "path";
|
|
@@ -10034,7 +10086,7 @@ async function runShield() {
|
|
|
10034
10086
|
const upstream = pickUpstream();
|
|
10035
10087
|
const { port, close } = await startProxy(upstream);
|
|
10036
10088
|
log4(`redacting secrets on the LLM path \u2192 masking before ${upstream.host} (127.0.0.1:${port})`);
|
|
10037
|
-
const child =
|
|
10089
|
+
const child = spawn3(cmd[0], cmd.slice(1), {
|
|
10038
10090
|
stdio: "inherit",
|
|
10039
10091
|
env: { ...process.env, ANTHROPIC_BASE_URL: `http://127.0.0.1:${port}` },
|
|
10040
10092
|
shell: process.platform === "win32"
|
package/dist/tui/index.js
CHANGED
|
@@ -127,6 +127,7 @@ function KeyHints({ hints }) {
|
|
|
127
127
|
// src/tui/panels/Live.tsx
|
|
128
128
|
import { Box as Box2, Text as Text2, useInput } from "ink";
|
|
129
129
|
import TextInput from "ink-text-input";
|
|
130
|
+
import { spawn } from "child_process";
|
|
130
131
|
import { closeSync, openSync, readSync, statSync } from "fs";
|
|
131
132
|
import { homedir as homedir2 } from "os";
|
|
132
133
|
import { join as join2 } from "path";
|
|
@@ -222,26 +223,26 @@ async function request(method, path, opts = {}) {
|
|
|
222
223
|
}
|
|
223
224
|
const attempt = () => fetch(url, {
|
|
224
225
|
method,
|
|
225
|
-
headers,
|
|
226
|
+
headers: { ...headers, Connection: "close" },
|
|
226
227
|
body: bodyInit,
|
|
227
228
|
signal: AbortSignal.timeout(opts.timeoutMs ?? 15e3)
|
|
228
229
|
});
|
|
230
|
+
const maxTries = method === "GET" ? 3 : 1;
|
|
229
231
|
let res;
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
} catch (firstErr) {
|
|
233
|
-
if (method !== "GET") {
|
|
234
|
-
const msg = firstErr instanceof Error ? firstErr.message : String(firstErr);
|
|
235
|
-
throw new ApiError(0, "NETWORK_ERROR", `Cannot reach SolonGate API: ${msg}`);
|
|
236
|
-
}
|
|
237
|
-
await new Promise((r) => setTimeout(r, 800));
|
|
232
|
+
let lastErr;
|
|
233
|
+
for (let i = 0; i < maxTries; i++) {
|
|
238
234
|
try {
|
|
239
235
|
res = await attempt();
|
|
236
|
+
break;
|
|
240
237
|
} catch (err) {
|
|
241
|
-
|
|
242
|
-
|
|
238
|
+
lastErr = err;
|
|
239
|
+
if (i < maxTries - 1) await new Promise((r) => setTimeout(r, 600 * (i + 1)));
|
|
243
240
|
}
|
|
244
241
|
}
|
|
242
|
+
if (!res) {
|
|
243
|
+
const msg = lastErr instanceof Error ? lastErr.message : String(lastErr);
|
|
244
|
+
throw new ApiError(0, "NETWORK_ERROR", `Cannot reach SolonGate API: ${msg}`);
|
|
245
|
+
}
|
|
245
246
|
const text = await res.text().catch(() => "");
|
|
246
247
|
let json = void 0;
|
|
247
248
|
if (text) {
|
|
@@ -559,6 +560,12 @@ function StreamLine({ e, loc }) {
|
|
|
559
560
|
] });
|
|
560
561
|
}
|
|
561
562
|
var FILTERS = ["all", "local", "cloud"];
|
|
563
|
+
var sessStatus = (lastAt, now) => now - lastAt < 6e4 ? "active" : now - lastAt < 3e5 ? "idle" : "ended";
|
|
564
|
+
var STATUS_STYLE = {
|
|
565
|
+
active: { dot: "\u25CF", label: "ACTIVE", color: theme.ok },
|
|
566
|
+
idle: { dot: "\u25D0", label: "IDLE", color: theme.warn },
|
|
567
|
+
ended: { dot: "\u25CB", label: "ENDED", color: theme.dim }
|
|
568
|
+
};
|
|
562
569
|
function LivePanel({ active: active2 }) {
|
|
563
570
|
const [s, setS] = useState2(null);
|
|
564
571
|
const [lat, setLat] = useState2([]);
|
|
@@ -579,18 +586,26 @@ function LivePanel({ active: active2 }) {
|
|
|
579
586
|
const seenRef = useRef2(/* @__PURE__ */ new Set());
|
|
580
587
|
const lastLocalTs = useRef2(0);
|
|
581
588
|
const pausedUntil = useRef2(0);
|
|
589
|
+
const sessRef = useRef2([]);
|
|
590
|
+
const prevStatus = useRef2(/* @__PURE__ */ new Map());
|
|
591
|
+
const [alerts, setAlerts] = useState2([]);
|
|
582
592
|
const [frozen, setFrozen] = useState2(false);
|
|
583
593
|
const frozenRef = useRef2(false);
|
|
584
594
|
frozenRef.current = frozen;
|
|
585
595
|
const pushLog = useCallback2((msg, level = "ok") => {
|
|
586
596
|
setLog((prev) => [...prev.slice(-59), { ts: Date.now(), msg, level }]);
|
|
587
597
|
}, []);
|
|
598
|
+
const netFails = useRef2(0);
|
|
588
599
|
const onApiError = useCallback2(
|
|
589
600
|
(e) => {
|
|
590
601
|
const is429 = e instanceof ApiError && e.status === 429;
|
|
602
|
+
const isNet = e instanceof ApiError && e.status === 0;
|
|
591
603
|
if (is429 && Date.now() >= pausedUntil.current) {
|
|
592
604
|
pausedUntil.current = Date.now() + 3e4;
|
|
593
605
|
pushLog("rate limited by api \xB7 backing off 30s", "warn");
|
|
606
|
+
} else if (isNet) {
|
|
607
|
+
netFails.current += 1;
|
|
608
|
+
if (netFails.current === 3) pushLog("network unreachable \xB7 retrying\u2026", "warn");
|
|
594
609
|
} else if (!is429) {
|
|
595
610
|
pushLog(`api error \xB7 ${truncate(e instanceof Error ? e.message : String(e), 40)}`, "bad");
|
|
596
611
|
}
|
|
@@ -666,6 +681,8 @@ function LivePanel({ active: active2 }) {
|
|
|
666
681
|
try {
|
|
667
682
|
const fd = await api.audit.list({ limit: 50 });
|
|
668
683
|
if (frozenRef.current) return;
|
|
684
|
+
if (netFails.current >= 3) pushLog("network recovered", "ok");
|
|
685
|
+
netFails.current = 0;
|
|
669
686
|
const ms = Date.now() - t0;
|
|
670
687
|
setLat((l) => [...l, ms].slice(-240));
|
|
671
688
|
const fresh = fd.entries.filter((e) => !seenRef.current.has(e.id));
|
|
@@ -732,6 +749,30 @@ function LivePanel({ active: active2 }) {
|
|
|
732
749
|
return () => clearInterval(t);
|
|
733
750
|
}, [active2, frozen]);
|
|
734
751
|
const startRef = useRef2(Date.now());
|
|
752
|
+
useEffect2(() => {
|
|
753
|
+
if (!active2) return;
|
|
754
|
+
for (const r of sessRef.current) {
|
|
755
|
+
const prev = prevStatus.current.get(r.id);
|
|
756
|
+
if (prev && prev === "active" && r.status === "idle") {
|
|
757
|
+
const name = r.isMe ? `${r.agent} (this machine)` : r.agent;
|
|
758
|
+
const msg = `${name} went IDLE \u2014 no tool call for 60s`;
|
|
759
|
+
pushLog(`\u23F8 ${msg}`, "warn");
|
|
760
|
+
setAlerts((a) => [...a.filter((x) => x.id !== r.id), { id: r.id, msg, until: Date.now() + 15e3 }].slice(-4));
|
|
761
|
+
try {
|
|
762
|
+
process.stdout.write("\x07");
|
|
763
|
+
} catch {
|
|
764
|
+
}
|
|
765
|
+
try {
|
|
766
|
+
const child = spawn("notify-send", ["-a", "SolonGate", "Agent idle", msg], { stdio: "ignore", detached: true });
|
|
767
|
+
child.on("error", () => {
|
|
768
|
+
});
|
|
769
|
+
child.unref();
|
|
770
|
+
} catch {
|
|
771
|
+
}
|
|
772
|
+
}
|
|
773
|
+
prevStatus.current.set(r.id, r.status);
|
|
774
|
+
}
|
|
775
|
+
}, [tick, active2, pushLog]);
|
|
735
776
|
const cols = process.stdout.columns ?? 100;
|
|
736
777
|
const rows = process.stdout.rows ?? 30;
|
|
737
778
|
const ins = insights.data ?? {};
|
|
@@ -784,7 +825,7 @@ function LivePanel({ active: active2 }) {
|
|
|
784
825
|
localSess.set(e.session, cur);
|
|
785
826
|
}
|
|
786
827
|
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 })),
|
|
828
|
+
...[...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, status: sessStatus(v.lastAt, nowMs), isMe: ring?.session === id })),
|
|
788
829
|
...sess.filter((a) => !localSess.has(a.session_id)).map((a) => ({
|
|
789
830
|
source: "cloud",
|
|
790
831
|
id: a.session_id,
|
|
@@ -792,10 +833,12 @@ function LivePanel({ active: active2 }) {
|
|
|
792
833
|
calls: a.total_calls,
|
|
793
834
|
denies: a.denied_calls,
|
|
794
835
|
lastAt: Date.parse(a.last_seen_at),
|
|
836
|
+
status: sessStatus(Date.parse(a.last_seen_at), nowMs),
|
|
795
837
|
trust: a.trust_score,
|
|
796
838
|
isMe: false
|
|
797
839
|
}))
|
|
798
840
|
];
|
|
841
|
+
sessRef.current = combinedSess;
|
|
799
842
|
const localSessIds = new Set(localSess.keys());
|
|
800
843
|
if (ring?.session) localSessIds.add(ring.session);
|
|
801
844
|
const isLoc = (e) => e.source === "local" || !!e.session && localSessIds.has(e.session);
|
|
@@ -879,6 +922,10 @@ function LivePanel({ active: active2 }) {
|
|
|
879
922
|
{
|
|
880
923
|
value: search,
|
|
881
924
|
onChange: (v) => {
|
|
925
|
+
if (v.endsWith("/")) {
|
|
926
|
+
setEditingSearch(false);
|
|
927
|
+
return;
|
|
928
|
+
}
|
|
882
929
|
setSearch(v);
|
|
883
930
|
setScroll(0);
|
|
884
931
|
setDetailScroll(0);
|
|
@@ -886,7 +933,7 @@ function LivePanel({ active: active2 }) {
|
|
|
886
933
|
onSubmit: () => setEditingSearch(false)
|
|
887
934
|
}
|
|
888
935
|
),
|
|
889
|
-
/* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: " (enter done \xB7 empty = clear)" })
|
|
936
|
+
/* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: " (enter/\u200B/ done \xB7 empty = clear)" })
|
|
890
937
|
] }) : search ? /* @__PURE__ */ jsxs2(Text2, { wrap: "truncate", children: [
|
|
891
938
|
/* @__PURE__ */ jsx2(Text2, { color: theme.warn, children: "search: " }),
|
|
892
939
|
/* @__PURE__ */ jsx2(Text2, { color: theme.accentBright, bold: true, children: search }),
|
|
@@ -930,7 +977,11 @@ function LivePanel({ active: active2 }) {
|
|
|
930
977
|
titleBar,
|
|
931
978
|
/* @__PURE__ */ jsxs2(Text2, { wrap: "truncate", children: [
|
|
932
979
|
/* @__PURE__ */ jsx2(Text2, { backgroundColor: BG, color: "#AAC8FA", bold: true, children: ` SESSION ${detail.id.slice(0, 8)} ` }),
|
|
933
|
-
|
|
980
|
+
(() => {
|
|
981
|
+
const st = STATUS_STYLE[sessStatus(detail.lastAt, nowMs)];
|
|
982
|
+
return /* @__PURE__ */ jsx2(Text2, { color: st.color, bold: true, children: ` ${st.dot} ${st.label}` });
|
|
983
|
+
})(),
|
|
984
|
+
/* @__PURE__ */ jsx2(Text2, { color: detail.source === "local" ? theme.ok : "#4f6db8", children: " " + detail.source.toUpperCase() }),
|
|
934
985
|
/* @__PURE__ */ jsx2(Text2, { bold: true, children: " " + truncate(detail.isMe ? detail.agent + " (this machine)" : detail.agent, 34) }),
|
|
935
986
|
detail.trust != null ? /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: ` trust ${detail.trust}/100` }) : null,
|
|
936
987
|
/* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: " \u2190 back" })
|
|
@@ -960,8 +1011,8 @@ function LivePanel({ active: active2 }) {
|
|
|
960
1011
|
/* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: " \u2502" })
|
|
961
1012
|
] }),
|
|
962
1013
|
/* @__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
1014
|
/* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", height: tlRows, overflow: "hidden", children: [
|
|
1015
|
+
searchRow,
|
|
965
1016
|
tl.length === 0 ? /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: detailCloud.length === 0 ? spin + " loading history\u2026" : "no entries" }) : null,
|
|
966
1017
|
tl.map((e) => /* @__PURE__ */ jsx2(StreamLine, { e, loc: isLoc(e) }, e.id))
|
|
967
1018
|
] }),
|
|
@@ -995,6 +1046,7 @@ function LivePanel({ active: active2 }) {
|
|
|
995
1046
|
/* @__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" }),
|
|
996
1047
|
/* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: " \u2502" })
|
|
997
1048
|
] }),
|
|
1049
|
+
alerts.filter((a) => nowMs < a.until).slice(-1).map((a) => /* @__PURE__ */ jsx2(Text2, { wrap: "truncate", backgroundColor: "#3d2a12", color: "#ffb454", bold: true, children: ` \u23F8 IDLE \xB7 ${truncate(a.msg, innerW - 12)} ` }, a.id)),
|
|
998
1050
|
/* @__PURE__ */ jsxs2(Box2, { height: 1 + chartH, children: [
|
|
999
1051
|
/* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", width: leftW, marginRight: 2, children: [
|
|
1000
1052
|
/* @__PURE__ */ jsx2(PaneTitle, { label: "TRAFFIC", extra: `${trafficLabel} \xB7 peak ${Math.max(0, ...traffic)} \xB7 red = denials`, width: leftW }),
|
|
@@ -1032,14 +1084,14 @@ function LivePanel({ active: active2 }) {
|
|
|
1032
1084
|
/* @__PURE__ */ jsx2(PaneTitle, { label: "SESSIONS", extra: mode === "pick" ? "\u2191\u2193 pick \xB7 enter open" : `${combinedSess.length} \xB7 s = inspect`, width: colW }),
|
|
1033
1085
|
pickable.length === 0 ? /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: "scanning\u2026" }) : null,
|
|
1034
1086
|
pickable.map((r, i) => {
|
|
1035
|
-
const freshDot = nowMs - r.lastAt < 9e4;
|
|
1036
1087
|
const sel = mode === "pick" && i === pickIdx;
|
|
1088
|
+
const st = STATUS_STYLE[r.status];
|
|
1037
1089
|
return /* @__PURE__ */ jsxs2(Text2, { wrap: "truncate", backgroundColor: sel ? "#1c2f63" : void 0, bold: sel || r.isMe, children: [
|
|
1038
|
-
/* @__PURE__ */ jsxs2(Text2, { color: sel ? theme.accentBright :
|
|
1039
|
-
sel ? "\u25B8" :
|
|
1090
|
+
/* @__PURE__ */ jsxs2(Text2, { color: sel ? theme.accentBright : st.color, children: [
|
|
1091
|
+
sel ? "\u25B8" : st.dot,
|
|
1040
1092
|
" "
|
|
1041
1093
|
] }),
|
|
1042
|
-
/* @__PURE__ */ jsx2(Text2, { color:
|
|
1094
|
+
/* @__PURE__ */ jsx2(Text2, { color: st.color, children: st.label.padEnd(7) }),
|
|
1043
1095
|
/* @__PURE__ */ jsx2(Text2, { color: theme.accentBright, children: truncate(r.isMe ? "this machine" : r.agent, 12).padEnd(13) }),
|
|
1044
1096
|
/* @__PURE__ */ jsxs2(Text2, { children: [
|
|
1045
1097
|
String(r.calls).padStart(4),
|
|
@@ -1073,7 +1125,7 @@ function LivePanel({ active: active2 }) {
|
|
|
1073
1125
|
width: innerW
|
|
1074
1126
|
}
|
|
1075
1127
|
),
|
|
1076
|
-
/* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", height: streamRows, overflow: "hidden", children: [
|
|
1128
|
+
/* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", height: searchRow ? streamRows - 1 : streamRows, overflow: "hidden", children: [
|
|
1077
1129
|
searchRow,
|
|
1078
1130
|
localOn === false ? /* @__PURE__ */ jsxs2(Text2, { wrap: "truncate", children: [
|
|
1079
1131
|
/* @__PURE__ */ jsx2(Text2, { color: theme.warn, children: "local logs off" }),
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@solongate/proxy",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.76.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": {
|