@agent-e/server 1.8.0 → 1.8.2
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/README.md +54 -0
- package/dist/AgentEServer-A7K6426K.mjs +7 -0
- package/dist/{chunk-XFI27OM4.mjs → chunk-3HGNFK4A.mjs} +243 -53
- package/dist/chunk-3HGNFK4A.mjs.map +1 -0
- package/dist/cli.js +241 -51
- package/dist/cli.js.map +1 -1
- package/dist/cli.mjs +1 -1
- package/dist/index.d.mts +2 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +251 -53
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +2 -2
- package/package.json +1 -1
- package/dist/AgentEServer-O3DNPERG.mjs +0 -7
- package/dist/chunk-XFI27OM4.mjs.map +0 -1
- /package/dist/{AgentEServer-O3DNPERG.mjs.map → AgentEServer-A7K6426K.mjs.map} +0 -0
package/dist/index.d.mts
CHANGED
|
@@ -23,6 +23,8 @@ declare class AgentEServer {
|
|
|
23
23
|
private lastState;
|
|
24
24
|
private adjustmentQueue;
|
|
25
25
|
private alerts;
|
|
26
|
+
/** Serialization lock for processTick — prevents concurrent ticks from corrupting shared state. */
|
|
27
|
+
private tickLock;
|
|
26
28
|
readonly port: number;
|
|
27
29
|
private readonly host;
|
|
28
30
|
private readonly thresholds;
|
package/dist/index.d.ts
CHANGED
|
@@ -23,6 +23,8 @@ declare class AgentEServer {
|
|
|
23
23
|
private lastState;
|
|
24
24
|
private adjustmentQueue;
|
|
25
25
|
private alerts;
|
|
26
|
+
/** Serialization lock for processTick — prevents concurrent ticks from corrupting shared state. */
|
|
27
|
+
private tickLock;
|
|
26
28
|
readonly port: number;
|
|
27
29
|
private readonly host;
|
|
28
30
|
private readonly thresholds;
|
package/dist/index.js
CHANGED
|
@@ -31,7 +31,8 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
|
|
|
31
31
|
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
32
32
|
|
|
33
33
|
// src/dashboard.ts
|
|
34
|
-
function getDashboardHtml() {
|
|
34
|
+
function getDashboardHtml(nonce) {
|
|
35
|
+
const nonceAttr = nonce ? ` nonce="${nonce}"` : "";
|
|
35
36
|
return `<!DOCTYPE html>
|
|
36
37
|
<html lang="en">
|
|
37
38
|
<head>
|
|
@@ -41,7 +42,7 @@ function getDashboardHtml() {
|
|
|
41
42
|
<link rel="preconnect" href="https://fonts.googleapis.com">
|
|
42
43
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
|
43
44
|
<link href="https://fonts.googleapis.com/css2?family=Inter:opsz,wght@14..32,400;14..32,500;14..32,600;14..32,700&family=JetBrains+Mono:wght@400;500&display=swap" rel="stylesheet">
|
|
44
|
-
<script src="https://cdn.jsdelivr.net/npm/chart.js@4.5.1/dist/chart.umd.min.js"
|
|
45
|
+
<script src="https://cdn.jsdelivr.net/npm/chart.js@4.5.1/dist/chart.umd.min.js" integrity="sha384-jb8JQMbMoBUzgWatfe6COACi2ljcDdZQ2OxczGA3bGNeWe+6DChMTBJemed7ZnvJ" crossorigin="anonymous"` + nonceAttr + `></script>
|
|
45
46
|
<style>
|
|
46
47
|
*, *::before, *::after { margin: 0; padding: 0; box-sizing: border-box; }
|
|
47
48
|
|
|
@@ -339,6 +340,15 @@ function getDashboardHtml() {
|
|
|
339
340
|
.t-pending-icon { color: var(--warning); }
|
|
340
341
|
.t-pending-val { color: var(--warning); font-variant-numeric: tabular-nums; }
|
|
341
342
|
|
|
343
|
+
/* -- LLM line colors (V1.8.1) -- */
|
|
344
|
+
.t-narration-icon { color: #a78bfa; }
|
|
345
|
+
.t-narration { color: #c4b5fd; }
|
|
346
|
+
.t-explanation-icon { color: #60a5fa; }
|
|
347
|
+
.t-explanation { color: #93c5fd; }
|
|
348
|
+
.t-anomaly-icon { color: #f59e0b; }
|
|
349
|
+
.t-anomaly-label { color: #fbbf24; }
|
|
350
|
+
.t-anomaly { color: #fcd34d; }
|
|
351
|
+
|
|
342
352
|
/* -- Advisor Inline Buttons -- */
|
|
343
353
|
.advisor-btn {
|
|
344
354
|
display: none;
|
|
@@ -717,7 +727,7 @@ function getDashboardHtml() {
|
|
|
717
727
|
<header class="header" id="header">
|
|
718
728
|
<div class="header-left">
|
|
719
729
|
<span class="header-logo">AgentE</span>
|
|
720
|
-
<span class="header-version">v1.8.
|
|
730
|
+
<span class="header-version">v1.8.1</span>
|
|
721
731
|
</div>
|
|
722
732
|
<div class="header-right">
|
|
723
733
|
<div class="kpi-pill">
|
|
@@ -853,7 +863,7 @@ function getDashboardHtml() {
|
|
|
853
863
|
|
|
854
864
|
</main>
|
|
855
865
|
|
|
856
|
-
<script
|
|
866
|
+
<script` + nonceAttr + `>
|
|
857
867
|
(function() {
|
|
858
868
|
'use strict';
|
|
859
869
|
|
|
@@ -900,6 +910,9 @@ function getDashboardHtml() {
|
|
|
900
910
|
var $dashboardRoot = document.getElementById('dashboard-root');
|
|
901
911
|
|
|
902
912
|
// -- Helpers --
|
|
913
|
+
// SECURITY-CRITICAL: esc() prevents XSS by escaping all HTML-significant characters.
|
|
914
|
+
// Every user-derived or WebSocket-derived value rendered via innerHTML MUST pass through esc().
|
|
915
|
+
// If adding new terminal renderers or DOM builders, always use esc() on dynamic data.
|
|
903
916
|
function esc(s) { return String(s).replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>').replace(/"/g,'"').replace(/'/g,''').replace(/\\\\/g,'\'); }
|
|
904
917
|
function pad(n, w) { return String(n).padStart(w || 4, ' '); }
|
|
905
918
|
function fmt(n) { return typeof n === 'number' ? n.toFixed(3) : '\\u2014'; }
|
|
@@ -1055,6 +1068,31 @@ function getDashboardHtml() {
|
|
|
1055
1068
|
+ advisorBtns;
|
|
1056
1069
|
}
|
|
1057
1070
|
|
|
1071
|
+
// -- LLM line renderers (V1.8.1) --
|
|
1072
|
+
function narrationToTerminal(msg) {
|
|
1073
|
+
return '<span class="t-tick">[Tick ' + pad(msg.tick) + ']</span> '
|
|
1074
|
+
+ '<span class="t-narration-icon">\\u{1F9E0} </span>'
|
|
1075
|
+
+ '<span class="t-narration">"' + esc(msg.text) + '"</span>';
|
|
1076
|
+
}
|
|
1077
|
+
|
|
1078
|
+
function explanationToTerminal(msg) {
|
|
1079
|
+
return '<span class="t-tick">[Tick ' + pad(msg.tick) + ']</span> '
|
|
1080
|
+
+ '<span class="t-explanation-icon">\\u{1F4A1} </span>'
|
|
1081
|
+
+ '<span class="t-explanation">"' + esc(msg.text) + '"</span>';
|
|
1082
|
+
}
|
|
1083
|
+
|
|
1084
|
+
function anomalyToTerminal(msg) {
|
|
1085
|
+
var metricsStr = '';
|
|
1086
|
+
if (msg.metrics && msg.metrics.length > 0) {
|
|
1087
|
+
var m = msg.metrics[0];
|
|
1088
|
+
metricsStr = m.name + ' ' + m.deviation.toFixed(1) + '\\u03C3 \u2014 ';
|
|
1089
|
+
}
|
|
1090
|
+
return '<span class="t-tick">[Tick ' + pad(msg.tick) + ']</span> '
|
|
1091
|
+
+ '<span class="t-anomaly-icon">\\u{1F50D} </span>'
|
|
1092
|
+
+ '<span class="t-anomaly-label">Anomaly detected: </span>'
|
|
1093
|
+
+ '<span class="t-anomaly">' + esc(metricsStr) + '"' + esc(msg.text) + '"</span>';
|
|
1094
|
+
}
|
|
1095
|
+
|
|
1058
1096
|
// -- Alerts --
|
|
1059
1097
|
function renderAlerts(alerts) {
|
|
1060
1098
|
if (!alerts || alerts.length === 0) {
|
|
@@ -1279,14 +1317,25 @@ function getDashboardHtml() {
|
|
|
1279
1317
|
}
|
|
1280
1318
|
|
|
1281
1319
|
// -- API calls --
|
|
1320
|
+
// Read token from URL query param \u2014 never embed the API key in the HTML body.
|
|
1321
|
+
var _authKey = new URLSearchParams(location.search).get('token');
|
|
1322
|
+
|
|
1323
|
+
function authHeaders() {
|
|
1324
|
+
var h = {};
|
|
1325
|
+
if (_authKey) h['Authorization'] = 'Bearer ' + _authKey;
|
|
1326
|
+
return h;
|
|
1327
|
+
}
|
|
1328
|
+
|
|
1282
1329
|
function fetchJSON(path) {
|
|
1283
|
-
return fetch(path).then(function(r) { return r.json(); });
|
|
1330
|
+
return fetch(path, { headers: authHeaders() }).then(function(r) { return r.json(); });
|
|
1284
1331
|
}
|
|
1285
1332
|
|
|
1286
1333
|
function postJSON(path, body) {
|
|
1334
|
+
var h = authHeaders();
|
|
1335
|
+
h['Content-Type'] = 'application/json';
|
|
1287
1336
|
return fetch(path, {
|
|
1288
1337
|
method: 'POST',
|
|
1289
|
-
headers:
|
|
1338
|
+
headers: h,
|
|
1290
1339
|
body: JSON.stringify(body),
|
|
1291
1340
|
}).then(function(r) { return r.json(); });
|
|
1292
1341
|
}
|
|
@@ -1348,7 +1397,9 @@ function getDashboardHtml() {
|
|
|
1348
1397
|
// -- WebSocket --
|
|
1349
1398
|
function connectWS() {
|
|
1350
1399
|
var proto = location.protocol === 'https:' ? 'wss:' : 'ws:';
|
|
1351
|
-
|
|
1400
|
+
var wsUrl = proto + '//' + location.host;
|
|
1401
|
+
if (_authKey) wsUrl += '?token=' + encodeURIComponent(_authKey);
|
|
1402
|
+
ws = new WebSocket(wsUrl);
|
|
1352
1403
|
|
|
1353
1404
|
ws.onopen = function() {
|
|
1354
1405
|
reconnectDelay = 1000;
|
|
@@ -1394,6 +1445,19 @@ function getDashboardHtml() {
|
|
|
1394
1445
|
$hPending.textContent = pendingDecisions.length;
|
|
1395
1446
|
}
|
|
1396
1447
|
break;
|
|
1448
|
+
|
|
1449
|
+
// V1.8.1: LLM feed events
|
|
1450
|
+
case 'narration':
|
|
1451
|
+
addTerminalLine(narrationToTerminal(msg));
|
|
1452
|
+
break;
|
|
1453
|
+
|
|
1454
|
+
case 'explanation':
|
|
1455
|
+
addTerminalLine(explanationToTerminal(msg));
|
|
1456
|
+
break;
|
|
1457
|
+
|
|
1458
|
+
case 'anomaly':
|
|
1459
|
+
addTerminalLine(anomalyToTerminal(msg));
|
|
1460
|
+
break;
|
|
1397
1461
|
}
|
|
1398
1462
|
};
|
|
1399
1463
|
}
|
|
@@ -1490,11 +1554,36 @@ var init_dashboard = __esm({
|
|
|
1490
1554
|
}
|
|
1491
1555
|
});
|
|
1492
1556
|
|
|
1557
|
+
// src/validation.ts
|
|
1558
|
+
function validateEvent(e) {
|
|
1559
|
+
if (!e || typeof e !== "object") return false;
|
|
1560
|
+
const ev = e;
|
|
1561
|
+
return typeof ev["type"] === "string" && VALID_EVENT_TYPES.has(ev["type"]) && typeof ev["timestamp"] === "number" && typeof ev["actor"] === "string";
|
|
1562
|
+
}
|
|
1563
|
+
var VALID_EVENT_TYPES;
|
|
1564
|
+
var init_validation = __esm({
|
|
1565
|
+
"src/validation.ts"() {
|
|
1566
|
+
"use strict";
|
|
1567
|
+
VALID_EVENT_TYPES = /* @__PURE__ */ new Set([
|
|
1568
|
+
"trade",
|
|
1569
|
+
"mint",
|
|
1570
|
+
"burn",
|
|
1571
|
+
"transfer",
|
|
1572
|
+
"produce",
|
|
1573
|
+
"consume",
|
|
1574
|
+
"role_change",
|
|
1575
|
+
"enter",
|
|
1576
|
+
"churn"
|
|
1577
|
+
]);
|
|
1578
|
+
}
|
|
1579
|
+
});
|
|
1580
|
+
|
|
1493
1581
|
// src/routes.ts
|
|
1494
1582
|
function setSecurityHeaders(res) {
|
|
1495
1583
|
res.setHeader("X-Content-Type-Options", "nosniff");
|
|
1496
1584
|
res.setHeader("X-Frame-Options", "DENY");
|
|
1497
1585
|
res.setHeader("Referrer-Policy", "strict-origin-when-cross-origin");
|
|
1586
|
+
res.setHeader("Strict-Transport-Security", "max-age=31536000; includeSubDomains");
|
|
1498
1587
|
}
|
|
1499
1588
|
function setCorsHeaders(res, allowedOrigin, requestOrigin) {
|
|
1500
1589
|
setSecurityHeaders(res);
|
|
@@ -1539,17 +1628,28 @@ function readBody(req) {
|
|
|
1539
1628
|
return new Promise((resolve, reject) => {
|
|
1540
1629
|
const chunks = [];
|
|
1541
1630
|
let totalBytes = 0;
|
|
1631
|
+
const timeout = setTimeout(() => {
|
|
1632
|
+
req.destroy();
|
|
1633
|
+
reject(new Error("Request body read timeout"));
|
|
1634
|
+
}, READ_BODY_TIMEOUT_MS);
|
|
1542
1635
|
req.on("data", (chunk) => {
|
|
1543
1636
|
totalBytes += chunk.length;
|
|
1544
1637
|
if (totalBytes > MAX_BODY_BYTES) {
|
|
1638
|
+
clearTimeout(timeout);
|
|
1545
1639
|
req.destroy();
|
|
1546
1640
|
reject(new Error("Request body too large"));
|
|
1547
1641
|
return;
|
|
1548
1642
|
}
|
|
1549
1643
|
chunks.push(chunk);
|
|
1550
1644
|
});
|
|
1551
|
-
req.on("end", () =>
|
|
1552
|
-
|
|
1645
|
+
req.on("end", () => {
|
|
1646
|
+
clearTimeout(timeout);
|
|
1647
|
+
resolve(Buffer.concat(chunks).toString("utf-8"));
|
|
1648
|
+
});
|
|
1649
|
+
req.on("error", (err) => {
|
|
1650
|
+
clearTimeout(timeout);
|
|
1651
|
+
reject(err);
|
|
1652
|
+
});
|
|
1553
1653
|
});
|
|
1554
1654
|
}
|
|
1555
1655
|
function createRouteHandler(server) {
|
|
@@ -1596,9 +1696,10 @@ function createRouteHandler(server) {
|
|
|
1596
1696
|
});
|
|
1597
1697
|
return;
|
|
1598
1698
|
}
|
|
1699
|
+
const validEvents = Array.isArray(events) ? events.filter(validateEvent) : void 0;
|
|
1599
1700
|
const result = await server.processTick(
|
|
1600
1701
|
state,
|
|
1601
|
-
|
|
1702
|
+
validEvents
|
|
1602
1703
|
);
|
|
1603
1704
|
const warnings = validation?.warnings ?? [];
|
|
1604
1705
|
respond(200, {
|
|
@@ -1628,6 +1729,10 @@ function createRouteHandler(server) {
|
|
|
1628
1729
|
return;
|
|
1629
1730
|
}
|
|
1630
1731
|
if (path === "/decisions" && method === "GET") {
|
|
1732
|
+
if (!checkAuth(req, apiKey)) {
|
|
1733
|
+
respond(401, { error: "Unauthorized" });
|
|
1734
|
+
return;
|
|
1735
|
+
}
|
|
1631
1736
|
const rawLimit = parseInt(url.searchParams.get("limit") ?? "100", 10);
|
|
1632
1737
|
const limit = Math.min(Math.max(Number.isNaN(rawLimit) ? 100 : rawLimit, 1), 1e3);
|
|
1633
1738
|
const sinceParam = url.searchParams.get("since");
|
|
@@ -1643,7 +1748,11 @@ function createRouteHandler(server) {
|
|
|
1643
1748
|
} else {
|
|
1644
1749
|
decisions = agentE.log.latest(limit);
|
|
1645
1750
|
}
|
|
1646
|
-
|
|
1751
|
+
const sanitized = decisions.map((d) => {
|
|
1752
|
+
const { metricsSnapshot: _, ...rest } = d;
|
|
1753
|
+
return rest;
|
|
1754
|
+
});
|
|
1755
|
+
respond(200, { decisions: sanitized });
|
|
1647
1756
|
return;
|
|
1648
1757
|
}
|
|
1649
1758
|
if (path === "/config" && method === "POST") {
|
|
@@ -1661,18 +1770,18 @@ function createRouteHandler(server) {
|
|
|
1661
1770
|
}
|
|
1662
1771
|
const config = parsed;
|
|
1663
1772
|
if (Array.isArray(config["lock"])) {
|
|
1664
|
-
for (const param of config["lock"]) {
|
|
1773
|
+
for (const param of config["lock"].slice(0, MAX_CONFIG_ARRAY)) {
|
|
1665
1774
|
if (typeof param === "string") server.lock(param);
|
|
1666
1775
|
}
|
|
1667
1776
|
}
|
|
1668
1777
|
if (Array.isArray(config["unlock"])) {
|
|
1669
|
-
for (const param of config["unlock"]) {
|
|
1778
|
+
for (const param of config["unlock"].slice(0, MAX_CONFIG_ARRAY)) {
|
|
1670
1779
|
if (typeof param === "string") server.unlock(param);
|
|
1671
1780
|
}
|
|
1672
1781
|
}
|
|
1673
1782
|
if (Array.isArray(config["constrain"])) {
|
|
1674
1783
|
const validated = [];
|
|
1675
|
-
for (const c of config["constrain"]) {
|
|
1784
|
+
for (const c of config["constrain"].slice(0, MAX_CONFIG_ARRAY)) {
|
|
1676
1785
|
if (c && typeof c === "object" && typeof c["param"] === "string" && typeof c["min"] === "number" && typeof c["max"] === "number") {
|
|
1677
1786
|
const constraint = c;
|
|
1678
1787
|
if (!Number.isFinite(constraint.min) || !Number.isFinite(constraint.max)) {
|
|
@@ -1745,14 +1854,28 @@ function createRouteHandler(server) {
|
|
|
1745
1854
|
return;
|
|
1746
1855
|
}
|
|
1747
1856
|
if (path === "/" && method === "GET" && server.serveDashboard) {
|
|
1857
|
+
if (apiKey) {
|
|
1858
|
+
const dashToken = url.searchParams.get("token") ?? "";
|
|
1859
|
+
const hasBearer = checkAuth(req, apiKey);
|
|
1860
|
+
const hasQueryToken = dashToken.length === apiKey.length && (0, import_node_crypto.timingSafeEqual)(Buffer.from(dashToken), Buffer.from(apiKey));
|
|
1861
|
+
if (!hasBearer && !hasQueryToken) {
|
|
1862
|
+
respond(401, { error: "Unauthorized \u2014 use ?token=<apiKey> or Authorization header" });
|
|
1863
|
+
return;
|
|
1864
|
+
}
|
|
1865
|
+
}
|
|
1748
1866
|
setCorsHeaders(res, cors, reqOrigin);
|
|
1749
|
-
|
|
1750
|
-
res.setHeader("
|
|
1867
|
+
const nonce = (0, import_node_crypto.randomBytes)(16).toString("base64");
|
|
1868
|
+
res.setHeader("Content-Security-Policy", `default-src 'self'; script-src 'nonce-${nonce}' https://cdn.jsdelivr.net; style-src 'unsafe-inline' https://fonts.googleapis.com; font-src https://fonts.gstatic.com; connect-src 'self' ws: wss:; img-src 'self' data:`);
|
|
1869
|
+
res.setHeader("Cache-Control", "no-cache, private");
|
|
1751
1870
|
res.writeHead(200, { "Content-Type": "text/html; charset=utf-8" });
|
|
1752
|
-
res.end(getDashboardHtml());
|
|
1871
|
+
res.end(getDashboardHtml(nonce));
|
|
1753
1872
|
return;
|
|
1754
1873
|
}
|
|
1755
1874
|
if (path === "/metrics" && method === "GET") {
|
|
1875
|
+
if (!checkAuth(req, apiKey)) {
|
|
1876
|
+
respond(401, { error: "Unauthorized" });
|
|
1877
|
+
return;
|
|
1878
|
+
}
|
|
1756
1879
|
const agentE = server.getAgentE();
|
|
1757
1880
|
const latest = agentE.store.latest();
|
|
1758
1881
|
const history = agentE.store.recentHistory(100);
|
|
@@ -1760,6 +1883,10 @@ function createRouteHandler(server) {
|
|
|
1760
1883
|
return;
|
|
1761
1884
|
}
|
|
1762
1885
|
if (path === "/metrics/personas" && method === "GET") {
|
|
1886
|
+
if (!checkAuth(req, apiKey)) {
|
|
1887
|
+
respond(401, { error: "Unauthorized" });
|
|
1888
|
+
return;
|
|
1889
|
+
}
|
|
1763
1890
|
const agentE = server.getAgentE();
|
|
1764
1891
|
const latest = agentE.store.latest();
|
|
1765
1892
|
const dist = latest.personaDistribution || {};
|
|
@@ -1850,6 +1977,10 @@ function createRouteHandler(server) {
|
|
|
1850
1977
|
return;
|
|
1851
1978
|
}
|
|
1852
1979
|
if (path === "/pending" && method === "GET") {
|
|
1980
|
+
if (!checkAuth(req, apiKey)) {
|
|
1981
|
+
respond(401, { error: "Unauthorized" });
|
|
1982
|
+
return;
|
|
1983
|
+
}
|
|
1853
1984
|
const agentE = server.getAgentE();
|
|
1854
1985
|
const pending = agentE.log.query({ result: "skipped_override" });
|
|
1855
1986
|
respond(200, {
|
|
@@ -1866,14 +1997,17 @@ function createRouteHandler(server) {
|
|
|
1866
1997
|
}
|
|
1867
1998
|
};
|
|
1868
1999
|
}
|
|
1869
|
-
var import_node_crypto, import_core, MAX_BODY_BYTES;
|
|
2000
|
+
var import_node_crypto, import_core, MAX_BODY_BYTES, READ_BODY_TIMEOUT_MS, MAX_CONFIG_ARRAY;
|
|
1870
2001
|
var init_routes = __esm({
|
|
1871
2002
|
"src/routes.ts"() {
|
|
1872
2003
|
"use strict";
|
|
1873
2004
|
import_node_crypto = require("crypto");
|
|
1874
2005
|
import_core = require("@agent-e/core");
|
|
1875
2006
|
init_dashboard();
|
|
2007
|
+
init_validation();
|
|
1876
2008
|
MAX_BODY_BYTES = 1048576;
|
|
2009
|
+
READ_BODY_TIMEOUT_MS = 3e4;
|
|
2010
|
+
MAX_CONFIG_ARRAY = 1e3;
|
|
1877
2011
|
}
|
|
1878
2012
|
});
|
|
1879
2013
|
|
|
@@ -1895,6 +2029,7 @@ function sanitizeJson2(obj) {
|
|
|
1895
2029
|
}
|
|
1896
2030
|
function createWebSocketHandler(httpServer, server) {
|
|
1897
2031
|
const wss = new import_ws.WebSocketServer({ server: httpServer, maxPayload: MAX_WS_PAYLOAD });
|
|
2032
|
+
let globalLastTickTime = 0;
|
|
1898
2033
|
const aliveMap = /* @__PURE__ */ new WeakMap();
|
|
1899
2034
|
const heartbeatInterval = setInterval(() => {
|
|
1900
2035
|
for (const ws of wss.clients) {
|
|
@@ -1922,7 +2057,8 @@ function createWebSocketHandler(httpServer, server) {
|
|
|
1922
2057
|
}
|
|
1923
2058
|
if (server.apiKey) {
|
|
1924
2059
|
const url = new URL(req.url ?? "/", `http://${req.headers.host ?? "localhost"}`);
|
|
1925
|
-
const
|
|
2060
|
+
const authHeader = req.headers["authorization"];
|
|
2061
|
+
const token = (authHeader?.startsWith("Bearer ") ? authHeader.slice(7) : void 0) ?? url.searchParams.get("token");
|
|
1926
2062
|
if (!token || token.length !== server.apiKey.length || !(0, import_node_crypto2.timingSafeEqual)(Buffer.from(token), Buffer.from(server.apiKey))) {
|
|
1927
2063
|
ws.close(1008, "Unauthorized");
|
|
1928
2064
|
return;
|
|
@@ -1956,7 +2092,12 @@ function createWebSocketHandler(httpServer, server) {
|
|
|
1956
2092
|
send(ws, { type: "error", message: "Rate limited \u2014 min 100ms between ticks" });
|
|
1957
2093
|
break;
|
|
1958
2094
|
}
|
|
2095
|
+
if (now - globalLastTickTime < GLOBAL_MIN_TICK_INTERVAL_MS) {
|
|
2096
|
+
send(ws, { type: "error", message: "Rate limited \u2014 server tick capacity exceeded" });
|
|
2097
|
+
break;
|
|
2098
|
+
}
|
|
1959
2099
|
lastTickTime = now;
|
|
2100
|
+
globalLastTickTime = now;
|
|
1960
2101
|
const state = msg["state"];
|
|
1961
2102
|
const events = msg["events"];
|
|
1962
2103
|
if (server.validateState) {
|
|
@@ -1970,9 +2111,10 @@ function createWebSocketHandler(httpServer, server) {
|
|
|
1970
2111
|
}
|
|
1971
2112
|
}
|
|
1972
2113
|
try {
|
|
2114
|
+
const validEvents = Array.isArray(events) ? events.filter(validateEvent) : void 0;
|
|
1973
2115
|
const result = await server.processTick(
|
|
1974
2116
|
state,
|
|
1975
|
-
|
|
2117
|
+
validEvents
|
|
1976
2118
|
);
|
|
1977
2119
|
send(ws, {
|
|
1978
2120
|
type: "tick_result",
|
|
@@ -1992,13 +2134,17 @@ function createWebSocketHandler(httpServer, server) {
|
|
|
1992
2134
|
break;
|
|
1993
2135
|
}
|
|
1994
2136
|
case "event": {
|
|
1995
|
-
const
|
|
1996
|
-
if (
|
|
1997
|
-
server.getAgentE().ingest(event);
|
|
1998
|
-
send(ws, { type: "event_ack" });
|
|
1999
|
-
} else {
|
|
2137
|
+
const rawEvent = msg["event"];
|
|
2138
|
+
if (!rawEvent) {
|
|
2000
2139
|
send(ws, { type: "error", message: 'Missing "event" field' });
|
|
2140
|
+
break;
|
|
2001
2141
|
}
|
|
2142
|
+
if (!validateEvent(rawEvent)) {
|
|
2143
|
+
send(ws, { type: "error", message: "Invalid event \u2014 requires type (valid event type), timestamp (number), and actor (string)" });
|
|
2144
|
+
break;
|
|
2145
|
+
}
|
|
2146
|
+
server.getAgentE().ingest(rawEvent);
|
|
2147
|
+
send(ws, { type: "event_ack" });
|
|
2002
2148
|
break;
|
|
2003
2149
|
}
|
|
2004
2150
|
case "health": {
|
|
@@ -2056,16 +2202,18 @@ function createWebSocketHandler(httpServer, server) {
|
|
|
2056
2202
|
broadcast
|
|
2057
2203
|
};
|
|
2058
2204
|
}
|
|
2059
|
-
var import_node_crypto2, import_ws, import_core2, MAX_WS_PAYLOAD, MAX_WS_CONNECTIONS, MIN_TICK_INTERVAL_MS;
|
|
2205
|
+
var import_node_crypto2, import_ws, import_core2, MAX_WS_PAYLOAD, MAX_WS_CONNECTIONS, MIN_TICK_INTERVAL_MS, GLOBAL_MIN_TICK_INTERVAL_MS;
|
|
2060
2206
|
var init_websocket = __esm({
|
|
2061
2207
|
"src/websocket.ts"() {
|
|
2062
2208
|
"use strict";
|
|
2063
2209
|
import_node_crypto2 = require("crypto");
|
|
2064
2210
|
import_ws = require("ws");
|
|
2065
2211
|
import_core2 = require("@agent-e/core");
|
|
2212
|
+
init_validation();
|
|
2066
2213
|
MAX_WS_PAYLOAD = 1048576;
|
|
2067
2214
|
MAX_WS_CONNECTIONS = 100;
|
|
2068
2215
|
MIN_TICK_INTERVAL_MS = 100;
|
|
2216
|
+
GLOBAL_MIN_TICK_INTERVAL_MS = 50;
|
|
2069
2217
|
}
|
|
2070
2218
|
});
|
|
2071
2219
|
|
|
@@ -2087,6 +2235,8 @@ var init_AgentEServer = __esm({
|
|
|
2087
2235
|
this.lastState = null;
|
|
2088
2236
|
this.adjustmentQueue = [];
|
|
2089
2237
|
this.alerts = [];
|
|
2238
|
+
/** Serialization lock for processTick — prevents concurrent ticks from corrupting shared state. */
|
|
2239
|
+
this.tickLock = Promise.resolve();
|
|
2090
2240
|
this.startedAt = Date.now();
|
|
2091
2241
|
this.wsHandle = null;
|
|
2092
2242
|
this.port = config.port ?? 3100;
|
|
@@ -2138,6 +2288,44 @@ var init_AgentEServer = __esm({
|
|
|
2138
2288
|
this.agentE.on("alert", (diagnosis) => {
|
|
2139
2289
|
this.alerts.push(diagnosis);
|
|
2140
2290
|
});
|
|
2291
|
+
this.agentE.on("narration", (n) => {
|
|
2292
|
+
const narration = n;
|
|
2293
|
+
const tick = this.lastState?.tick ?? 0;
|
|
2294
|
+
this.broadcast({
|
|
2295
|
+
type: "narration",
|
|
2296
|
+
tick,
|
|
2297
|
+
text: narration.narration,
|
|
2298
|
+
principle: narration.diagnosis.principle.name,
|
|
2299
|
+
severity: narration.diagnosis.violation.severity,
|
|
2300
|
+
confidence: narration.confidence
|
|
2301
|
+
});
|
|
2302
|
+
});
|
|
2303
|
+
this.agentE.on("explanation", (e) => {
|
|
2304
|
+
const explanation = e;
|
|
2305
|
+
this.broadcast({
|
|
2306
|
+
type: "explanation",
|
|
2307
|
+
tick: this.lastState?.tick ?? 0,
|
|
2308
|
+
text: explanation.explanation,
|
|
2309
|
+
parameter: explanation.plan.parameter,
|
|
2310
|
+
direction: explanation.plan.targetValue > explanation.plan.currentValue ? "increase" : "decrease",
|
|
2311
|
+
expectedOutcome: explanation.expectedOutcome,
|
|
2312
|
+
risks: explanation.risks
|
|
2313
|
+
});
|
|
2314
|
+
});
|
|
2315
|
+
this.agentE.on("anomaly", (a) => {
|
|
2316
|
+
const anomaly = a;
|
|
2317
|
+
this.broadcast({
|
|
2318
|
+
type: "anomaly",
|
|
2319
|
+
tick: anomaly.tick,
|
|
2320
|
+
text: anomaly.interpretation,
|
|
2321
|
+
metrics: anomaly.anomalies.map((m) => ({
|
|
2322
|
+
name: m.metric,
|
|
2323
|
+
deviation: m.deviation,
|
|
2324
|
+
currentValue: m.currentValue
|
|
2325
|
+
})),
|
|
2326
|
+
severity: anomaly.severity
|
|
2327
|
+
});
|
|
2328
|
+
});
|
|
2141
2329
|
this.agentE.connect(adapter).start();
|
|
2142
2330
|
const routeHandler = createRouteHandler(this);
|
|
2143
2331
|
this.server = http.createServer(routeHandler);
|
|
@@ -2185,36 +2373,46 @@ var init_AgentEServer = __esm({
|
|
|
2185
2373
|
* 6. Return response
|
|
2186
2374
|
*/
|
|
2187
2375
|
async processTick(state, events) {
|
|
2188
|
-
|
|
2189
|
-
|
|
2190
|
-
this.
|
|
2191
|
-
|
|
2192
|
-
|
|
2193
|
-
|
|
2376
|
+
const prev = this.tickLock;
|
|
2377
|
+
let unlock;
|
|
2378
|
+
this.tickLock = new Promise((resolve) => {
|
|
2379
|
+
unlock = resolve;
|
|
2380
|
+
});
|
|
2381
|
+
await prev;
|
|
2382
|
+
try {
|
|
2383
|
+
this.adjustmentQueue = [];
|
|
2384
|
+
this.alerts = [];
|
|
2385
|
+
this.lastState = state;
|
|
2386
|
+
if (events) {
|
|
2387
|
+
for (const event of events) {
|
|
2388
|
+
this.agentE.ingest(event);
|
|
2389
|
+
}
|
|
2194
2390
|
}
|
|
2195
|
-
|
|
2196
|
-
|
|
2197
|
-
|
|
2198
|
-
|
|
2199
|
-
|
|
2200
|
-
|
|
2201
|
-
|
|
2202
|
-
|
|
2203
|
-
|
|
2391
|
+
await this.agentE.tick(state);
|
|
2392
|
+
const rawAdj = [...this.adjustmentQueue];
|
|
2393
|
+
this.adjustmentQueue = [];
|
|
2394
|
+
const decisions = this.agentE.getDecisions({ since: state.tick, until: state.tick });
|
|
2395
|
+
const adjustments = rawAdj.map((adj) => {
|
|
2396
|
+
const decision = decisions.find(
|
|
2397
|
+
(d) => d.plan.parameter === adj.key && d.result === "applied"
|
|
2398
|
+
);
|
|
2399
|
+
return {
|
|
2400
|
+
parameter: adj.key,
|
|
2401
|
+
value: adj.value,
|
|
2402
|
+
...adj.scope ? { scope: adj.scope } : {},
|
|
2403
|
+
reasoning: decision?.diagnosis.violation.suggestedAction.reasoning ?? ""
|
|
2404
|
+
};
|
|
2405
|
+
});
|
|
2204
2406
|
return {
|
|
2205
|
-
|
|
2206
|
-
|
|
2207
|
-
|
|
2208
|
-
|
|
2407
|
+
adjustments,
|
|
2408
|
+
alerts: [...this.alerts],
|
|
2409
|
+
health: this.agentE.getHealth(),
|
|
2410
|
+
tick: state.tick,
|
|
2411
|
+
decisions
|
|
2209
2412
|
};
|
|
2210
|
-
}
|
|
2211
|
-
|
|
2212
|
-
|
|
2213
|
-
alerts: [...this.alerts],
|
|
2214
|
-
health: this.agentE.getHealth(),
|
|
2215
|
-
tick: state.tick,
|
|
2216
|
-
decisions
|
|
2217
|
-
};
|
|
2413
|
+
} finally {
|
|
2414
|
+
unlock();
|
|
2415
|
+
}
|
|
2218
2416
|
}
|
|
2219
2417
|
/**
|
|
2220
2418
|
* Run Observer + Diagnoser on the given state without side effects (no execution).
|