@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/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"></script>
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.0</span>
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,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;').replace(/"/g,'&quot;').replace(/'/g,'&#39;').replace(/\\\\/g,'&#92;'); }
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: { 'Content-Type': 'application/json' },
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
- ws = new WebSocket(proto + '//' + location.host);
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", () => resolve(Buffer.concat(chunks).toString("utf-8")));
1552
- req.on("error", reject);
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
- Array.isArray(events) ? events : void 0
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
- respond(200, { decisions });
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
- 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:");
1750
- res.setHeader("Cache-Control", "public, max-age=60");
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 token = url.searchParams.get("token") ?? req.headers["authorization"]?.replace("Bearer ", "");
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
- Array.isArray(events) ? events : void 0
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 event = msg["event"];
1996
- if (event) {
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
- this.adjustmentQueue = [];
2189
- this.alerts = [];
2190
- this.lastState = state;
2191
- if (events) {
2192
- for (const event of events) {
2193
- this.agentE.ingest(event);
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
- await this.agentE.tick(state);
2197
- const rawAdj = [...this.adjustmentQueue];
2198
- this.adjustmentQueue = [];
2199
- const decisions = this.agentE.getDecisions({ since: state.tick, until: state.tick });
2200
- const adjustments = rawAdj.map((adj) => {
2201
- const decision = decisions.find(
2202
- (d) => d.plan.parameter === adj.key && d.result === "applied"
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
- parameter: adj.key,
2206
- value: adj.value,
2207
- ...adj.scope ? { scope: adj.scope } : {},
2208
- reasoning: decision?.diagnosis.violation.suggestedAction.reasoning ?? ""
2407
+ adjustments,
2408
+ alerts: [...this.alerts],
2409
+ health: this.agentE.getHealth(),
2410
+ tick: state.tick,
2411
+ decisions
2209
2412
  };
2210
- });
2211
- return {
2212
- adjustments,
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).