@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 CHANGED
@@ -232,6 +232,60 @@ Connect to the same port via WebSocket upgrade.
232
232
 
233
233
  Heartbeat: Server pings every 30 seconds.
234
234
 
235
+ ## Authentication
236
+
237
+ Protect mutation routes and the dashboard with an API key:
238
+
239
+ ```ts
240
+ const server = new AgentEServer({
241
+ apiKey: process.env.AGENTE_API_KEY,
242
+ });
243
+ ```
244
+
245
+ When `apiKey` is set:
246
+
247
+ - **POST routes** (`/tick`, `/config`, `/approve`, `/reject`, `/diagnose`) require `Authorization: Bearer <key>`.
248
+ - **Sensitive GET routes** (`/decisions`, `/metrics`, `/metrics/personas`, `/pending`) also require the header.
249
+ - **Dashboard** (`GET /`) accepts either the `Authorization` header or a `?token=<key>` query parameter.
250
+ - **WebSocket** accepts the key via `Authorization` header or `?token=<key>` on the upgrade request.
251
+ - **Open routes** (`/health`, `/principles`) remain unauthenticated for health-check probes.
252
+
253
+ All key comparisons use `crypto.timingSafeEqual()` to prevent timing side-channel attacks.
254
+
255
+ ## Security
256
+
257
+ The server includes multiple layers of defense-in-depth:
258
+
259
+ ### Input Validation
260
+
261
+ - **State validation** — all incoming economy state is validated before processing. Invalid state returns detailed errors with field paths.
262
+ - **Event validation** — events are checked for required fields (`type`, `actor`, `timestamp`) and a valid `type` value before ingestion. Malformed events are silently dropped (HTTP) or return an error (WebSocket).
263
+ - **Prototype pollution protection** — `__proto__`, `constructor`, and `prototype` keys are recursively stripped from all parsed JSON bodies.
264
+ - **Body size limits** — HTTP request bodies are capped at 1 MB with a 30-second read timeout to mitigate slow-loris attacks.
265
+ - **Array caps** — configuration arrays (lock/unlock/constrain) are capped at 1,000 entries.
266
+
267
+ ### Rate Limiting
268
+
269
+ - **Per-connection** — each WebSocket connection is limited to one tick per 100 ms.
270
+ - **Global** — a server-wide rate limiter caps ticks at 20/sec across all WebSocket connections to prevent CPU saturation.
271
+ - **Connection limit** — maximum 50 concurrent WebSocket connections; excess connections are closed with code 1013.
272
+
273
+ ### Transport Security
274
+
275
+ - **CORS** — configurable origin restriction via `corsOrigin` (default: `http://localhost:3100`). WebSocket connections from disallowed origins are closed with code 1008.
276
+ - **HSTS** — `Strict-Transport-Security: max-age=31536000; includeSubDomains` header on all responses.
277
+ - **Nonce-based CSP** — the dashboard uses a per-request cryptographic nonce for `script-src`, eliminating `'unsafe-inline'` scripts.
278
+ - **SRI** — the CDN-loaded Chart.js library includes a `integrity` hash and `crossorigin="anonymous"` attribute.
279
+ - **Security headers** — `X-Content-Type-Options: nosniff`, `X-Frame-Options: DENY`, and `Cache-Control: no-cache, private` on all responses.
280
+
281
+ ### Concurrency
282
+
283
+ - **Tick serialization** — `processTick()` uses a Promise-based mutex so concurrent HTTP + WebSocket ticks cannot corrupt shared adjustment queues.
284
+
285
+ ### Data Exposure
286
+
287
+ - **metricsSnapshot stripping** — the `/decisions` endpoint strips full metrics snapshots from decision records to avoid leaking large internal state objects.
288
+
235
289
  ## State Validation
236
290
 
237
291
  All incoming state is validated before processing. Invalid state returns detailed errors with paths:
@@ -0,0 +1,7 @@
1
+ import {
2
+ AgentEServer
3
+ } from "./chunk-3HGNFK4A.mjs";
4
+ export {
5
+ AgentEServer
6
+ };
7
+ //# sourceMappingURL=AgentEServer-A7K6426K.mjs.map
@@ -9,11 +9,12 @@ import {
9
9
  } from "@agent-e/core";
