@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/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"></script>
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
 
@@ -340,6 +341,15 @@ function getDashboardHtml() {
340
341
  .t-pending-icon { color: var(--warning); }
341
342
  .t-pending-val { color: var(--warning); font-variant-numeric: tabular-nums; }
342
343
 
344
+ /* -- LLM line colors (V1.8.1) -- */
345
+ .t-narration-icon { color: #a78bfa; }
346
+ .t-narration { color: #c4b5fd; }
347
+ .t-explanation-icon { color: #60a5fa; }
348
+ .t-explanation { color: #93c5fd; }
349
+ .t-anomaly-icon { color: #f59e0b; }
350
+ .t-anomaly-label { color: #fbbf24; }
351
+ .t-anomaly { color: #fcd34d; }
352
+
343
353
  /* -- Advisor Inline Buttons -- */
344
354
  .advisor-btn {
345
355
  display: none;
@@ -718,7 +728,7 @@ function getDashboardHtml() {
718
728
  <header class="header" id="header">
719
729
  <div class="header-left">
720
730
  <span class="header-logo">AgentE</span>
721
- <span class="header-version">v1.8.0</span>
731
+ <span class="header-version">v1.8.1</span>
722
732
  </div>
723
733
  <div class="header-right">
724
734
  <div class="kpi-pill">
@@ -854,7 +864,7 @@ function getDashboardHtml() {
854
864
 
855
865
  </main>
856
866
 
857
- <script>
867
+ <script` + nonceAttr + `>
858
868
  (function() {
859
869
  'use strict';
860
870
 
@@ -901,6 +911,9 @@ function getDashboardHtml() {
901
911
  var $dashboardRoot = document.getElementById('dashboard-root');
902
912
 
903
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.
904
917
  function esc(s) { return String(s).replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;').replace(/"/g,'&quot;').replace(/'/g,'&#39;').replace(/\\\\/g,'&#92;'); }
905
918
  function pad(n, w) { return String(n).padStart(w || 4, ' '); }
906
919
  function fmt(n) { return typeof n === 'number' ? n.toFixed(3) : '\\u2014'; }
@@ -1056,6 +1069,31 @@ function getDashboardHtml() {
1056
1069
  + advisorBtns;
1057
1070
  }
1058
1071
 
1072
+ // -- LLM line renderers (V1.8.1) --
1073
+ function narrationToTerminal(msg) {
1074
+ return '<span class="t-tick">[Tick ' + pad(msg.tick) + ']</span> '
1075
+ + '<span class="t-narration-icon">\\u{1F9E0} </span>'
1076
+ + '<span class="t-narration">"' + esc(msg.text) + '"</span>';
1077
+ }
1078
+
1079
+ function explanationToTerminal(msg) {
1080
+ return '<span class="t-tick">[Tick ' + pad(msg.tick) + ']</span> '
1081
+ + '<span class="t-explanation-icon">\\u{1F4A1} </span>'
1082
+ + '<span class="t-explanation">"' + esc(msg.text) + '"</span>';
1083
+ }
1084
+
1085
+ function anomalyToTerminal(msg) {
1086
+ var metricsStr = '';
1087
+ if (msg.metrics && msg.metrics.length > 0) {
1088
+ var m = msg.metrics[0];
1089
+ metricsStr = m.name + ' ' + m.deviation.toFixed(1) + '\\u03C3 \u2014 ';
1090
+ }
1091
+ return '<span class="t-tick">[Tick ' + pad(msg.tick) + ']</span> '
1092
+ + '<span class="t-anomaly-icon">\\u{1F50D} </span>'
1093
+ + '<span class="t-anomaly-label">Anomaly detected: </span>'
1094
+ + '<span class="t-anomaly">' + esc(metricsStr) + '"' + esc(msg.text) + '"</span>';
1095
+ }
1096
+
1059
1097
  // -- Alerts --
1060
1098
  function renderAlerts(alerts) {
1061
1099
  if (!alerts || alerts.length === 0) {
@@ -1280,14 +1318,25 @@ function getDashboardHtml() {
1280
1318
  }
1281
1319
 
1282
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
+
1283
1330
  function fetchJSON(path) {
1284
- return fetch(path).then(function(r) { return r.json(); });
1331
+ return fetch(path, { headers: authHeaders() }).then(function(r) { return r.json(); });
1285
1332
  }
1286
1333
 
1287
1334
  function postJSON(path, body) {
1335
+ var h = authHeaders();
1336
+ h['Content-Type'] = 'application/json';
1288
1337
  return fetch(path, {
1289
1338
  method: 'POST',
1290
- headers: { 'Content-Type': 'application/json' },
1339
+ headers: h,
1291
1340
  body: JSON.stringify(body),
1292
1341
  }).then(function(r) { return r.json(); });
1293
1342
  }
@@ -1349,7 +1398,9 @@ function getDashboardHtml() {
1349
1398
  // -- WebSocket --
1350
1399
  function connectWS() {
1351
1400
  var proto = location.protocol === 'https:' ? 'wss:' : 'ws:';
1352
- ws = new WebSocket(proto + '//' + location.host);
1401
+ var wsUrl = proto + '//' + location.host;
1402
+ if (_authKey) wsUrl += '?token=' + encodeURIComponent(_authKey);
1403
+ ws = new WebSocket(wsUrl);
1353
1404
 
1354
1405
  ws.onopen = function() {
1355
1406
  reconnectDelay = 1000;
@@ -1395,6 +1446,19 @@ function getDashboardHtml() {
1395
1446
  $hPending.textContent = pendingDecisions.length;
1396
1447
  }
1397
1448
  break;
1449
+
1450
+ // V1.8.1: LLM feed events
1451
+ case 'narration':
1452
+ addTerminalLine(narrationToTerminal(msg));
1453
+ break;
1454
+
1455
+ case 'explanation':
1456
+ addTerminalLine(explanationToTerminal(msg));
1457
+ break;
1458
+
1459
+ case 'anomaly':
1460
+ addTerminalLine(anomalyToTerminal(msg));
1461
+ break;
1398
1462
  }
1399
1463
  };
1400
1464
  }
@@ -1486,11 +1550,30 @@ function getDashboardHtml() {
1486
1550
  </html>`;
1487
1551
  }
1488
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
+
1489
1571
  // src/routes.ts
1490
1572
  function setSecurityHeaders(res) {
1491
1573
  res.setHeader("X-Content-Type-Options", "nosniff");
1492
1574
  res.setHeader("X-Frame-Options", "DENY");
1493
1575
  res.setHeader("Referrer-Policy", "strict-origin-when-cross-origin");
1576
+ res.setHeader("Strict-Transport-Security", "max-age=31536000; includeSubDomains");
1494
1577
  }
1495
1578
  function setCorsHeaders(res, allowedOrigin, requestOrigin) {
1496
1579
  setSecurityHeaders(res);
@@ -1532,21 +1615,34 @@ function json(res, status, data, origin, reqOrigin) {
1532
1615
  res.end(JSON.stringify(data));
1533
1616
  }
1534
1617
  var MAX_BODY_BYTES = 1048576;
1618
+ var READ_BODY_TIMEOUT_MS = 3e4;
1619
+ var MAX_CONFIG_ARRAY = 1e3;
1535
1620
  function readBody(req) {
1536
1621
  return new Promise((resolve, reject) => {
1537
1622
  const chunks = [];
1538
1623
  let totalBytes = 0;
1624
+ const timeout = setTimeout(() => {
1625
+ req.destroy();
1626
+ reject(new Error("Request body read timeout"));
1627
+ }, READ_BODY_TIMEOUT_MS);
1539
1628
  req.on("data", (chunk) => {
1540
1629
  totalBytes += chunk.length;
1541
1630
  if (totalBytes > MAX_BODY_BYTES) {
1631
+ clearTimeout(timeout);
1542
1632
  req.destroy();
1543
1633
  reject(new Error("Request body too large"));
1544
1634
  return;
1545
1635
  }
1546
1636
  chunks.push(chunk);
1547
1637
  });
1548
- req.on("end", () => resolve(Buffer.concat(chunks).toString("utf-8")));
1549
- req.on("error", reject);
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
+ });
1550
1646
  });
1551
1647
  }
1552
1648
  function createRouteHandler(server2) {
@@ -1593,9 +1689,10 @@ function createRouteHandler(server2) {
1593
1689
  });
1594
1690
  return;
1595
1691
  }
1692
+ const validEvents = Array.isArray(events) ? events.filter(validateEvent) : void 0;
1596
1693
  const result = await server2.processTick(
1597
1694
  state,
1598
- Array.isArray(events) ? events : void 0
1695
+ validEvents
1599
1696
  );
1600
1697
  const warnings = validation?.warnings ?? [];
1601
1698
  respond(200, {
@@ -1625,6 +1722,10 @@ function createRouteHandler(server2) {
1625
1722
  return;
1626
1723
  }
1627
1724
  if (path === "/decisions" && method === "GET") {
1725
+ if (!checkAuth(req, apiKey)) {
1726
+ respond(401, { error: "Unauthorized" });
1727
+ return;
1728
+ }
1628
1729
  const rawLimit = parseInt(url.searchParams.get("limit") ?? "100", 10);
1629
1730
  const limit = Math.min(Math.max(Number.isNaN(rawLimit) ? 100 : rawLimit, 1), 1e3);
1630
1731
  const sinceParam = url.searchParams.get("since");
@@ -1640,7 +1741,11 @@ function createRouteHandler(server2) {
1640
1741
  } else {
1641
1742
  decisions = agentE.log.latest(limit);
1642
1743
  }
1643
- respond(200, { decisions });
1744
+ const sanitized = decisions.map((d) => {
1745
+ const { metricsSnapshot: _, ...rest } = d;
1746
+ return rest;
1747
+ });
1748
+ respond(200, { decisions: sanitized });
1644
1749
  return;
1645
1750
  }
1646
1751
  if (path === "/config" && method === "POST") {
@@ -1658,18 +1763,18 @@ function createRouteHandler(server2) {
1658
1763
  }
1659
1764
  const config = parsed;
1660
1765
  if (Array.isArray(config["lock"])) {
1661
- for (const param of config["lock"]) {
1766
+ for (const param of config["lock"].slice(0, MAX_CONFIG_ARRAY)) {
1662
1767
  if (typeof param === "string") server2.lock(param);
1663
1768
  }
1664
1769
  }
1665
1770
  if (Array.isArray(config["unlock"])) {
1666
- for (const param of config["unlock"]) {
1771
+ for (const param of config["unlock"].slice(0, MAX_CONFIG_ARRAY)) {
1667
1772
  if (typeof param === "string") server2.unlock(param);
1668
1773
  }
1669
1774
  }
1670
1775
  if (Array.isArray(config["constrain"])) {
1671
1776
  const validated = [];
1672
- for (const c of config["constrain"]) {
1777
+ for (const c of config["constrain"].slice(0, MAX_CONFIG_ARRAY)) {
1673
1778
  if (c && typeof c === "object" && typeof c["param"] === "string" && typeof c["min"] === "number" && typeof c["max"] === "number") {
1674
1779
  const constraint = c;
1675
1780
  if (!Number.isFinite(constraint.min) || !Number.isFinite(constraint.max)) {
@@ -1742,14 +1847,28 @@ function createRouteHandler(server2) {
1742
1847
  return;
1743
1848
  }
1744
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
+ }
1745
1859
  setCorsHeaders(res, cors, reqOrigin);
1746
- res.setHeader("Content-Security-Policy", "default-src 'self'; script-src 'unsafe-inline' 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:");
1747
- res.setHeader("Cache-Control", "public, max-age=60");
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");
1748
1863
  res.writeHead(200, { "Content-Type": "text/html; charset=utf-8" });
1749
- res.end(getDashboardHtml());
1864
+ res.end(getDashboardHtml(nonce));
1750
1865
  return;
1751
1866
  }
1752
1867
  if (path === "/metrics" && method === "GET") {
1868
+ if (!checkAuth(req, apiKey)) {
1869
+ respond(401, { error: "Unauthorized" });
1870
+ return;
1871
+ }
1753
1872
  const agentE = server2.getAgentE();
1754
1873
  const latest = agentE.store.latest();
1755
1874
  const history = agentE.store.recentHistory(100);
@@ -1757,6 +1876,10 @@ function createRouteHandler(server2) {
1757
1876
  return;
1758
1877
  }
1759
1878
  if (path === "/metrics/personas" && method === "GET") {
1879
+ if (!checkAuth(req, apiKey)) {
1880
+ respond(401, { error: "Unauthorized" });
1881
+ return;
1882
+ }
1760
1883
  const agentE = server2.getAgentE();
1761
1884
  const latest = agentE.store.latest();
1762
1885
  const dist = latest.personaDistribution || {};
@@ -1847,6 +1970,10 @@ function createRouteHandler(server2) {
1847
1970
  return;
1848
1971
  }
1849
1972
  if (path === "/pending" && method === "GET") {
1973
+ if (!checkAuth(req, apiKey)) {
1974
+ respond(401, { error: "Unauthorized" });
1975
+ return;
1976
+ }
1850
1977
  const agentE = server2.getAgentE();
1851
1978
  const pending = agentE.log.query({ result: "skipped_override" });
1852
1979
  respond(200, {
@@ -1876,6 +2003,7 @@ function send(ws, data) {
1876
2003
  var MAX_WS_PAYLOAD = 1048576;
1877
2004
  var MAX_WS_CONNECTIONS = 100;
1878
2005
  var MIN_TICK_INTERVAL_MS = 100;
2006
+ var GLOBAL_MIN_TICK_INTERVAL_MS = 50;
1879
2007
  function sanitizeJson2(obj) {
1880
2008
  if (obj === null || typeof obj !== "object") return obj;
1881
2009
  if (Array.isArray(obj)) return obj.map(sanitizeJson2);
@@ -1888,6 +2016,7 @@ function sanitizeJson2(obj) {
1888
2016
  }
1889
2017
  function createWebSocketHandler(httpServer, server2) {
1890
2018
  const wss = new import_ws.WebSocketServer({ server: httpServer, maxPayload: MAX_WS_PAYLOAD });
2019
+ let globalLastTickTime = 0;
1891
2020
  const aliveMap = /* @__PURE__ */ new WeakMap();
1892
2021
  const heartbeatInterval = setInterval(() => {
1893
2022
  for (const ws of wss.clients) {
@@ -1915,7 +2044,8 @@ function createWebSocketHandler(httpServer, server2) {
1915
2044
  }
1916
2045
  if (server2.apiKey) {
1917
2046
  const url = new URL(req.url ?? "/", `http://${req.headers.host ?? "localhost"}`);
1918
- const token = url.searchParams.get("token") ?? req.headers["authorization"]?.replace("Bearer ", "");
2047
+ const authHeader = req.headers["authorization"];
2048
+ const token = (authHeader?.startsWith("Bearer ") ? authHeader.slice(7) : void 0) ?? url.searchParams.get("token");
1919
2049
  if (!token || token.length !== server2.apiKey.length || !(0, import_node_crypto2.timingSafeEqual)(Buffer.from(token), Buffer.from(server2.apiKey))) {
1920
2050
  ws.close(1008, "Unauthorized");
1921
2051
  return;
@@ -1949,7 +2079,12 @@ function createWebSocketHandler(httpServer, server2) {
1949
2079
  send(ws, { type: "error", message: "Rate limited \u2014 min 100ms between ticks" });
1950
2080
  break;
1951
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
+ }
1952
2086
  lastTickTime = now;
2087
+ globalLastTickTime = now;
1953
2088
  const state = msg["state"];
1954
2089
  const events = msg["events"];
1955
2090
  if (server2.validateState) {
@@ -1963,9 +2098,10 @@ function createWebSocketHandler(httpServer, server2) {
1963
2098
  }
1964
2099
  }
1965
2100
  try {
2101
+ const validEvents = Array.isArray(events) ? events.filter(validateEvent) : void 0;
1966
2102
  const result = await server2.processTick(
1967
2103
  state,
1968
- Array.isArray(events) ? events : void 0
2104
+ validEvents
1969
2105
  );
1970
2106
  send(ws, {
1971
2107
  type: "tick_result",
@@ -1985,13 +2121,17 @@ function createWebSocketHandler(httpServer, server2) {
1985
2121
  break;
1986
2122
  }
1987
2123
  case "event": {
1988
- const event = msg["event"];
1989
- if (event) {
1990
- server2.getAgentE().ingest(event);
1991
- send(ws, { type: "event_ack" });
1992
- } else {
2124
+ const rawEvent = msg["event"];
2125
+ if (!rawEvent) {
1993
2126
  send(ws, { type: "error", message: 'Missing "event" field' });
2127
+ break;
1994
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" });
1995
2135
  break;
1996
2136
  }
1997
2137
  case "health": {
@@ -2056,6 +2196,8 @@ var AgentEServer = class {
2056
2196
  this.lastState = null;
2057
2197
  this.adjustmentQueue = [];
2058
2198
  this.alerts = [];
2199
+ /** Serialization lock for processTick — prevents concurrent ticks from corrupting shared state. */
2200
+ this.tickLock = Promise.resolve();
2059
2201
  this.startedAt = Date.now();
2060
2202
  this.wsHandle = null;
2061
2203
  this.port = config.port ?? 3100;
@@ -2107,6 +2249,44 @@ var AgentEServer = class {
2107
2249
  this.agentE.on("alert", (diagnosis) => {
2108
2250
  this.alerts.push(diagnosis);
2109
2251
  });
2252
+ this.agentE.on("narration", (n) => {
2253
+ const narration = n;
2254
+ const tick = this.lastState?.tick ?? 0;
2255
+ this.broadcast({
2256
+ type: "narration",
2257
+ tick,
2258
+ text: narration.narration,
2259
+ principle: narration.diagnosis.principle.name,
2260
+ severity: narration.diagnosis.violation.severity,
2261
+ confidence: narration.confidence
2262
+ });
2263
+ });
2264
+ this.agentE.on("explanation", (e) => {
2265
+ const explanation = e;
2266
+ this.broadcast({
2267
+ type: "explanation",
2268
+ tick: this.lastState?.tick ?? 0,
2269
+ text: explanation.explanation,
2270
+ parameter: explanation.plan.parameter,
2271
+ direction: explanation.plan.targetValue > explanation.plan.currentValue ? "increase" : "decrease",
2272
+ expectedOutcome: explanation.expectedOutcome,
2273
+ risks: explanation.risks
2274
+ });
2275
+ });
2276
+ this.agentE.on("anomaly", (a) => {
2277
+ const anomaly = a;
2278
+ this.broadcast({
2279
+ type: "anomaly",
2280
+ tick: anomaly.tick,
2281
+ text: anomaly.interpretation,
2282
+ metrics: anomaly.anomalies.map((m) => ({
2283
+ name: m.metric,
2284
+ deviation: m.deviation,
2285
+ currentValue: m.currentValue
2286
+ })),
2287
+ severity: anomaly.severity
2288
+ });
2289
+ });
2110
2290
  this.agentE.connect(adapter).start();
2111
2291
  const routeHandler = createRouteHandler(this);
2112
2292
  this.server = http.createServer(routeHandler);
@@ -2154,36 +2334,46 @@ var AgentEServer = class {
2154
2334
  * 6. Return response
2155
2335
  */
2156
2336
  async processTick(state, events) {
2157
- this.adjustmentQueue = [];
2158
- this.alerts = [];
2159
- this.lastState = state;
2160
- if (events) {
2161
- for (const event of events) {
2162
- this.agentE.ingest(event);
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
+ }
2163
2351
  }
2164
- }
2165
- await this.agentE.tick(state);
2166
- const rawAdj = [...this.adjustmentQueue];
2167
- this.adjustmentQueue = [];
2168
- const decisions = this.agentE.getDecisions({ since: state.tick, until: state.tick });
2169
- const adjustments = rawAdj.map((adj) => {
2170
- const decision = decisions.find(
2171
- (d) => d.plan.parameter === adj.key && d.result === "applied"
2172
- );
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
+ });
2173
2367
  return {
2174
- parameter: adj.key,
2175
- value: adj.value,
2176
- ...adj.scope ? { scope: adj.scope } : {},
2177
- reasoning: decision?.diagnosis.violation.suggestedAction.reasoning ?? ""
2368
+ adjustments,
2369
+ alerts: [...this.alerts],
2370
+ health: this.agentE.getHealth(),
2371
+ tick: state.tick,
2372
+ decisions
2178
2373
  };
2179
- });
2180
- return {
2181
- adjustments,
2182
- alerts: [...this.alerts],
2183
- health: this.agentE.getHealth(),
2184
- tick: state.tick,
2185
- decisions
2186
- };
2374
+ } finally {
2375
+ unlock();
2376
+ }
2187
2377
  }
2188
2378
  /**
2189
2379
  * Run Observer + Diagnoser on the given state without side effects (no execution).