@relayplane/proxy 1.9.20 → 1.9.21
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/agent-policy.d.ts +54 -0
- package/dist/agent-policy.d.ts.map +1 -0
- package/dist/agent-policy.js +183 -0
- package/dist/agent-policy.js.map +1 -0
- package/dist/cli.js +722 -3
- package/dist/cli.js.map +1 -1
- package/dist/config.d.ts +6 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js.map +1 -1
- package/dist/policy-analyzer.d.ts +41 -0
- package/dist/policy-analyzer.d.ts.map +1 -0
- package/dist/policy-analyzer.js +268 -0
- package/dist/policy-analyzer.js.map +1 -0
- package/dist/policy-suggestions.d.ts +31 -0
- package/dist/policy-suggestions.d.ts.map +1 -0
- package/dist/policy-suggestions.js +173 -0
- package/dist/policy-suggestions.js.map +1 -0
- package/dist/routing-log.d.ts +47 -0
- package/dist/routing-log.d.ts.map +1 -0
- package/dist/routing-log.js +141 -0
- package/dist/routing-log.js.map +1 -0
- package/dist/standalone-proxy.d.ts.map +1 -1
- package/dist/standalone-proxy.js +111 -0
- package/dist/standalone-proxy.js.map +1 -1
- package/dist/telemetryPinger.d.ts +2 -0
- package/dist/telemetryPinger.d.ts.map +1 -0
- package/dist/telemetryPinger.js +80 -0
- package/dist/telemetryPinger.js.map +1 -0
- package/package.json +4 -2
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Routing Log
|
|
4
|
+
*
|
|
5
|
+
* In-memory ring buffer (1000 entries) with JSONL persistence at
|
|
6
|
+
* ~/.relayplane/routing-log.jsonl. Tracks all routing decisions for
|
|
7
|
+
* observability and debugging.
|
|
8
|
+
*/
|
|
9
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
12
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
13
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
14
|
+
}
|
|
15
|
+
Object.defineProperty(o, k2, desc);
|
|
16
|
+
}) : (function(o, m, k, k2) {
|
|
17
|
+
if (k2 === undefined) k2 = k;
|
|
18
|
+
o[k2] = m[k];
|
|
19
|
+
}));
|
|
20
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
21
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
22
|
+
}) : function(o, v) {
|
|
23
|
+
o["default"] = v;
|
|
24
|
+
});
|
|
25
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
26
|
+
var ownKeys = function(o) {
|
|
27
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
28
|
+
var ar = [];
|
|
29
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
30
|
+
return ar;
|
|
31
|
+
};
|
|
32
|
+
return ownKeys(o);
|
|
33
|
+
};
|
|
34
|
+
return function (mod) {
|
|
35
|
+
if (mod && mod.__esModule) return mod;
|
|
36
|
+
var result = {};
|
|
37
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
38
|
+
__setModuleDefault(result, mod);
|
|
39
|
+
return result;
|
|
40
|
+
};
|
|
41
|
+
})();
|
|
42
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
43
|
+
exports.LOG_FILE = void 0;
|
|
44
|
+
exports._resetRoutingLog = _resetRoutingLog;
|
|
45
|
+
exports.initRoutingLog = initRoutingLog;
|
|
46
|
+
exports.appendRoutingLog = appendRoutingLog;
|
|
47
|
+
exports.getRoutingLog = getRoutingLog;
|
|
48
|
+
exports.flushRoutingLog = flushRoutingLog;
|
|
49
|
+
const fs = __importStar(require("node:fs"));
|
|
50
|
+
const path = __importStar(require("node:path"));
|
|
51
|
+
const os = __importStar(require("node:os"));
|
|
52
|
+
// ─── Constants ────────────────────────────────────────────────────────────────
|
|
53
|
+
const LOG_DIR = path.join(os.homedir(), '.relayplane');
|
|
54
|
+
exports.LOG_FILE = path.join(LOG_DIR, 'routing-log.jsonl');
|
|
55
|
+
const BAK_FILE = path.join(LOG_DIR, 'routing-log.jsonl.bak');
|
|
56
|
+
const MAX_ENTRIES = 1000;
|
|
57
|
+
const MAX_FILE_SIZE = 10 * 1024 * 1024; // 10 MB
|
|
58
|
+
// ─── State ────────────────────────────────────────────────────────────────────
|
|
59
|
+
let _buffer = [];
|
|
60
|
+
/** Reset for testing */
|
|
61
|
+
function _resetRoutingLog() {
|
|
62
|
+
_buffer = [];
|
|
63
|
+
}
|
|
64
|
+
// ─── Public API ───────────────────────────────────────────────────────────────
|
|
65
|
+
/**
|
|
66
|
+
* Initialize routing log by loading the last 1000 lines from file.
|
|
67
|
+
* Called once on proxy startup.
|
|
68
|
+
*/
|
|
69
|
+
function initRoutingLog() {
|
|
70
|
+
if (!fs.existsSync(exports.LOG_FILE))
|
|
71
|
+
return;
|
|
72
|
+
try {
|
|
73
|
+
const content = fs.readFileSync(exports.LOG_FILE, 'utf-8');
|
|
74
|
+
const lines = content.split('\n').filter(l => l.trim().length > 0);
|
|
75
|
+
const last = lines.slice(-MAX_ENTRIES);
|
|
76
|
+
_buffer = [];
|
|
77
|
+
for (const line of last) {
|
|
78
|
+
try {
|
|
79
|
+
const entry = JSON.parse(line);
|
|
80
|
+
_buffer.push(entry);
|
|
81
|
+
}
|
|
82
|
+
catch {
|
|
83
|
+
// Skip malformed lines
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
catch {
|
|
88
|
+
// Non-critical — start with empty buffer
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Append a routing decision to the in-memory buffer and JSONL file.
|
|
93
|
+
*/
|
|
94
|
+
function appendRoutingLog(entry) {
|
|
95
|
+
// Ring buffer: drop oldest when full
|
|
96
|
+
if (_buffer.length >= MAX_ENTRIES) {
|
|
97
|
+
_buffer.shift();
|
|
98
|
+
}
|
|
99
|
+
_buffer.push(entry);
|
|
100
|
+
// Persist to file
|
|
101
|
+
try {
|
|
102
|
+
fs.mkdirSync(LOG_DIR, { recursive: true });
|
|
103
|
+
// Rotate if file is too large
|
|
104
|
+
try {
|
|
105
|
+
const stat = fs.statSync(exports.LOG_FILE);
|
|
106
|
+
if (stat.size > MAX_FILE_SIZE) {
|
|
107
|
+
fs.renameSync(exports.LOG_FILE, BAK_FILE);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
catch {
|
|
111
|
+
// File may not exist yet — that's fine
|
|
112
|
+
}
|
|
113
|
+
fs.appendFileSync(exports.LOG_FILE, JSON.stringify(entry) + '\n', 'utf-8');
|
|
114
|
+
}
|
|
115
|
+
catch {
|
|
116
|
+
// Non-critical — don't break the proxy
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* Get routing log entries from the in-memory buffer with optional filters.
|
|
121
|
+
*/
|
|
122
|
+
function getRoutingLog(opts) {
|
|
123
|
+
let entries = _buffer.slice();
|
|
124
|
+
if (opts?.agentFingerprint) {
|
|
125
|
+
const fp = opts.agentFingerprint;
|
|
126
|
+
entries = entries.filter(e => e.agentFingerprint === fp || e.agentName === fp);
|
|
127
|
+
}
|
|
128
|
+
if (opts?.taskType) {
|
|
129
|
+
const tt = opts.taskType;
|
|
130
|
+
entries = entries.filter(e => e.taskType === tt);
|
|
131
|
+
}
|
|
132
|
+
const limit = Math.min(opts?.limit ?? 100, MAX_ENTRIES);
|
|
133
|
+
return entries.slice(-limit);
|
|
134
|
+
}
|
|
135
|
+
/**
|
|
136
|
+
* No-op flush — writes are synchronous. Called for symmetry on shutdown.
|
|
137
|
+
*/
|
|
138
|
+
function flushRoutingLog() {
|
|
139
|
+
// No-op: all writes are synchronous appends
|
|
140
|
+
}
|
|
141
|
+
//# sourceMappingURL=routing-log.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"routing-log.js","sourceRoot":"","sources":["../src/routing-log.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"standalone-proxy.d.ts","sourceRoot":"","sources":["../src/standalone-proxy.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAEH,OAAO,KAAK,IAAI,MAAM,WAAW,CAAC;AAOlC,OAAO,KAAK,EAAE,QAAQ,IAAI,YAAY,EAAY,MAAM,kBAAkB,CAAC;AAE3E,KAAK,QAAQ,GAAG,YAAY,GACxB,YAAY,GACZ,UAAU,GACV,MAAM,GACN,SAAS,GACT,UAAU,GACV,WAAW,GACX,YAAY,GACZ,QAAQ,CAAC;AAOb,OAAO,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;
|
|
1
|
+
{"version":3,"file":"standalone-proxy.d.ts","sourceRoot":"","sources":["../src/standalone-proxy.ts"],"names":[],"mappings":"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"}
|
package/dist/standalone-proxy.js
CHANGED
|
@@ -84,10 +84,13 @@ const anomaly_js_1 = require("./anomaly.js");
|
|
|
84
84
|
const alerts_js_1 = require("./alerts.js");
|
|
85
85
|
const downgrade_js_1 = require("./downgrade.js");
|
|
86
86
|
const agent_tracker_js_1 = require("./agent-tracker.js");
|
|
87
|
+
const routing_log_js_1 = require("./routing-log.js");
|
|
88
|
+
const agent_policy_js_1 = require("./agent-policy.js");
|
|
87
89
|
const version_status_js_1 = require("./utils/version-status.js");
|
|
88
90
|
const signup_nudge_js_1 = require("./signup-nudge.js");
|
|
89
91
|
const star_nudge_js_1 = require("./star-nudge.js");
|
|
90
92
|
const estimate_js_1 = require("./estimate.js");
|
|
93
|
+
const telemetryPinger_js_1 = require("./telemetryPinger.js");
|
|
91
94
|
// Per-IP rate limit state for /v1/estimate (60 req/min per IP)
|
|
92
95
|
const estimateRateMap = new Map();
|
|
93
96
|
// Fix A: Purge expired rate-limit entries every 5 minutes to prevent memory leak.
|
|
@@ -1088,11 +1091,13 @@ function extractPromptText(messages) {
|
|
|
1088
1091
|
if (!messages || !Array.isArray(messages))
|
|
1089
1092
|
return '';
|
|
1090
1093
|
return messages
|
|
1094
|
+
.filter((msg) => Boolean(msg))
|
|
1091
1095
|
.map((msg) => {
|
|
1092
1096
|
if (typeof msg.content === 'string')
|
|
1093
1097
|
return msg.content;
|
|
1094
1098
|
if (Array.isArray(msg.content)) {
|
|
1095
1099
|
return msg.content
|
|
1100
|
+
.filter((c) => Boolean(c))
|
|
1096
1101
|
.map((c) => {
|
|
1097
1102
|
const part = c;
|
|
1098
1103
|
return part.type === 'text' ? (part.text ?? '') : '';
|
|
@@ -1105,12 +1110,14 @@ function extractPromptText(messages) {
|
|
|
1105
1110
|
}
|
|
1106
1111
|
function extractMessageText(messages) {
|
|
1107
1112
|
return messages
|
|
1113
|
+
.filter((msg) => Boolean(msg))
|
|
1108
1114
|
.map((msg) => {
|
|
1109
1115
|
const content = msg.content;
|
|
1110
1116
|
if (typeof content === 'string')
|
|
1111
1117
|
return content;
|
|
1112
1118
|
if (Array.isArray(content)) {
|
|
1113
1119
|
return content
|
|
1120
|
+
.filter((c) => Boolean(c))
|
|
1114
1121
|
.map((c) => {
|
|
1115
1122
|
const part = c;
|
|
1116
1123
|
return part.type === 'text' ? (part.text ?? '') : '';
|
|
@@ -2627,6 +2634,25 @@ td{padding:8px 12px;border-bottom:1px solid #111318}
|
|
|
2627
2634
|
.rename-btn{background:none;border:none;cursor:pointer;font-size:.75rem;opacity:.5;padding:2px}.rename-btn:hover{opacity:1}
|
|
2628
2635
|
</style></head><body>
|
|
2629
2636
|
<div class="header"><div><h1>⚡ RelayPlane Dashboard</h1></div><div class="meta"><a href="/dashboard/config">Config</a> · <span id="ver"></span><span id="vstat" class="vstat unavailable">Unable to check</span> · up <span id="uptime"></span> · refreshes every 5s</div></div>
|
|
2637
|
+
<div id="policy-nudge" style="display:none;background:#1a1a2e;border:1px solid #4a9eff;border-radius:6px;padding:12px 16px;margin-bottom:16px;display:flex;align-items:center;justify-content:space-between;font-family:monospace;font-size:13px;color:#e0e0e0"><span>You've routed <strong id="nudge-reqs">0</strong> requests across <strong id="nudge-agents">0</strong> detected agent<span id="nudge-plural">s</span>. Run <code>relayplane policy auto</code> to optimize routing. Estimated savings: ~<strong id="nudge-savings">$0</strong>/mo.</span><div style="display:flex;gap:8px;margin-left:16px"><button onclick="fetch('/v1/policy-auto',{method:'POST'}).then(()=>location.reload())" style="background:#4a9eff;color:#000;border:none;padding:4px 12px;border-radius:4px;cursor:pointer;font-size:12px">Run now</button><button onclick="document.getElementById('policy-nudge').style.display='none';localStorage.setItem('nudge-dismissed','1')" style="background:transparent;color:#888;border:1px solid #444;padding:4px 8px;border-radius:4px;cursor:pointer;font-size:12px">Dismiss</button></div></div>
|
|
2638
|
+
<script>
|
|
2639
|
+
(async function(){
|
|
2640
|
+
if(localStorage.getItem('nudge-dismissed'))return;
|
|
2641
|
+
try{
|
|
2642
|
+
const n=await fetch('/v1/policy-nudge').then(r=>r.json());
|
|
2643
|
+
if(n.show){
|
|
2644
|
+
const el=document.getElementById('policy-nudge');
|
|
2645
|
+
if(el){
|
|
2646
|
+
el.style.display='flex';
|
|
2647
|
+
document.getElementById('nudge-reqs').textContent=n.requestCount;
|
|
2648
|
+
document.getElementById('nudge-agents').textContent=n.agentCount;
|
|
2649
|
+
document.getElementById('nudge-plural').textContent=n.agentCount===1?'':'s';
|
|
2650
|
+
document.getElementById('nudge-savings').textContent='$'+n.estimatedMonthlySavings;
|
|
2651
|
+
}
|
|
2652
|
+
}
|
|
2653
|
+
}catch(e){}
|
|
2654
|
+
})();
|
|
2655
|
+
</script>
|
|
2630
2656
|
<div class="cards">
|
|
2631
2657
|
<div class="card"><div class="label">Requests (7d window, max 10k)</div><div class="value" id="totalReq">—</div><div id="totalReqDetail" style="font-size:.75rem;color:#64748b;margin-top:4px">—</div></div>
|
|
2632
2658
|
<div class="card"><div class="label">Total Cost</div><div class="value" id="totalCost">—</div></div>
|
|
@@ -3413,6 +3439,8 @@ async function startProxy(config = {}) {
|
|
|
3413
3439
|
return;
|
|
3414
3440
|
}
|
|
3415
3441
|
if (req.method === 'GET' && pathname === '/v1/version-status') {
|
|
3442
|
+
// This endpoint is hit by the dashboard, so trigger the dashboard ping.
|
|
3443
|
+
(0, telemetryPinger_js_1.sendPing)('dashboard');
|
|
3416
3444
|
const latest = await getLatestProxyVersion();
|
|
3417
3445
|
const status = (0, version_status_js_1.getVersionStatus)(PROXY_VERSION, latest);
|
|
3418
3446
|
res.writeHead(200, { 'Content-Type': 'application/json', 'Cache-Control': 'public, max-age=60' });
|
|
@@ -4043,6 +4071,66 @@ async function startProxy(config = {}) {
|
|
|
4043
4071
|
}));
|
|
4044
4072
|
return;
|
|
4045
4073
|
}
|
|
4074
|
+
// === Policy nudge endpoint ===
|
|
4075
|
+
if (req.method === 'GET' && pathname === '/v1/policy-nudge') {
|
|
4076
|
+
const log = (0, routing_log_js_1.getRoutingLog)({ limit: 100 });
|
|
4077
|
+
const agentCount = new Set(log.map(e => e.agentFingerprint).filter(Boolean)).size;
|
|
4078
|
+
const policy = (0, agent_policy_js_1.loadPolicy)();
|
|
4079
|
+
const policyActive = Object.keys(policy.agents ?? {}).length > 0;
|
|
4080
|
+
const show = log.length >= 10 && !policyActive;
|
|
4081
|
+
const totalCost = Object.values((0, agent_tracker_js_1.getAgentRegistry)()).reduce((sum, a) => sum + a.totalCost, 0);
|
|
4082
|
+
const estimatedMonthlySavings = show ? Math.round(totalCost * 0.4 * 30) : 0;
|
|
4083
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
4084
|
+
res.end(JSON.stringify({ show, requestCount: log.length, agentCount, estimatedMonthlySavings, policyActive }));
|
|
4085
|
+
return;
|
|
4086
|
+
}
|
|
4087
|
+
// === Policy auto endpoint (from dashboard "Run now" button) ===
|
|
4088
|
+
if (req.method === 'POST' && pathname === '/v1/policy-auto') {
|
|
4089
|
+
try {
|
|
4090
|
+
const { analyzeTraffic } = await import('./policy-analyzer.js');
|
|
4091
|
+
const { detectAvailableProviders, suggestPolicies } = await import('./policy-suggestions.js');
|
|
4092
|
+
const { dump: yamlDumpLocal } = await import('js-yaml');
|
|
4093
|
+
const analyses = await analyzeTraffic({ lookbackDays: 7 });
|
|
4094
|
+
if (analyses.length === 0) {
|
|
4095
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
4096
|
+
res.end(JSON.stringify({ success: false, error: 'No traffic data found' }));
|
|
4097
|
+
return;
|
|
4098
|
+
}
|
|
4099
|
+
const providers = detectAvailableProviders();
|
|
4100
|
+
const suggestions = suggestPolicies(analyses, providers);
|
|
4101
|
+
const isoDate = new Date().toISOString().slice(0, 10);
|
|
4102
|
+
const header = `# RelayPlane routing policy\n# Generated by dashboard on ${isoDate}\n# Edit manually or re-run \`relayplane policy auto\` to regenerate.\n\n`;
|
|
4103
|
+
const agents = {};
|
|
4104
|
+
for (let i = 0; i < analyses.length; i++) {
|
|
4105
|
+
const a = analyses[i];
|
|
4106
|
+
const s = suggestions[i];
|
|
4107
|
+
if (s.noSuggestion)
|
|
4108
|
+
continue;
|
|
4109
|
+
const entry = { fingerprint: a.fingerprint, preferred: s.suggestedModel };
|
|
4110
|
+
if (s.escalateTo)
|
|
4111
|
+
entry['escalateTo'] = s.escalateTo;
|
|
4112
|
+
if (s.escalateOn)
|
|
4113
|
+
entry['escalateOn'] = s.escalateOn;
|
|
4114
|
+
if (s.neverDowngrade)
|
|
4115
|
+
entry['neverDowngrade'] = true;
|
|
4116
|
+
agents[a.name] = entry;
|
|
4117
|
+
}
|
|
4118
|
+
const body = yamlDumpLocal({ version: 1, agents }, { lineWidth: 120 });
|
|
4119
|
+
fs.mkdirSync(path.dirname(agent_policy_js_1.POLICY_FILE), { recursive: true });
|
|
4120
|
+
const tmp = agent_policy_js_1.POLICY_FILE + '.tmp';
|
|
4121
|
+
fs.writeFileSync(tmp, header + body, 'utf-8');
|
|
4122
|
+
fs.renameSync(tmp, agent_policy_js_1.POLICY_FILE);
|
|
4123
|
+
const agentsConfigured = Object.keys(agents).length;
|
|
4124
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
4125
|
+
res.end(JSON.stringify({ success: true, agentsConfigured }));
|
|
4126
|
+
}
|
|
4127
|
+
catch (err) {
|
|
4128
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
4129
|
+
res.writeHead(500, { 'Content-Type': 'application/json' });
|
|
4130
|
+
res.end(JSON.stringify({ success: false, error: msg }));
|
|
4131
|
+
}
|
|
4132
|
+
return;
|
|
4133
|
+
}
|
|
4046
4134
|
if (req.method === 'GET' && pathname === '/v1/mesh/stats') {
|
|
4047
4135
|
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
4048
4136
|
res.end(JSON.stringify(meshHandle.getStats()));
|
|
@@ -5990,6 +6078,29 @@ async function startProxy(config = {}) {
|
|
|
5990
6078
|
console.log(` Streaming: ✅ Enabled`);
|
|
5991
6079
|
startWatchdog();
|
|
5992
6080
|
log('Health watchdog started (30s interval, sd_notify enabled)');
|
|
6081
|
+
// Fire startup ping.
|
|
6082
|
+
// Use setImmediate to ensure it runs after startup logs are printed.
|
|
6083
|
+
setImmediate(() => (0, telemetryPinger_js_1.sendPing)('startup'));
|
|
6084
|
+
// Policy nudge — check once, 5 seconds after startup, only if nudge conditions met
|
|
6085
|
+
setTimeout(async () => {
|
|
6086
|
+
try {
|
|
6087
|
+
const nudgeLog = (0, routing_log_js_1.getRoutingLog)({ limit: 100 });
|
|
6088
|
+
const nudgePolicy = (0, agent_policy_js_1.loadPolicy)();
|
|
6089
|
+
const nudgePolicyActive = Object.keys(nudgePolicy.agents ?? {}).length > 0;
|
|
6090
|
+
if (nudgeLog.length >= 10 && !nudgePolicyActive) {
|
|
6091
|
+
const agentCount = new Set(nudgeLog.map(e => e.agentFingerprint).filter(Boolean)).size;
|
|
6092
|
+
const totalCost = Object.values((0, agent_tracker_js_1.getAgentRegistry)()).reduce((s, a) => s + a.totalCost, 0);
|
|
6093
|
+
const savings = Math.round(totalCost * 0.4 * 30);
|
|
6094
|
+
console.log('');
|
|
6095
|
+
console.log(' ─────────────────────────────────────────────────────');
|
|
6096
|
+
console.log(` 💡 ${nudgeLog.length} requests routed across ${agentCount} agent${agentCount !== 1 ? 's' : ''}.`);
|
|
6097
|
+
console.log(` Run \`relayplane policy auto\` to optimize routing (~$${savings}/mo savings estimated).`);
|
|
6098
|
+
console.log(' ─────────────────────────────────────────────────────');
|
|
6099
|
+
console.log('');
|
|
6100
|
+
}
|
|
6101
|
+
}
|
|
6102
|
+
catch { /* non-critical */ }
|
|
6103
|
+
}, 5000);
|
|
5993
6104
|
resolve(server);
|
|
5994
6105
|
});
|
|
5995
6106
|
});
|