@agent-e/server 1.8.1 → 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-ALGME445.mjs → chunk-3HGNFK4A.mjs} +157 -52
- package/dist/chunk-3HGNFK4A.mjs.map +1 -0
- package/dist/cli.js +155 -50
- 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 +165 -52
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +2 -2
- package/package.json +1 -1
- package/dist/AgentEServer-KIQBHRUS.mjs +0 -7
- package/dist/chunk-ALGME445.mjs.map +0 -1
- /package/dist/{AgentEServer-KIQBHRUS.mjs.map → AgentEServer-A7K6426K.mjs.map} +0 -0
package/dist/cli.mjs
CHANGED
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
|
|
|
@@ -862,7 +863,7 @@ function getDashboardHtml() {
|
|
|
862
863
|
|
|
863
864
|
</main>
|
|
864
865
|
|
|
865
|
-
<script
|
|
866
|
+
<script` + nonceAttr + `>
|
|
866
867
|
(function() {
|
|
867
868
|
'use strict';
|
|
868
869
|
|
|
@@ -909,6 +910,9 @@ function getDashboardHtml() {
|
|
|
909
910
|
var $dashboardRoot = document.getElementById('dashboard-root');
|
|
910
911
|
|
|
911
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.
|
|
912
916
|
function esc(s) { return String(s).replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>').replace(/"/g,'"').replace(/'/g,''').replace(/\\\\/g,'\'); }
|
|
913
917
|
function pad(n, w) { return String(n).padStart(w || 4, ' '); }
|
|
914
918
|
function fmt(n) { return typeof n === 'number' ? n.toFixed(3) : '\\u2014'; }
|
|
@@ -1313,14 +1317,25 @@ function getDashboardHtml() {
|
|
|
1313
1317
|
}
|
|
1314
1318
|
|
|
1315
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
|
+
|
|
1316
1329
|
function fetchJSON(path) {
|
|
1317
|
-
return fetch(path).then(function(r) { return r.json(); });
|
|
1330
|
+
return fetch(path, { headers: authHeaders() }).then(function(r) { return r.json(); });
|
|
1318
1331
|
}
|
|
1319
1332
|
|
|
1320
1333
|
function postJSON(path, body) {
|
|
1334
|
+
var h = authHeaders();
|
|
1335
|
+
h['Content-Type'] = 'application/json';
|
|
1321
1336
|
return fetch(path, {
|
|
1322
1337
|
method: 'POST',
|
|
1323
|
-
headers:
|
|
1338
|
+
headers: h,
|
|
1324
1339
|
body: JSON.stringify(body),
|
|
1325
1340
|
}).then(function(r) { return r.json(); });
|
|
1326
1341
|
}
|
|
@@ -1382,7 +1397,9 @@ function getDashboardHtml() {
|
|
|
1382
1397
|
// -- WebSocket --
|
|
1383
1398
|
function connectWS() {
|
|
1384
1399
|
var proto = location.protocol === 'https:' ? 'wss:' : 'ws:';
|
|
1385
|
-
|
|
1400
|
+
var wsUrl = proto + '//' + location.host;
|
|
1401
|
+
if (_authKey) wsUrl += '?token=' + encodeURIComponent(_authKey);
|
|
1402
|
+
ws = new WebSocket(wsUrl);
|
|
1386
1403
|
|
|
1387
1404
|
ws.onopen = function() {
|
|
1388
1405
|
reconnectDelay = 1000;
|
|
@@ -1537,11 +1554,36 @@ var init_dashboard = __esm({
|
|
|
1537
1554
|
}
|
|
1538
1555
|
});
|
|
1539
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
|
+
|
|
1540
1581
|
// src/routes.ts
|
|
1541
1582
|
function setSecurityHeaders(res) {
|
|
1542
1583
|
res.setHeader("X-Content-Type-Options", "nosniff");
|
|
1543
1584
|
res.setHeader("X-Frame-Options", "DENY");
|
|
1544
1585
|
res.setHeader("Referrer-Policy", "strict-origin-when-cross-origin");
|
|
1586
|
+
res.setHeader("Strict-Transport-Security", "max-age=31536000; includeSubDomains");
|
|
1545
1587
|
}
|
|
1546
1588
|
function setCorsHeaders(res, allowedOrigin, requestOrigin) {
|
|
1547
1589
|
setSecurityHeaders(res);
|
|
@@ -1586,17 +1628,28 @@ function readBody(req) {
|
|
|
1586
1628
|
return new Promise((resolve, reject) => {
|
|
1587
1629
|
const chunks = [];
|
|
1588
1630
|
let totalBytes = 0;
|
|
1631
|
+
const timeout = setTimeout(() => {
|
|
1632
|
+
req.destroy();
|
|
1633
|
+
reject(new Error("Request body read timeout"));
|
|
1634
|
+
}, READ_BODY_TIMEOUT_MS);
|
|
1589
1635
|
req.on("data", (chunk) => {
|
|
1590
1636
|
totalBytes += chunk.length;
|
|
1591
1637
|
if (totalBytes > MAX_BODY_BYTES) {
|
|
1638
|
+
clearTimeout(timeout);
|
|
1592
1639
|
req.destroy();
|
|
1593
1640
|
reject(new Error("Request body too large"));
|
|
1594
1641
|
return;
|
|
1595
1642
|
}
|
|
1596
1643
|
chunks.push(chunk);
|
|
1597
1644
|
});
|
|
1598
|
-
req.on("end", () =>
|
|
1599
|
-
|
|
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
|
+
});
|
|
1600
1653
|
});
|
|
1601
1654
|
}
|
|
1602
1655
|
function createRouteHandler(server) {
|
|
@@ -1643,9 +1696,10 @@ function createRouteHandler(server) {
|
|
|
1643
1696
|
});
|
|
1644
1697
|
return;
|
|
1645
1698
|
}
|
|
1699
|
+
const validEvents = Array.isArray(events) ? events.filter(validateEvent) : void 0;
|
|
1646
1700
|
const result = await server.processTick(
|
|
1647
1701
|
state,
|
|
1648
|
-
|
|
1702
|
+
validEvents
|
|
1649
1703
|
);
|
|
1650
1704
|
const warnings = validation?.warnings ?? [];
|
|
1651
1705
|
respond(200, {
|
|
@@ -1675,6 +1729,10 @@ function createRouteHandler(server) {
|
|
|
1675
1729
|
return;
|
|
1676
1730
|
}
|
|
1677
1731
|
if (path === "/decisions" && method === "GET") {
|
|
1732
|
+
if (!checkAuth(req, apiKey)) {
|
|
1733
|
+
respond(401, { error: "Unauthorized" });
|
|
1734
|
+
return;
|
|
1735
|
+
}
|
|
1678
1736
|
const rawLimit = parseInt(url.searchParams.get("limit") ?? "100", 10);
|
|
1679
1737
|
const limit = Math.min(Math.max(Number.isNaN(rawLimit) ? 100 : rawLimit, 1), 1e3);
|
|
1680
1738
|
const sinceParam = url.searchParams.get("since");
|
|
@@ -1690,7 +1748,11 @@ function createRouteHandler(server) {
|
|
|
1690
1748
|
} else {
|
|
1691
1749
|
decisions = agentE.log.latest(limit);
|
|
1692
1750
|
}
|
|
1693
|
-
|
|
1751
|
+
const sanitized = decisions.map((d) => {
|
|
1752
|
+
const { metricsSnapshot: _, ...rest } = d;
|
|
1753
|
+
return rest;
|
|
1754
|
+
});
|
|
1755
|
+
respond(200, { decisions: sanitized });
|
|
1694
1756
|
return;
|
|
1695
1757
|
}
|
|
1696
1758
|
if (path === "/config" && method === "POST") {
|
|
@@ -1708,18 +1770,18 @@ function createRouteHandler(server) {
|
|
|
1708
1770
|
}
|
|
1709
1771
|
const config = parsed;
|
|
1710
1772
|
if (Array.isArray(config["lock"])) {
|
|
1711
|
-
for (const param of config["lock"]) {
|
|
1773
|
+
for (const param of config["lock"].slice(0, MAX_CONFIG_ARRAY)) {
|
|
1712
1774
|
if (typeof param === "string") server.lock(param);
|
|
1713
1775
|
}
|
|
1714
1776
|
}
|
|
1715
1777
|
if (Array.isArray(config["unlock"])) {
|
|
1716
|
-
for (const param of config["unlock"]) {
|
|
1778
|
+
for (const param of config["unlock"].slice(0, MAX_CONFIG_ARRAY)) {
|
|
1717
1779
|
if (typeof param === "string") server.unlock(param);
|
|
1718
1780
|
}
|
|
1719
1781
|
}
|
|
1720
1782
|
if (Array.isArray(config["constrain"])) {
|
|
1721
1783
|
const validated = [];
|
|
1722
|
-
for (const c of config["constrain"]) {
|
|
1784
|
+
for (const c of config["constrain"].slice(0, MAX_CONFIG_ARRAY)) {
|
|
1723
1785
|
if (c && typeof c === "object" && typeof c["param"] === "string" && typeof c["min"] === "number" && typeof c["max"] === "number") {
|
|
1724
1786
|
const constraint = c;
|
|
1725
1787
|
if (!Number.isFinite(constraint.min) || !Number.isFinite(constraint.max)) {
|
|
@@ -1792,14 +1854,28 @@ function createRouteHandler(server) {
|
|
|
1792
1854
|
return;
|
|
1793
1855
|
}
|
|
1794
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
|
+
}
|
|
1795
1866
|
setCorsHeaders(res, cors, reqOrigin);
|
|
1796
|
-
|
|
1797
|
-
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");
|
|
1798
1870
|
res.writeHead(200, { "Content-Type": "text/html; charset=utf-8" });
|
|
1799
|
-
res.end(getDashboardHtml());
|
|
1871
|
+
res.end(getDashboardHtml(nonce));
|
|
1800
1872
|
return;
|
|
1801
1873
|
}
|
|
1802
1874
|
if (path === "/metrics" && method === "GET") {
|
|
1875
|
+
if (!checkAuth(req, apiKey)) {
|
|
1876
|
+
respond(401, { error: "Unauthorized" });
|
|
1877
|
+
return;
|
|
1878
|
+
}
|
|
1803
1879
|
const agentE = server.getAgentE();
|
|
1804
1880
|
const latest = agentE.store.latest();
|
|
1805
1881
|
const history = agentE.store.recentHistory(100);
|
|
@@ -1807,6 +1883,10 @@ function createRouteHandler(server) {
|
|
|
1807
1883
|
return;
|
|
1808
1884
|
}
|
|
1809
1885
|
if (path === "/metrics/personas" && method === "GET") {
|
|
1886
|
+
if (!checkAuth(req, apiKey)) {
|
|
1887
|
+
respond(401, { error: "Unauthorized" });
|
|
1888
|
+
return;
|
|
1889
|
+
}
|
|
1810
1890
|
const agentE = server.getAgentE();
|
|
1811
1891
|
const latest = agentE.store.latest();
|
|
1812
1892
|
const dist = latest.personaDistribution || {};
|
|
@@ -1897,6 +1977,10 @@ function createRouteHandler(server) {
|
|
|
1897
1977
|
return;
|
|
1898
1978
|
}
|
|
1899
1979
|
if (path === "/pending" && method === "GET") {
|
|
1980
|
+
if (!checkAuth(req, apiKey)) {
|
|
1981
|
+
respond(401, { error: "Unauthorized" });
|
|
1982
|
+
return;
|
|
1983
|
+
}
|
|
1900
1984
|
const agentE = server.getAgentE();
|
|
1901
1985
|
const pending = agentE.log.query({ result: "skipped_override" });
|
|
1902
1986
|
respond(200, {
|
|
@@ -1913,14 +1997,17 @@ function createRouteHandler(server) {
|
|
|
1913
1997
|
}
|
|
1914
1998
|
};
|
|
1915
1999
|
}
|
|
1916
|
-
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;
|
|
1917
2001
|
var init_routes = __esm({
|
|
1918
2002
|
"src/routes.ts"() {
|
|
1919
2003
|
"use strict";
|
|
1920
2004
|
import_node_crypto = require("crypto");
|
|
1921
2005
|
import_core = require("@agent-e/core");
|
|
1922
2006
|
init_dashboard();
|
|
2007
|
+
init_validation();
|
|
1923
2008
|
MAX_BODY_BYTES = 1048576;
|
|
2009
|
+
READ_BODY_TIMEOUT_MS = 3e4;
|
|
2010
|
+
MAX_CONFIG_ARRAY = 1e3;
|
|
1924
2011
|
}
|
|
1925
2012
|
});
|
|
1926
2013
|
|
|
@@ -1942,6 +2029,7 @@ function sanitizeJson2(obj) {
|
|
|
1942
2029
|
}
|
|
1943
2030
|
function createWebSocketHandler(httpServer, server) {
|
|
1944
2031
|
const wss = new import_ws.WebSocketServer({ server: httpServer, maxPayload: MAX_WS_PAYLOAD });
|
|
2032
|
+
let globalLastTickTime = 0;
|
|
1945
2033
|
const aliveMap = /* @__PURE__ */ new WeakMap();
|
|
1946
2034
|
const heartbeatInterval = setInterval(() => {
|
|
1947
2035
|
for (const ws of wss.clients) {
|
|
@@ -1969,7 +2057,8 @@ function createWebSocketHandler(httpServer, server) {
|
|
|
1969
2057
|
}
|
|
1970
2058
|
if (server.apiKey) {
|
|
1971
2059
|
const url = new URL(req.url ?? "/", `http://${req.headers.host ?? "localhost"}`);
|
|
1972
|
-
const
|
|
2060
|
+
const authHeader = req.headers["authorization"];
|
|
2061
|
+
const token = (authHeader?.startsWith("Bearer ") ? authHeader.slice(7) : void 0) ?? url.searchParams.get("token");
|
|
1973
2062
|
if (!token || token.length !== server.apiKey.length || !(0, import_node_crypto2.timingSafeEqual)(Buffer.from(token), Buffer.from(server.apiKey))) {
|
|
1974
2063
|
ws.close(1008, "Unauthorized");
|
|
1975
2064
|
return;
|
|
@@ -2003,7 +2092,12 @@ function createWebSocketHandler(httpServer, server) {
|
|
|
2003
2092
|
send(ws, { type: "error", message: "Rate limited \u2014 min 100ms between ticks" });
|
|
2004
2093
|
break;
|
|
2005
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
|
+
}
|
|
2006
2099
|
lastTickTime = now;
|
|
2100
|
+
globalLastTickTime = now;
|
|
2007
2101
|
const state = msg["state"];
|
|
2008
2102
|
const events = msg["events"];
|
|
2009
2103
|
if (server.validateState) {
|
|
@@ -2017,9 +2111,10 @@ function createWebSocketHandler(httpServer, server) {
|
|
|
2017
2111
|
}
|
|
2018
2112
|
}
|
|
2019
2113
|
try {
|
|
2114
|
+
const validEvents = Array.isArray(events) ? events.filter(validateEvent) : void 0;
|
|
2020
2115
|
const result = await server.processTick(
|
|
2021
2116
|
state,
|
|
2022
|
-
|
|
2117
|
+
validEvents
|
|
2023
2118
|
);
|
|
2024
2119
|
send(ws, {
|
|
2025
2120
|
type: "tick_result",
|
|
@@ -2039,13 +2134,17 @@ function createWebSocketHandler(httpServer, server) {
|
|
|
2039
2134
|
break;
|
|
2040
2135
|
}
|
|
2041
2136
|
case "event": {
|
|
2042
|
-
const
|
|
2043
|
-
if (
|
|
2044
|
-
server.getAgentE().ingest(event);
|
|
2045
|
-
send(ws, { type: "event_ack" });
|
|
2046
|
-
} else {
|
|
2137
|
+
const rawEvent = msg["event"];
|
|
2138
|
+
if (!rawEvent) {
|
|
2047
2139
|
send(ws, { type: "error", message: 'Missing "event" field' });
|
|
2140
|
+
break;
|
|
2048
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" });
|
|
2049
2148
|
break;
|
|
2050
2149
|
}
|
|
2051
2150
|
case "health": {
|
|
@@ -2103,16 +2202,18 @@ function createWebSocketHandler(httpServer, server) {
|
|
|
2103
2202
|
broadcast
|
|
2104
2203
|
};
|
|
2105
2204
|
}
|
|
2106
|
-
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;
|
|
2107
2206
|
var init_websocket = __esm({
|
|
2108
2207
|
"src/websocket.ts"() {
|
|
2109
2208
|
"use strict";
|
|
2110
2209
|
import_node_crypto2 = require("crypto");
|
|
2111
2210
|
import_ws = require("ws");
|
|
2112
2211
|
import_core2 = require("@agent-e/core");
|
|
2212
|
+
init_validation();
|
|
2113
2213
|
MAX_WS_PAYLOAD = 1048576;
|
|
2114
2214
|
MAX_WS_CONNECTIONS = 100;
|
|
2115
2215
|
MIN_TICK_INTERVAL_MS = 100;
|
|
2216
|
+
GLOBAL_MIN_TICK_INTERVAL_MS = 50;
|
|
2116
2217
|
}
|
|
2117
2218
|
});
|
|
2118
2219
|
|
|
@@ -2134,6 +2235,8 @@ var init_AgentEServer = __esm({
|
|
|
2134
2235
|
this.lastState = null;
|
|
2135
2236
|
this.adjustmentQueue = [];
|
|
2136
2237
|
this.alerts = [];
|
|
2238
|
+
/** Serialization lock for processTick — prevents concurrent ticks from corrupting shared state. */
|
|
2239
|
+
this.tickLock = Promise.resolve();
|
|
2137
2240
|
this.startedAt = Date.now();
|
|
2138
2241
|
this.wsHandle = null;
|
|
2139
2242
|
this.port = config.port ?? 3100;
|
|
@@ -2270,36 +2373,46 @@ var init_AgentEServer = __esm({
|
|
|
2270
2373
|
* 6. Return response
|
|
2271
2374
|
*/
|
|
2272
2375
|
async processTick(state, events) {
|
|
2273
|
-
|
|
2274
|
-
|
|
2275
|
-
this.
|
|
2276
|
-
|
|
2277
|
-
|
|
2278
|
-
|
|
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
|
+
}
|
|
2279
2390
|
}
|
|
2280
|
-
|
|
2281
|
-
|
|
2282
|
-
|
|
2283
|
-
|
|
2284
|
-
|
|
2285
|
-
|
|
2286
|
-
|
|
2287
|
-
|
|
2288
|
-
|
|
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
|
+
});
|
|
2289
2406
|
return {
|
|
2290
|
-
|
|
2291
|
-
|
|
2292
|
-
|
|
2293
|
-
|
|
2407
|
+
adjustments,
|
|
2408
|
+
alerts: [...this.alerts],
|
|
2409
|
+
health: this.agentE.getHealth(),
|
|
2410
|
+
tick: state.tick,
|
|
2411
|
+
decisions
|
|
2294
2412
|
};
|
|
2295
|
-
}
|
|
2296
|
-
|
|
2297
|
-
|
|
2298
|
-
alerts: [...this.alerts],
|
|
2299
|
-
health: this.agentE.getHealth(),
|
|
2300
|
-
tick: state.tick,
|
|
2301
|
-
decisions
|
|
2302
|
-
};
|
|
2413
|
+
} finally {
|
|
2414
|
+
unlock();
|
|
2415
|
+
}
|
|
2303
2416
|
}
|
|
2304
2417
|
/**
|
|
2305
2418
|
* Run Observer + Diagnoser on the given state without side effects (no execution).
|