@solongate/proxy 0.75.1 → 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 +72 -24
- package/dist/tui/index.js +67 -19
- 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);
|
|
@@ -7507,10 +7544,13 @@ function LivePanel({ active: active2 }) {
|
|
|
7507
7544
|
const tl = d.slice(dScroll, dScroll + tlRows);
|
|
7508
7545
|
return /* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", paddingX: 1, children: [
|
|
7509
7546
|
titleBar,
|
|
7510
|
-
searchRow,
|
|
7511
7547
|
/* @__PURE__ */ jsxs2(Text2, { wrap: "truncate", children: [
|
|
7512
7548
|
/* @__PURE__ */ jsx2(Text2, { backgroundColor: BG, color: "#AAC8FA", bold: true, children: ` SESSION ${detail.id.slice(0, 8)} ` }),
|
|
7513
|
-
|
|
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() }),
|
|
7514
7554
|
/* @__PURE__ */ jsx2(Text2, { bold: true, children: " " + truncate2(detail.isMe ? detail.agent + " (this machine)" : detail.agent, 34) }),
|
|
7515
7555
|
detail.trust != null ? /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: ` trust ${detail.trust}/100` }) : null,
|
|
7516
7556
|
/* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: " \u2190 back" })
|
|
@@ -7541,6 +7581,7 @@ function LivePanel({ active: active2 }) {
|
|
|
7541
7581
|
] }),
|
|
7542
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 }),
|
|
7543
7583
|
/* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", height: tlRows, overflow: "hidden", children: [
|
|
7584
|
+
searchRow,
|
|
7544
7585
|
tl.length === 0 ? /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: detailCloud.length === 0 ? spin + " loading history\u2026" : "no entries" }) : null,
|
|
7545
7586
|
tl.map((e) => /* @__PURE__ */ jsx2(StreamLine, { e, loc: isLoc(e) }, e.id))
|
|
7546
7587
|
] }),
|
|
@@ -7574,7 +7615,7 @@ function LivePanel({ active: active2 }) {
|
|
|
7574
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" }),
|
|
7575
7616
|
/* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: " \u2502" })
|
|
7576
7617
|
] }),
|
|
7577
|
-
|
|
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)),
|
|
7578
7619
|
/* @__PURE__ */ jsxs2(Box2, { height: 1 + chartH, children: [
|
|
7579
7620
|
/* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", width: leftW, marginRight: 2, children: [
|
|
7580
7621
|
/* @__PURE__ */ jsx2(PaneTitle, { label: "TRAFFIC", extra: `${trafficLabel} \xB7 peak ${Math.max(0, ...traffic)} \xB7 red = denials`, width: leftW }),
|
|
@@ -7612,14 +7653,14 @@ function LivePanel({ active: active2 }) {
|
|
|
7612
7653
|
/* @__PURE__ */ jsx2(PaneTitle, { label: "SESSIONS", extra: mode === "pick" ? "\u2191\u2193 pick \xB7 enter open" : `${combinedSess.length} \xB7 s = inspect`, width: colW }),
|
|
7613
7654
|
pickable.length === 0 ? /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: "scanning\u2026" }) : null,
|
|
7614
7655
|
pickable.map((r, i) => {
|
|
7615
|
-
const freshDot = nowMs - r.lastAt < 9e4;
|
|
7616
7656
|
const sel = mode === "pick" && i === pickIdx;
|
|
7657
|
+
const st = STATUS_STYLE[r.status];
|
|
7617
7658
|
return /* @__PURE__ */ jsxs2(Text2, { wrap: "truncate", backgroundColor: sel ? "#1c2f63" : void 0, bold: sel || r.isMe, children: [
|
|
7618
|
-
/* @__PURE__ */ jsxs2(Text2, { color: sel ? theme.accentBright :
|
|
7619
|
-
sel ? "\u25B8" :
|
|
7659
|
+
/* @__PURE__ */ jsxs2(Text2, { color: sel ? theme.accentBright : st.color, children: [
|
|
7660
|
+
sel ? "\u25B8" : st.dot,
|
|
7620
7661
|
" "
|
|
7621
7662
|
] }),
|
|
7622
|
-
/* @__PURE__ */ jsx2(Text2, { color:
|
|
7663
|
+
/* @__PURE__ */ jsx2(Text2, { color: st.color, children: st.label.padEnd(7) }),
|
|
7623
7664
|
/* @__PURE__ */ jsx2(Text2, { color: theme.accentBright, children: truncate2(r.isMe ? "this machine" : r.agent, 12).padEnd(13) }),
|
|
7624
7665
|
/* @__PURE__ */ jsxs2(Text2, { children: [
|
|
7625
7666
|
String(r.calls).padStart(4),
|
|
@@ -7654,6 +7695,7 @@ function LivePanel({ active: active2 }) {
|
|
|
7654
7695
|
}
|
|
7655
7696
|
),
|
|
7656
7697
|
/* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", height: searchRow ? streamRows - 1 : streamRows, overflow: "hidden", children: [
|
|
7698
|
+
searchRow,
|
|
7657
7699
|
localOn === false ? /* @__PURE__ */ jsxs2(Text2, { wrap: "truncate", children: [
|
|
7658
7700
|
/* @__PURE__ */ jsx2(Text2, { color: theme.warn, children: "local logs off" }),
|
|
7659
7701
|
/* @__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`" })
|
|
@@ -7671,7 +7713,7 @@ function LivePanel({ active: active2 }) {
|
|
|
7671
7713
|
] })
|
|
7672
7714
|
] });
|
|
7673
7715
|
}
|
|
7674
|
-
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;
|
|
7675
7717
|
var init_Live = __esm({
|
|
7676
7718
|
"src/tui/panels/Live.tsx"() {
|
|
7677
7719
|
"use strict";
|
|
@@ -7694,6 +7736,12 @@ var init_Live = __esm({
|
|
|
7694
7736
|
return `${p(Math.floor(s / 3600))}:${p(Math.floor(s % 3600 / 60))}:${p(s % 60)}`;
|
|
7695
7737
|
};
|
|
7696
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
|
+
};
|
|
7697
7745
|
}
|
|
7698
7746
|
});
|
|
7699
7747
|
|
|
@@ -9635,7 +9683,7 @@ var init_global_install = __esm({
|
|
|
9635
9683
|
|
|
9636
9684
|
// src/login.ts
|
|
9637
9685
|
var login_exports = {};
|
|
9638
|
-
import { spawn } from "child_process";
|
|
9686
|
+
import { spawn as spawn2 } from "child_process";
|
|
9639
9687
|
function startSpinner(text) {
|
|
9640
9688
|
if (!process.stderr.isTTY) {
|
|
9641
9689
|
process.stderr.write(` ${text}
|
|
@@ -9671,7 +9719,7 @@ function openBrowser(url) {
|
|
|
9671
9719
|
try {
|
|
9672
9720
|
const cmd = process.platform === "win32" ? "cmd" : process.platform === "darwin" ? "open" : "xdg-open";
|
|
9673
9721
|
const args = process.platform === "win32" ? ["/c", "start", '""', url] : [url];
|
|
9674
|
-
const child =
|
|
9722
|
+
const child = spawn2(cmd, args, { stdio: "ignore", detached: true });
|
|
9675
9723
|
child.on("error", () => {
|
|
9676
9724
|
});
|
|
9677
9725
|
child.unref();
|
|
@@ -9790,7 +9838,7 @@ __export(shield_exports, {
|
|
|
9790
9838
|
});
|
|
9791
9839
|
import { createServer, request as httpRequest } from "http";
|
|
9792
9840
|
import { request as httpsRequest } from "https";
|
|
9793
|
-
import { spawn as
|
|
9841
|
+
import { spawn as spawn3 } from "child_process";
|
|
9794
9842
|
import { URL as URL2 } from "url";
|
|
9795
9843
|
import { readFileSync as readFileSync7, existsSync as existsSync5, readdirSync, statSync as statSync2 } from "fs";
|
|
9796
9844
|
import { resolve as resolve5 } from "path";
|
|
@@ -10038,7 +10086,7 @@ async function runShield() {
|
|
|
10038
10086
|
const upstream = pickUpstream();
|
|
10039
10087
|
const { port, close } = await startProxy(upstream);
|
|
10040
10088
|
log4(`redacting secrets on the LLM path \u2192 masking before ${upstream.host} (127.0.0.1:${port})`);
|
|
10041
|
-
const child =
|
|
10089
|
+
const child = spawn3(cmd[0], cmd.slice(1), {
|
|
10042
10090
|
stdio: "inherit",
|
|
10043
10091
|
env: { ...process.env, ANTHROPIC_BASE_URL: `http://127.0.0.1:${port}` },
|
|
10044
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);
|
|
@@ -932,10 +975,13 @@ function LivePanel({ active: active2 }) {
|
|
|
932
975
|
const tl = d.slice(dScroll, dScroll + tlRows);
|
|
933
976
|
return /* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", paddingX: 1, children: [
|
|
934
977
|
titleBar,
|
|
935
|
-
searchRow,
|
|
936
978
|
/* @__PURE__ */ jsxs2(Text2, { wrap: "truncate", children: [
|
|
937
979
|
/* @__PURE__ */ jsx2(Text2, { backgroundColor: BG, color: "#AAC8FA", bold: true, children: ` SESSION ${detail.id.slice(0, 8)} ` }),
|
|
938
|
-
|
|
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() }),
|
|
939
985
|
/* @__PURE__ */ jsx2(Text2, { bold: true, children: " " + truncate(detail.isMe ? detail.agent + " (this machine)" : detail.agent, 34) }),
|
|
940
986
|
detail.trust != null ? /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: ` trust ${detail.trust}/100` }) : null,
|
|
941
987
|
/* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: " \u2190 back" })
|
|
@@ -966,6 +1012,7 @@ function LivePanel({ active: active2 }) {
|
|
|
966
1012
|
] }),
|
|
967
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 }),
|
|
968
1014
|
/* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", height: tlRows, overflow: "hidden", children: [
|
|
1015
|
+
searchRow,
|
|
969
1016
|
tl.length === 0 ? /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: detailCloud.length === 0 ? spin + " loading history\u2026" : "no entries" }) : null,
|
|
970
1017
|
tl.map((e) => /* @__PURE__ */ jsx2(StreamLine, { e, loc: isLoc(e) }, e.id))
|
|
971
1018
|
] }),
|
|
@@ -999,7 +1046,7 @@ function LivePanel({ active: active2 }) {
|
|
|
999
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" }),
|
|
1000
1047
|
/* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: " \u2502" })
|
|
1001
1048
|
] }),
|
|
1002
|
-
|
|
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)),
|
|
1003
1050
|
/* @__PURE__ */ jsxs2(Box2, { height: 1 + chartH, children: [
|
|
1004
1051
|
/* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", width: leftW, marginRight: 2, children: [
|
|
1005
1052
|
/* @__PURE__ */ jsx2(PaneTitle, { label: "TRAFFIC", extra: `${trafficLabel} \xB7 peak ${Math.max(0, ...traffic)} \xB7 red = denials`, width: leftW }),
|
|
@@ -1037,14 +1084,14 @@ function LivePanel({ active: active2 }) {
|
|
|
1037
1084
|
/* @__PURE__ */ jsx2(PaneTitle, { label: "SESSIONS", extra: mode === "pick" ? "\u2191\u2193 pick \xB7 enter open" : `${combinedSess.length} \xB7 s = inspect`, width: colW }),
|
|
1038
1085
|
pickable.length === 0 ? /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: "scanning\u2026" }) : null,
|
|
1039
1086
|
pickable.map((r, i) => {
|
|
1040
|
-
const freshDot = nowMs - r.lastAt < 9e4;
|
|
1041
1087
|
const sel = mode === "pick" && i === pickIdx;
|
|
1088
|
+
const st = STATUS_STYLE[r.status];
|
|
1042
1089
|
return /* @__PURE__ */ jsxs2(Text2, { wrap: "truncate", backgroundColor: sel ? "#1c2f63" : void 0, bold: sel || r.isMe, children: [
|
|
1043
|
-
/* @__PURE__ */ jsxs2(Text2, { color: sel ? theme.accentBright :
|
|
1044
|
-
sel ? "\u25B8" :
|
|
1090
|
+
/* @__PURE__ */ jsxs2(Text2, { color: sel ? theme.accentBright : st.color, children: [
|
|
1091
|
+
sel ? "\u25B8" : st.dot,
|
|
1045
1092
|
" "
|
|
1046
1093
|
] }),
|
|
1047
|
-
/* @__PURE__ */ jsx2(Text2, { color:
|
|
1094
|
+
/* @__PURE__ */ jsx2(Text2, { color: st.color, children: st.label.padEnd(7) }),
|
|
1048
1095
|
/* @__PURE__ */ jsx2(Text2, { color: theme.accentBright, children: truncate(r.isMe ? "this machine" : r.agent, 12).padEnd(13) }),
|
|
1049
1096
|
/* @__PURE__ */ jsxs2(Text2, { children: [
|
|
1050
1097
|
String(r.calls).padStart(4),
|
|
@@ -1079,6 +1126,7 @@ function LivePanel({ active: active2 }) {
|
|
|
1079
1126
|
}
|
|
1080
1127
|
),
|
|
1081
1128
|
/* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", height: searchRow ? streamRows - 1 : streamRows, overflow: "hidden", children: [
|
|
1129
|
+
searchRow,
|
|
1082
1130
|
localOn === false ? /* @__PURE__ */ jsxs2(Text2, { wrap: "truncate", children: [
|
|
1083
1131
|
/* @__PURE__ */ jsx2(Text2, { color: theme.warn, children: "local logs off" }),
|
|
1084
1132
|
/* @__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`" })
|
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": {
|