10
10
 
11
11
  // src/routes.ts
12
- import { timingSafeEqual } from "crypto";
12
+ import { timingSafeEqual, randomBytes } from "crypto";
13
13
  import { validateEconomyState } from "@agent-e/core";
14
14
 
15
15
  // src/dashboard.ts
16
- function getDashboardHtml() {
16
+ function getDashboardHtml(nonce) {
17
+ const nonceAttr = nonce ? ` nonce="${nonce}"` : "";
17
18
  return `<!DOCTYPE html>
18
19
  <html lang="en">
19
20
  <head>
@@ -23,7 +24,7 @@ function getDashboardHtml() {
23
24
  <link rel="preconnect" href="https://fonts.googleapis.com">
24
25
  <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
25
26
  <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">
26
- <script src="https://cdn.jsdelivr.net/npm/chart.js@4.5.1/dist/chart.umd.min.js"></script>
27
+ <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>
27
28
  <style>
28
29
  *, *::before, *::after { margin: 0; padding: 0; box-sizing: border-box; }
29
30
 
@@ -844,7 +845,7 @@ function getDashboardHtml() {
844
845
 
845
846
  </main>
846
847
 
847
- <script>
848
+ <script` + nonceAttr + `>
848
849
  (function() {
849
850
  'use strict';
850
851
 
@@ -891,6 +892,9 @@ function getDashboardHtml() {
891
892
  var $dashboardRoot = document.getElementById('dashboard-root');
892
893
 
893
894
  // -- Helpers --
895
+ // SECURITY-CRITICAL: esc() prevents XSS by escaping all HTML-significant characters.
896
+ // Every user-derived or WebSocket-derived value rendered via innerHTML MUST pass through esc().
897
+ // If adding new terminal renderers or DOM builders, always use esc() on dynamic data.
894
898
  function esc(s) { return String(s).replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;').replace(/"/g,'&quot;').replace(/'/g,'&#39;').replace(/\\\\/g,'&#92;'); }
895
899
  function pad(n, w) { return String(n).padStart(w || 4, ' '); }
896
900
  function fmt(n) { return typeof n === 'number' ? n.toFixed(3) : '\\u2014'; }
@@ -1295,14 +1299,25 @@ function getDashboardHtml() {
1295
1299
  }
1296
1300
 
1297
1301
  // -- API calls --
1302
+ // Read token from URL query param \u2014 never embed the API key in the HTML body.
1303
+ var _authKey = new URLSearchParams(location.search).get('token');
1304
+
1305
+ function authHeaders() {
1306
+ var h = {};
1307
+ if (_authKey) h['Authorization'] = 'Bearer ' + _authKey;
1308
+ return h;
1309
+ }
1310
+
1298
1311
  function fetchJSON(path) {
1299
- return fetch(path).then(function(r) { return r.json(); });
1312
+ return fetch(path, { headers: authHeaders() }).then(function(r) { return r.json(); });
1300
1313
  }
1301
1314
 
1302
1315
  function postJSON(path, body) {
1316
+ var h = authHeaders();
1317
+ h['Content-Type'] = 'application/json';
1303
1318
  return fetch(path, {
1304
1319
  method: 'POST',
1305
- headers: { 'Content-Type': 'application/json' },
1320
+ headers: h,
1306
1321
  body: JSON.stringify(body),
1307
1322
  }).then(function(r) { return r.json(); });
1308
1323
  }
@@ -1364,7 +1379,9 @@ function getDashboardHtml() {
1364
1379
  // -- WebSocket --
1365
1380
  function connectWS() {
1366
1381
  var proto = location.protocol === 'https:' ? 'wss:' : 'ws:';
1367
- ws = new WebSocket(proto + '//' + location.host);
1382
+ var wsUrl = proto + '//' + location.host;
1383
+ if (_authKey) wsUrl += '?token=' + encodeURIComponent(_authKey);
1384
+ ws = new WebSocket(wsUrl);
1368
1385
 
1369
1386
  ws.onopen = function() {
1370
1387
  reconnectDelay = 1000;
@@ -1514,11 +1531,30 @@ function getDashboardHtml() {
1514
1531
  </html>`;
1515
1532
  }
1516
1533
 
1534
+ // src/validation.ts
1535
+ var VALID_EVENT_TYPES = /* @__PURE__ */ new Set([
1536
+ "trade",
1537
+ "mint",
1538
+ "burn",
1539
+ "transfer",
1540
+ "produce",
1541
+ "consume",
1542
+ "role_change",
1543
+ "enter",
1544
+ "churn"
1545
+ ]);
1546
+ function validateEvent(e) {
1547
+ if (!e || typeof e !== "object") return false;
1548
+ const ev = e;
1549
+ return typeof ev["type"] === "string" && VALID_EVENT_TYPES.has(ev["type"]) && typeof ev["timestamp"] === "number" && typeof ev["actor"] === "string";
1550
+ }
1551
+
1517
1552
  // src/routes.ts
1518
1553
  function setSecurityHeaders(res) {
1519
1554
  res.setHeader("X-Content-Type-Options", "nosniff");
1520
1555
  res.setHeader("X-Frame-Options", "DENY");
1521
1556
  res.setHeader("Referrer-Policy", "strict-origin-when-cross-origin");
1557
+ res.setHeader("Strict-Transport-Security", "max-age=31536000; includeSubDomains");
1522
1558
  }
1523
1559
  function setCorsHeaders(res, allowedOrigin, requestOrigin) {
1524
1560
  setSecurityHeaders(res);
@@ -1560,21 +1596,34 @@ function json(res, status, data, origin, reqOrigin) {
1560
1596
  res.end(JSON.stringify(data));
1561
1597
  }
1562
1598
  var MAX_BODY_BYTES = 1048576;
1599
+ var READ_BODY_TIMEOUT_MS = 3e4;
1600
+ var MAX_CONFIG_ARRAY = 1e3;
1563
1601
  function readBody(req) {
1564
1602
  return new Promise((resolve, reject) => {
1565
1603
  const chunks = [];
1566
1604
  let totalBytes = 0;
1605
+ const timeout = setTimeout(() => {
1606
+ req.destroy();
1607
+ reject(new Error("Request body read timeout"));
1608
+ }, READ_BODY_TIMEOUT_MS);
1567
1609
  req.on("data", (chunk) => {
1568
1610
  totalBytes += chunk.length;
1569
1611
  if (totalBytes > MAX_BODY_BYTES) {
1612
+ clearTimeout(timeout);
1570
1613
  req.destroy();
1571
1614
  reject(new Error("Request body too large"));
1572
1615
  return;
1573
1616
  }
1574
1617
  chunks.push(chunk);
1575
1618
  });
1576
- req.on("end", () => resolve(Buffer.concat(chunks).toString("utf-8")));
1577
- req.on("error", reject);
1619
+ req.on("end", () => {
1620
+ clearTimeout(timeout);
1621
+ resolve(Buffer.concat(chunks).toString("utf-8"));
1622
+ });
1623
+ req.on("error", (err) => {
1624
+ clearTimeout(timeout);
1625
+ reject(err);
1626
+ });
1578
1627
  });
1579
1628
  }
1580
1629
  function createRouteHandler(server) {
@@ -1621,9 +1670,10 @@ function createRouteHandler(server) {
1621
1670
  });
1622
1671
  return;
1623
1672
  }
1673
+ const validEvents = Array.isArray(events) ? events.filter(validateEvent) : void 0;
1624
1674
  const result = await server.processTick(
1625
1675
  state,
1626
- Array.isArray(events) ? events : void 0
1676
+ validEvents
1627
1677
  );
1628
1678
  const warnings = validation?.warnings ?? [];
1629
1679
  respond(200, {
@@ -1653,6 +1703,10 @@ function createRouteHandler(server) {
1653
1703
  return;
1654
1704
  }
1655
1705
  if (path === "/decisions" && method === "GET") {
1706
+ if (!checkAuth(req, apiKey)) {
1707
+ respond(401, { error: "Unauthorized" });
1708
+ return;
1709
+ }
1656
1710
  const rawLimit = parseInt(url.searchParams.get("limit") ?? "100", 10);
1657
1711
  const limit = Math.min(Math.max(Number.isNaN(rawLimit) ? 100 : rawLimit, 1), 1e3);
1658
1712
  const sinceParam = url.searchParams.get("since");
@@ -1668,7 +1722,11 @@ function createRouteHandler(server) {
1668
1722
  } else {
1669
1723
  decisions = agentE.log.latest(limit);
1670
1724
  }
1671
- respond(200, { decisions });
1725
+ const sanitized = decisions.map((d) => {
1726
+ const { metricsSnapshot: _, ...rest } = d;
1727
+ return rest;
1728
+ });
1729
+ respond(200, { decisions: sanitized });
1672
1730
  return;
1673
1731
  }
1674
1732
  if (path === "/config" && method === "POST") {
@@ -1686,18 +1744,18 @@ function createRouteHandler(server) {
1686
1744
  }
1687
1745
  const config = parsed;
1688
1746
  if (Array.isArray(config["lock"])) {
1689
- for (const param of config["lock"]) {
1747
+ for (const param of config["lock"].slice(0, MAX_CONFIG_ARRAY)) {
1690
1748
  if (typeof param === "string") server.lock(param);
1691
1749
  }
1692
1750
  }
1693
1751
  if (Array.isArray(config["unlock"])) {
1694
- for (const param of config["unlock"]) {
1752
+ for (const param of config["unlock"].slice(0, MAX_CONFIG_ARRAY)) {
1695
1753
  if (typeof param === "string") server.unlock(param);
1696
1754
  }
1697
1755
  }
1698
1756
  if (Array.isArray(config["constrain"])) {
1699
1757
  const validated = [];
1700
- for (const c of config["constrain"]) {
1758
+ for (const c of config["constrain"].slice(0, MAX_CONFIG_ARRAY)) {
1701
1759
  if (c && typeof c === "object" && typeof c["param"] === "string" && typeof c["min"] === "number" && typeof c["max"] === "number") {
1702
1760
  const constraint = c;
1703
1761
  if (!Number.isFinite(constraint.min) || !Number.isFinite(constraint.max)) {
@@ -1770,14 +1828,28 @@ function createRouteHandler(server) {
1770
1828
  return;
1771
1829
  }
1772
1830
  if (path === "/" && method === "GET" && server.serveDashboard) {
1831
+ if (apiKey) {
1832
+ const dashToken = url.searchParams.get("token") ?? "";
1833
+ const hasBearer = checkAuth(req, apiKey);
1834
+ const hasQueryToken = dashToken.length === apiKey.length && timingSafeEqual(Buffer.from(dashToken), Buffer.from(apiKey));
1835
+ if (!hasBearer && !hasQueryToken) {
1836
+ respond(401, { error: "Unauthorized \u2014 use ?token=<apiKey> or Authorization header" });
1837
+ return;
1838
+ }
1839
+ }
1773
1840
  setCorsHeaders(res, cors, reqOrigin);
1774
- 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:");
1775
- res.setHeader("Cache-Control", "public, max-age=60");
1841
+ const nonce = randomBytes(16).toString("base64");
1842
+ 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:`);
1843
+ res.setHeader("Cache-Control", "no-cache, private");
1776
1844
  res.writeHead(200, { "Content-Type": "text/html; charset=utf-8" });
1777
- res.end(getDashboardHtml());
1845
+ res.end(getDashboardHtml(nonce));
1778
1846
  return;
1779
1847
  }
1780
1848
  if (path === "/metrics" && method === "GET") {
1849
+ if (!checkAuth(req, apiKey)) {
1850
+ respond(401, { error: "Unauthorized" });
1851
+ return;
1852
+ }
1781
1853
  const agentE = server.getAgentE();
1782
1854
  const latest = agentE.store.latest();
1783
1855
  const history = agentE.store.recentHistory(100);
@@ -1785,6 +1857,10 @@ function createRouteHandler(server) {
1785
1857
  return;
1786
1858
  }
1787
1859
  if (path === "/metrics/personas" && method === "GET") {
1860
+ if (!checkAuth(req, apiKey)) {
1861
+ respond(401, { error: "Unauthorized" });
1862
+ return;
1863
+ }
1788
1864
  const agentE = server.getAgentE();
1789
1865
  const latest = agentE.store.latest();
1790
1866
  const dist = latest.personaDistribution || {};
@@ -1875,6 +1951,10 @@ function createRouteHandler(server) {
1875
1951
  return;
1876
1952
  }
1877
1953
  if (path === "/pending" && method === "GET") {
1954
+ if (!checkAuth(req, apiKey)) {
1955
+ respond(401, { error: "Unauthorized" });
1956
+ return;
1957
+ }
1878
1958
  const agentE = server.getAgentE();
1879
1959
  const pending = agentE.log.query({ result: "skipped_override" });
1880
1960
  respond(200, {
@@ -1904,6 +1984,7 @@ function send(ws, data) {
1904
1984
  var MAX_WS_PAYLOAD = 1048576;
1905
1985
  var MAX_WS_CONNECTIONS = 100;
1906
1986
  var MIN_TICK_INTERVAL_MS = 100;
1987
+ var GLOBAL_MIN_TICK_INTERVAL_MS = 50;
1907
1988
  function sanitizeJson2(obj) {
1908
1989
  if (obj === null || typeof obj !== "object") return obj;
1909
1990
  if (Array.isArray(obj)) return obj.map(sanitizeJson2);
@@ -1916,6 +1997,7 @@ function sanitizeJson2(obj) {
1916
1997
  }
1917
1998
  function createWebSocketHandler(httpServer, server) {
1918
1999
  const wss = new WebSocketServer({ server: httpServer, maxPayload: MAX_WS_PAYLOAD });
2000
+ let globalLastTickTime = 0;
1919
2001
  const aliveMap = /* @__PURE__ */ new WeakMap();
1920
2002
  const heartbeatInterval = setInterval(() => {
1921
2003
  for (const ws of wss.clients) {
@@ -1943,7 +2025,8 @@ function createWebSocketHandler(httpServer, server) {
1943
2025
  }
1944
2026
  if (server.apiKey) {
1945
2027
  const url = new URL(req.url ?? "/", `http://${req.headers.host ?? "localhost"}`);
1946
- const token = url.searchParams.get("token") ?? req.headers["authorization"]?.replace("Bearer ", "");
2028
+ const authHeader = req.headers["authorization"];
2029
+ const token = (authHeader?.startsWith("Bearer ") ? authHeader.slice(7) : void 0) ?? url.searchParams.get("token");
1947
2030
  if (!token || token.length !== server.apiKey.length || !timingSafeEqual2(Buffer.from(token), Buffer.from(server.apiKey))) {
1948
2031
  ws.close(1008, "Unauthorized");
1949
2032
  return;
@@ -1977,7 +2060,12 @@ function createWebSocketHandler(httpServer, server) {
1977
2060
  send(ws, { type: "error", message: "Rate limited \u2014 min 100ms between ticks" });
1978
2061
  break;
1979
2062
  }
2063
+ if (now - globalLastTickTime < GLOBAL_MIN_TICK_INTERVAL_MS) {
2064
+ send(ws, { type: "error", message: "Rate limited \u2014 server tick capacity exceeded" });
2065
+ break;
2066
+ }
1980
2067
  lastTickTime = now;
2068
+ globalLastTickTime = now;
1981
2069
  const state = msg["state"];
1982
2070
  const events = msg["events"];
1983
2071
  if (server.validateState) {
@@ -1991,9 +2079,10 @@ function createWebSocketHandler(httpServer, server) {
1991
2079
  }
1992
2080
  }
1993
2081
  try {
2082
+ const validEvents = Array.isArray(events) ? events.filter(validateEvent) : void 0;
1994
2083
  const result = await server.processTick(
1995
2084
  state,
1996
- Array.isArray(events) ? events : void 0
2085
+ validEvents
1997
2086
  );
1998
2087
  send(ws, {
1999
2088
  type: "tick_result",
@@ -2013,13 +2102,17 @@ function createWebSocketHandler(httpServer, server) {
2013
2102
  break;
2014
2103
  }
2015
2104
  case "event": {
2016
- const event = msg["event"];
2017
- if (event) {
2018
- server.getAgentE().ingest(event);
2019
- send(ws, { type: "event_ack" });
2020
- } else {
2105
+ const rawEvent = msg["event"];
2106
+ if (!rawEvent) {
2021
2107
  send(ws, { type: "error", message: 'Missing "event" field' });
2108
+ break;
2022
2109
  }
2110
+ if (!validateEvent(rawEvent)) {
2111
+ send(ws, { type: "error", message: "Invalid event \u2014 requires type (valid event type), timestamp (number), and actor (string)" });
2112
+ break;
2113
+ }
2114
+ server.getAgentE().ingest(rawEvent);
2115
+ send(ws, { type: "event_ack" });
2023
2116
  break;
2024
2117
  }
2025
2118
  case "health": {
@@ -2084,6 +2177,8 @@ var AgentEServer = class {
2084
2177
  this.lastState = null;
2085
2178
  this.adjustmentQueue = [];
2086
2179
  this.alerts = [];
2180
+ /** Serialization lock for processTick — prevents concurrent ticks from corrupting shared state. */
2181
+ this.tickLock = Promise.resolve();
2087
2182
  this.startedAt = Date.now();
2088
2183
  this.wsHandle = null;
2089
2184
  this.port = config.port ?? 3100;
@@ -2220,36 +2315,46 @@ var AgentEServer = class {
2220
2315
  * 6. Return response
2221
2316
  */
2222
2317
  async processTick(state, events) {
2223
- this.adjustmentQueue = [];
2224
- this.alerts = [];
2225
- this.lastState = state;
2226
- if (events) {
2227
- for (const event of events) {
2228
- this.agentE.ingest(event);
2318
+ const prev = this.tickLock;
2319
+ let unlock;
2320
+ this.tickLock = new Promise((resolve) => {
2321
+ unlock = resolve;
2322
+ });
2323
+ await prev;
2324
+ try {
2325
+ this.adjustmentQueue = [];
2326
+ this.alerts = [];
2327
+ this.lastState = state;
2328
+ if (events) {
2329
+ for (const event of events) {
2330
+ this.agentE.ingest(event);
2331
+ }
2229
2332
  }
2230
- }
2231
- await this.agentE.tick(state);
2232
- const rawAdj = [...this.adjustmentQueue];
2233
- this.adjustmentQueue = [];
2234
- const decisions = this.agentE.getDecisions({ since: state.tick, until: state.tick });
2235
- const adjustments = rawAdj.map((adj) => {
2236
- const decision = decisions.find(
2237
- (d) => d.plan.parameter === adj.key && d.result === "applied"
2238
- );
2333
+ await this.agentE.tick(state);
2334
+ const rawAdj = [...this.adjustmentQueue];
2335
+ this.adjustmentQueue = [];
2336
+ const decisions = this.agentE.getDecisions({ since: state.tick, until: state.tick });
2337
+ const adjustments = rawAdj.map((adj) => {
2338
+ const decision = decisions.find(
2339
+ (d) => d.plan.parameter === adj.key && d.result === "applied"
2340
+ );
2341
+ return {
2342
+ parameter: adj.key,
2343
+ value: adj.value,
2344
+ ...adj.scope ? { scope: adj.scope } : {},
2345
+ reasoning: decision?.diagnosis.violation.suggestedAction.reasoning ?? ""
2346
+ };
2347
+ });
2239
2348
  return {
2240
- parameter: adj.key,
2241
- value: adj.value,
2242
- ...adj.scope ? { scope: adj.scope } : {},
2243
- reasoning: decision?.diagnosis.violation.suggestedAction.reasoning ?? ""
2349
+ adjustments,
2350
+ alerts: [...this.alerts],
2351
+ health: this.agentE.getHealth(),
2352
+ tick: state.tick,
2353
+ decisions
2244
2354
  };
2245
- });
2246
- return {
2247
- adjustments,
2248
- alerts: [...this.alerts],
2249
- health: this.agentE.getHealth(),
2250
- tick: state.tick,
2251
- decisions
2252
- };
2355
+ } finally {
2356
+ unlock();
2357
+ }
2253
2358
  }
2254
2359
  /**
2255
2360
  * Run Observer + Diagnoser on the given state without side effects (no execution).
@@ -2291,4 +2396,4 @@ var AgentEServer = class {
2291
2396
  export {
2292
2397
  AgentEServer
2293
2398
  };
2294
- //# sourceMappingURL=chunk-ALGME445.mjs.map
2399
+ //# sourceMappingURL=chunk-3HGNFK4A.mjs.map