@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.js
CHANGED
|
@@ -32,7 +32,8 @@ var import_node_crypto = require("crypto");
|
|
|
32
32
|
var import_core = require("@agent-e/core");
|
|
33
33
|
|
|
34
34
|
// src/dashboard.ts
|
|
35
|
-
function getDashboardHtml() {
|
|
35
|
+
function getDashboardHtml(nonce) {
|
|
36
|
+
const nonceAttr = nonce ? ` nonce="${nonce}"` : "";
|
|
36
37
|
return `<!DOCTYPE html>
|
|
37
38
|
<html lang="en">
|
|
38
39
|
<head>
|
|
@@ -42,7 +43,7 @@ function getDashboardHtml() {
|
|
|
42
43
|
<link rel="preconnect" href="https://fonts.googleapis.com">
|
|
43
44
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
|
44
45
|
<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">
|
|
45
|
-
<script src="https://cdn.jsdelivr.net/npm/chart.js@4.5.1/dist/chart.umd.min.js"
|
|
46
|
+
<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>
|
|
46
47
|
<style>
|
|
47
48
|
*, *::before, *::after { margin: 0; padding: 0; box-sizing: border-box; }
|
|
48
49
|
|
|
@@ -863,7 +864,7 @@ function getDashboardHtml() {
|
|
|
863
864
|
|
|
864
865
|
</main>
|
|
865
866
|
|
|
866
|
-
<script
|
|
867
|
+
<script` + nonceAttr + `>
|
|
867
868
|
(function() {
|
|
868
869
|
'use strict';
|
|
869
870
|
|
|
@@ -910,6 +911,9 @@ function getDashboardHtml() {
|
|
|
910
911
|
var $dashboardRoot = document.getElementById('dashboard-root');
|
|
911
912
|
|
|
912
913
|
// -- Helpers --
|
|
914
|
+
// SECURITY-CRITICAL: esc() prevents XSS by escaping all HTML-significant characters.
|
|
915
|
+
// Every user-derived or WebSocket-derived value rendered via innerHTML MUST pass through esc().
|
|
916
|
+
// If adding new terminal renderers or DOM builders, always use esc() on dynamic data.
|
|
913
917
|
function esc(s) { return String(s).replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>').replace(/"/g,'"').replace(/'/g,''').replace(/\\\\/g,'\'); }
|
|
914
918
|
function pad(n, w) { return String(n).padStart(w || 4, ' '); }
|
|
915
919
|
function fmt(n) { return typeof n === 'number' ? n.toFixed(3) : '\\u2014'; }
|
|
@@ -1314,14 +1318,25 @@ function getDashboardHtml() {
|
|
|
1314
1318
|
}
|
|
1315
1319
|
|
|
1316
1320
|
// -- API calls --
|
|
1321
|
+
// Read token from URL query param \u2014 never embed the API key in the HTML body.
|
|
1322
|
+
var _authKey = new URLSearchParams(location.search).get('token');
|
|
1323
|
+
|
|
1324
|
+
function authHeaders() {
|
|
1325
|
+
var h = {};
|
|
1326
|
+
if (_authKey) h['Authorization'] = 'Bearer ' + _authKey;
|
|
1327
|
+
return h;
|
|
1328
|
+
}
|
|
1329
|
+
|
|
1317
1330
|
function fetchJSON(path) {
|
|
1318
|
-
return fetch(path).then(function(r) { return r.json(); });
|
|
1331
|
+
return fetch(path, { headers: authHeaders() }).then(function(r) { return r.json(); });
|
|
1319
1332
|
}
|
|
1320
1333
|
|
|
1321
1334
|
function postJSON(path, body) {
|
|
1335
|
+
var h = authHeaders();
|
|
1336
|
+
h['Content-Type'] = 'application/json';
|
|
1322
1337
|
return fetch(path, {
|
|
1323
1338
|
method: 'POST',
|
|
1324
|
-
headers:
|
|
1339
|
+
headers: h,
|
|
1325
1340
|
body: JSON.stringify(body),
|
|
1326
1341
|
}).then(function(r) { return r.json(); });
|
|
1327
1342
|
}
|
|
@@ -1383,7 +1398,9 @@ function getDashboardHtml() {
|
|
|
1383
1398
|
// -- WebSocket --
|
|
1384
1399
|
function connectWS() {
|
|
1385
1400
|
var proto = location.protocol === 'https:' ? 'wss:' : 'ws:';
|
|
1386
|
-
|
|
1401
|
+
var wsUrl = proto + '//' + location.host;
|
|
1402
|
+
if (_authKey) wsUrl += '?token=' + encodeURIComponent(_authKey);
|
|
1403
|
+
ws = new WebSocket(wsUrl);
|
|
1387
1404
|
|
|
1388
1405
|
ws.onopen = function() {
|
|
1389
1406
|
reconnectDelay = 1000;
|
|
@@ -1533,11 +1550,30 @@ function getDashboardHtml() {
|
|
|
1533
1550
|
</html>`;
|
|
1534
1551
|
}
|
|
1535
1552
|
|
|
1553
|
+
// src/validation.ts
|
|
1554
|
+
var VALID_EVENT_TYPES = /* @__PURE__ */ new Set([
|
|
1555
|
+
"trade",
|
|
1556
|
+
"mint",
|
|
1557
|
+
"burn",
|
|
1558
|
+
"transfer",
|
|
1559
|
+
"produce",
|
|
1560
|
+
"consume",
|
|
1561
|
+
"role_change",
|
|
1562
|
+
"enter",
|
|
1563
|
+
"churn"
|
|
1564
|
+
]);
|
|
1565
|
+
function validateEvent(e) {
|
|
1566
|
+
if (!e || typeof e !== "object") return false;
|
|
1567
|
+
const ev = e;
|
|
1568
|
+
return typeof ev["type"] === "string" && VALID_EVENT_TYPES.has(ev["type"]) && typeof ev["timestamp"] === "number" && typeof ev["actor"] === "string";
|
|
1569
|
+
}
|
|
1570
|
+
|
|
1536
1571
|
// src/routes.ts
|
|
1537
1572
|
function setSecurityHeaders(res) {
|
|
1538
1573
|
res.setHeader("X-Content-Type-Options", "nosniff");
|
|
1539
1574
|
res.setHeader("X-Frame-Options", "DENY");
|
|
1540
1575
|
res.setHeader("Referrer-Policy", "strict-origin-when-cross-origin");
|
|
1576
|
+
res.setHeader("Strict-Transport-Security", "max-age=31536000; includeSubDomains");
|
|
1541
1577
|
}
|
|
1542
1578
|
function setCorsHeaders(res, allowedOrigin, requestOrigin) {
|
|
1543
1579
|
setSecurityHeaders(res);
|
|
@@ -1579,21 +1615,34 @@ function json(res, status, data, origin, reqOrigin) {
|
|
|
1579
1615
|
res.end(JSON.stringify(data));
|
|
1580
1616
|
}
|
|
1581
1617
|
var MAX_BODY_BYTES = 1048576;
|
|
1618
|
+
var READ_BODY_TIMEOUT_MS = 3e4;
|
|
1619
|
+
var MAX_CONFIG_ARRAY = 1e3;
|
|
1582
1620
|
function readBody(req) {
|
|
1583
1621
|
return new Promise((resolve, reject) => {
|
|
1584
1622
|
const chunks = [];
|
|
1585
1623
|
let totalBytes = 0;
|
|
1624
|
+
const timeout = setTimeout(() => {
|
|
1625
|
+
req.destroy();
|
|
1626
|
+
reject(new Error("Request body read timeout"));
|
|
1627
|
+
}, READ_BODY_TIMEOUT_MS);
|
|
1586
1628
|
req.on("data", (chunk) => {
|
|
1587
1629
|
totalBytes += chunk.length;
|
|
1588
1630
|
if (totalBytes > MAX_BODY_BYTES) {
|
|
1631
|
+
clearTimeout(timeout);
|
|
1589
1632
|
req.destroy();
|
|
1590
1633
|
reject(new Error("Request body too large"));
|
|
1591
1634
|
return;
|
|
1592
1635
|
}
|
|
1593
1636
|
chunks.push(chunk);
|
|
1594
1637
|
});
|
|
1595
|
-
req.on("end", () =>
|
|
1596
|
-
|
|
1638
|
+
req.on("end", () => {
|
|
1639
|
+
clearTimeout(timeout);
|
|
1640
|
+
resolve(Buffer.concat(chunks).toString("utf-8"));
|
|
1641
|
+
});
|
|
1642
|
+
req.on("error", (err) => {
|
|
1643
|
+
clearTimeout(timeout);
|
|
1644
|
+
reject(err);
|
|
1645
|
+
});
|
|
1597
1646
|
});
|
|
1598
1647
|
}
|
|
1599
1648
|
function createRouteHandler(server2) {
|
|
@@ -1640,9 +1689,10 @@ function createRouteHandler(server2) {
|
|
|
1640
1689
|
});
|
|
1641
1690
|
return;
|
|
1642
1691
|
}
|
|
1692
|
+
const validEvents = Array.isArray(events) ? events.filter(validateEvent) : void 0;
|
|
1643
1693
|
const result = await server2.processTick(
|
|
1644
1694
|
state,
|
|
1645
|
-
|
|
1695
|
+
validEvents
|
|
1646
1696
|
);
|
|
1647
1697
|
const warnings = validation?.warnings ?? [];
|
|
1648
1698
|
respond(200, {
|
|
@@ -1672,6 +1722,10 @@ function createRouteHandler(server2) {
|
|
|
1672
1722
|
return;
|
|
1673
1723
|
}
|
|
1674
1724
|
if (path === "/decisions" && method === "GET") {
|
|
1725
|
+
if (!checkAuth(req, apiKey)) {
|
|
1726
|
+
respond(401, { error: "Unauthorized" });
|
|
1727
|
+
return;
|
|
1728
|
+
}
|
|
1675
1729
|
const rawLimit = parseInt(url.searchParams.get("limit") ?? "100", 10);
|
|
1676
1730
|
const limit = Math.min(Math.max(Number.isNaN(rawLimit) ? 100 : rawLimit, 1), 1e3);
|
|
1677
1731
|
const sinceParam = url.searchParams.get("since");
|
|
@@ -1687,7 +1741,11 @@ function createRouteHandler(server2) {
|
|
|
1687
1741
|
} else {
|
|
1688
1742
|
decisions = agentE.log.latest(limit);
|
|
1689
1743
|
}
|
|
1690
|
-
|
|
1744
|
+
const sanitized = decisions.map((d) => {
|
|
1745
|
+
const { metricsSnapshot: _, ...rest } = d;
|
|
1746
|
+
return rest;
|
|
1747
|
+
});
|
|
1748
|
+
respond(200, { decisions: sanitized });
|
|
1691
1749
|
return;
|
|
1692
1750
|
}
|
|
1693
1751
|
if (path === "/config" && method === "POST") {
|
|
@@ -1705,18 +1763,18 @@ function createRouteHandler(server2) {
|
|
|
1705
1763
|
}
|
|
1706
1764
|
const config = parsed;
|
|
1707
1765
|
if (Array.isArray(config["lock"])) {
|
|
1708
|
-
for (const param of config["lock"]) {
|
|
1766
|
+
for (const param of config["lock"].slice(0, MAX_CONFIG_ARRAY)) {
|
|
1709
1767
|
if (typeof param === "string") server2.lock(param);
|
|
1710
1768
|
}
|
|
1711
1769
|
}
|
|
1712
1770
|
if (Array.isArray(config["unlock"])) {
|
|
1713
|
-
for (const param of config["unlock"]) {
|
|
1771
|
+
for (const param of config["unlock"].slice(0, MAX_CONFIG_ARRAY)) {
|
|
1714
1772
|
if (typeof param === "string") server2.unlock(param);
|
|
1715
1773
|
}
|
|
1716
1774
|
}
|
|
1717
1775
|
if (Array.isArray(config["constrain"])) {
|
|
1718
1776
|
const validated = [];
|
|
1719
|
-
for (const c of config["constrain"]) {
|
|
1777
|
+
for (const c of config["constrain"].slice(0, MAX_CONFIG_ARRAY)) {
|
|
1720
1778
|
if (c && typeof c === "object" && typeof c["param"] === "string" && typeof c["min"] === "number" && typeof c["max"] === "number") {
|
|
1721
1779
|
const constraint = c;
|
|
1722
1780
|
if (!Number.isFinite(constraint.min) || !Number.isFinite(constraint.max)) {
|
|
@@ -1789,14 +1847,28 @@ function createRouteHandler(server2) {
|
|
|
1789
1847
|
return;
|
|
1790
1848
|
}
|
|
1791
1849
|
if (path === "/" && method === "GET" && server2.serveDashboard) {
|
|
1850
|
+
if (apiKey) {
|
|
1851
|
+
const dashToken = url.searchParams.get("token") ?? "";
|
|
1852
|
+
const hasBearer = checkAuth(req, apiKey);
|
|
1853
|
+
const hasQueryToken = dashToken.length === apiKey.length && (0, import_node_crypto.timingSafeEqual)(Buffer.from(dashToken), Buffer.from(apiKey));
|
|
1854
|
+
if (!hasBearer && !hasQueryToken) {
|
|
1855
|
+
respond(401, { error: "Unauthorized \u2014 use ?token=<apiKey> or Authorization header" });
|
|
1856
|
+
return;
|
|
1857
|
+
}
|
|
1858
|
+
}
|
|
1792
1859
|
setCorsHeaders(res, cors, reqOrigin);
|
|
1793
|
-
|
|
1794
|
-
res.setHeader("
|
|
1860
|
+
const nonce = (0, import_node_crypto.randomBytes)(16).toString("base64");
|
|
1861
|
+
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:`);
|
|
1862
|
+
res.setHeader("Cache-Control", "no-cache, private");
|
|
1795
1863
|
res.writeHead(200, { "Content-Type": "text/html; charset=utf-8" });
|
|
1796
|
-
res.end(getDashboardHtml());
|
|
1864
|
+
res.end(getDashboardHtml(nonce));
|
|
1797
1865
|
return;
|
|
1798
1866
|
}
|
|
1799
1867
|
if (path === "/metrics" && method === "GET") {
|
|
1868
|
+
if (!checkAuth(req, apiKey)) {
|
|
1869
|
+
respond(401, { error: "Unauthorized" });
|
|
1870
|
+
return;
|
|
1871
|
+
}
|
|
1800
1872
|
const agentE = server2.getAgentE();
|
|
1801
1873
|
const latest = agentE.store.latest();
|
|
1802
1874
|
const history = agentE.store.recentHistory(100);
|
|
@@ -1804,6 +1876,10 @@ function createRouteHandler(server2) {
|
|
|
1804
1876
|
return;
|
|
1805
1877
|
}
|
|
1806
1878
|
if (path === "/metrics/personas" && method === "GET") {
|
|
1879
|
+
if (!checkAuth(req, apiKey)) {
|
|
1880
|
+
respond(401, { error: "Unauthorized" });
|
|
1881
|
+
return;
|
|
1882
|
+
}
|
|
1807
1883
|
const agentE = server2.getAgentE();
|
|
1808
1884
|
const latest = agentE.store.latest();
|
|
1809
1885
|
const dist = latest.personaDistribution || {};
|
|
@@ -1894,6 +1970,10 @@ function createRouteHandler(server2) {
|
|
|
1894
1970
|
return;
|
|
1895
1971
|
}
|
|
1896
1972
|
if (path === "/pending" && method === "GET") {
|
|
1973
|
+
if (!checkAuth(req, apiKey)) {
|
|
1974
|
+
respond(401, { error: "Unauthorized" });
|
|
1975
|
+
return;
|
|
1976
|
+
}
|
|
1897
1977
|
const agentE = server2.getAgentE();
|
|
1898
1978
|
const pending = agentE.log.query({ result: "skipped_override" });
|
|
1899
1979
|
respond(200, {
|
|
@@ -1923,6 +2003,7 @@ function send(ws, data) {
|
|
|
1923
2003
|
var MAX_WS_PAYLOAD = 1048576;
|
|
1924
2004
|
var MAX_WS_CONNECTIONS = 100;
|
|
1925
2005
|
var MIN_TICK_INTERVAL_MS = 100;
|
|
2006
|
+
var GLOBAL_MIN_TICK_INTERVAL_MS = 50;
|
|
1926
2007
|
function sanitizeJson2(obj) {
|
|
1927
2008
|
if (obj === null || typeof obj !== "object") return obj;
|
|
1928
2009
|
if (Array.isArray(obj)) return obj.map(sanitizeJson2);
|
|
@@ -1935,6 +2016,7 @@ function sanitizeJson2(obj) {
|
|
|
1935
2016
|
}
|
|
1936
2017
|
function createWebSocketHandler(httpServer, server2) {
|
|
1937
2018
|
const wss = new import_ws.WebSocketServer({ server: httpServer, maxPayload: MAX_WS_PAYLOAD });
|
|
2019
|
+
let globalLastTickTime = 0;
|
|
1938
2020
|
const aliveMap = /* @__PURE__ */ new WeakMap();
|
|
1939
2021
|
const heartbeatInterval = setInterval(() => {
|
|
1940
2022
|
for (const ws of wss.clients) {
|
|
@@ -1962,7 +2044,8 @@ function createWebSocketHandler(httpServer, server2) {
|
|
|
1962
2044
|
}
|
|
1963
2045
|
if (server2.apiKey) {
|
|
1964
2046
|
const url = new URL(req.url ?? "/", `http://${req.headers.host ?? "localhost"}`);
|
|
1965
|
-
const
|
|
2047
|
+
const authHeader = req.headers["authorization"];
|
|
2048
|
+
const token = (authHeader?.startsWith("Bearer ") ? authHeader.slice(7) : void 0) ?? url.searchParams.get("token");
|
|
1966
2049
|
if (!token || token.length !== server2.apiKey.length || !(0, import_node_crypto2.timingSafeEqual)(Buffer.from(token), Buffer.from(server2.apiKey))) {
|
|
1967
2050
|
ws.close(1008, "Unauthorized");
|
|
1968
2051
|
return;
|
|
@@ -1996,7 +2079,12 @@ function createWebSocketHandler(httpServer, server2) {
|
|
|
1996
2079
|
send(ws, { type: "error", message: "Rate limited \u2014 min 100ms between ticks" });
|
|
1997
2080
|
break;
|
|
1998
2081
|
}
|
|
2082
|
+
if (now - globalLastTickTime < GLOBAL_MIN_TICK_INTERVAL_MS) {
|
|
2083
|
+
send(ws, { type: "error", message: "Rate limited \u2014 server tick capacity exceeded" });
|
|
2084
|
+
break;
|
|
2085
|
+
}
|
|
1999
2086
|
lastTickTime = now;
|
|
2087
|
+
globalLastTickTime = now;
|
|
2000
2088
|
const state = msg["state"];
|
|
2001
2089
|
const events = msg["events"];
|
|
2002
2090
|
if (server2.validateState) {
|
|
@@ -2010,9 +2098,10 @@ function createWebSocketHandler(httpServer, server2) {
|
|
|
2010
2098
|
}
|
|
2011
2099
|
}
|
|
2012
2100
|
try {
|
|
2101
|
+
const validEvents = Array.isArray(events) ? events.filter(validateEvent) : void 0;
|
|
2013
2102
|
const result = await server2.processTick(
|
|
2014
2103
|
state,
|
|
2015
|
-
|
|
2104
|
+
validEvents
|
|
2016
2105
|
);
|
|
2017
2106
|
send(ws, {
|
|
2018
2107
|
type: "tick_result",
|
|
@@ -2032,13 +2121,17 @@ function createWebSocketHandler(httpServer, server2) {
|
|
|
2032
2121
|
break;
|
|
2033
2122
|
}
|
|
2034
2123
|
case "event": {
|
|
2035
|
-
const
|
|
2036
|
-
if (
|
|
2037
|
-
server2.getAgentE().ingest(event);
|
|
2038
|
-
send(ws, { type: "event_ack" });
|
|
2039
|
-
} else {
|
|
2124
|
+
const rawEvent = msg["event"];
|
|
2125
|
+
if (!rawEvent) {
|
|
2040
2126
|
send(ws, { type: "error", message: 'Missing "event" field' });
|
|
2127
|
+
break;
|
|
2041
2128
|
}
|
|
2129
|
+
if (!validateEvent(rawEvent)) {
|
|
2130
|
+
send(ws, { type: "error", message: "Invalid event \u2014 requires type (valid event type), timestamp (number), and actor (string)" });
|
|
2131
|
+
break;
|
|
2132
|
+
}
|
|
2133
|
+
server2.getAgentE().ingest(rawEvent);
|
|
2134
|
+
send(ws, { type: "event_ack" });
|
|
2042
2135
|
break;
|
|
2043
2136
|
}
|
|
2044
2137
|
case "health": {
|
|
@@ -2103,6 +2196,8 @@ var AgentEServer = class {
|
|
|
2103
2196
|
this.lastState = null;
|
|
2104
2197
|
this.adjustmentQueue = [];
|
|
2105
2198
|
this.alerts = [];
|
|
2199
|
+
/** Serialization lock for processTick — prevents concurrent ticks from corrupting shared state. */
|
|
2200
|
+
this.tickLock = Promise.resolve();
|
|
2106
2201
|
this.startedAt = Date.now();
|
|
2107
2202
|
this.wsHandle = null;
|
|
2108
2203
|
this.port = config.port ?? 3100;
|
|
@@ -2239,36 +2334,46 @@ var AgentEServer = class {
|
|
|
2239
2334
|
* 6. Return response
|
|
2240
2335
|
*/
|
|
2241
2336
|
async processTick(state, events) {
|
|
2242
|
-
|
|
2243
|
-
|
|
2244
|
-
this.
|
|
2245
|
-
|
|
2246
|
-
|
|
2247
|
-
|
|
2337
|
+
const prev = this.tickLock;
|
|
2338
|
+
let unlock;
|
|
2339
|
+
this.tickLock = new Promise((resolve) => {
|
|
2340
|
+
unlock = resolve;
|
|
2341
|
+
});
|
|
2342
|
+
await prev;
|
|
2343
|
+
try {
|
|
2344
|
+
this.adjustmentQueue = [];
|
|
2345
|
+
this.alerts = [];
|
|
2346
|
+
this.lastState = state;
|
|
2347
|
+
if (events) {
|
|
2348
|
+
for (const event of events) {
|
|
2349
|
+
this.agentE.ingest(event);
|
|
2350
|
+
}
|
|
2248
2351
|
}
|
|
2249
|
-
|
|
2250
|
-
|
|
2251
|
-
|
|
2252
|
-
|
|
2253
|
-
|
|
2254
|
-
|
|
2255
|
-
|
|
2256
|
-
|
|
2257
|
-
|
|
2352
|
+
await this.agentE.tick(state);
|
|
2353
|
+
const rawAdj = [...this.adjustmentQueue];
|
|
2354
|
+
this.adjustmentQueue = [];
|
|
2355
|
+
const decisions = this.agentE.getDecisions({ since: state.tick, until: state.tick });
|
|
2356
|
+
const adjustments = rawAdj.map((adj) => {
|
|
2357
|
+
const decision = decisions.find(
|
|
2358
|
+
(d) => d.plan.parameter === adj.key && d.result === "applied"
|
|
2359
|
+
);
|
|
2360
|
+
return {
|
|
2361
|
+
parameter: adj.key,
|
|
2362
|
+
value: adj.value,
|
|
2363
|
+
...adj.scope ? { scope: adj.scope } : {},
|
|
2364
|
+
reasoning: decision?.diagnosis.violation.suggestedAction.reasoning ?? ""
|
|
2365
|
+
};
|
|
2366
|
+
});
|
|
2258
2367
|
return {
|
|
2259
|
-
|
|
2260
|
-
|
|
2261
|
-
|
|
2262
|
-
|
|
2368
|
+
adjustments,
|
|
2369
|
+
alerts: [...this.alerts],
|
|
2370
|
+
health: this.agentE.getHealth(),
|
|
2371
|
+
tick: state.tick,
|
|
2372
|
+
decisions
|
|
2263
2373
|
};
|
|
2264
|
-
}
|
|
2265
|
-
|
|
2266
|
-
|
|
2267
|
-
alerts: [...this.alerts],
|
|
2268
|
-
health: this.agentE.getHealth(),
|
|
2269
|
-
tick: state.tick,
|
|
2270
|
-
decisions
|
|
2271
|
-
};
|
|
2374
|
+
} finally {
|
|
2375
|
+
unlock();
|
|
2376
|
+
}
|
|
2272
2377
|
}
|
|
2273
2378
|
/**
|
|
2274
2379
|
* Run Observer + Diagnoser on the given state without side effects (no execution).
|