@agent-e/server 1.6.9 → 1.6.11

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.
@@ -0,0 +1,7 @@
1
+ import {
2
+ AgentEServer
3
+ } from "./chunk-WUZUDVHX.mjs";
4
+ export {
5
+ AgentEServer
6
+ };
7
+ //# sourceMappingURL=AgentEServer-IXEWHDCG.mjs.map
@@ -955,8 +955,17 @@ function setSecurityHeaders(res) {
955
955
  }
956
956
  function setCorsHeaders(res, allowedOrigin, requestOrigin) {
957
957
  setSecurityHeaders(res);
958
- const origin = allowedOrigin === "*" ? "*" : requestOrigin === allowedOrigin ? allowedOrigin : allowedOrigin;
959
- res.setHeader("Access-Control-Allow-Origin", origin);
958
+ let origin;
959
+ if (allowedOrigin === "*") {
960
+ origin = "*";
961
+ } else if (requestOrigin === void 0) {
962
+ origin = allowedOrigin;
963
+ } else {
964
+ origin = requestOrigin === allowedOrigin ? allowedOrigin : "";
965
+ }
966
+ if (origin) {
967
+ res.setHeader("Access-Control-Allow-Origin", origin);
968
+ }
960
969
  res.setHeader("Access-Control-Allow-Methods", "GET, POST, OPTIONS");
961
970
  res.setHeader("Access-Control-Allow-Headers", "Content-Type, Authorization");
962
971
  }
@@ -1005,8 +1014,10 @@ function createRouteHandler(server) {
1005
1014
  const url = new URL(req.url ?? "/", `http://${req.headers.host ?? "localhost"}`);
1006
1015
  const path = url.pathname;
1007
1016
  const method = req.method?.toUpperCase() ?? "GET";
1017
+ const reqOrigin = req.headers["origin"];
1018
+ const respond = (status, data) => json(res, status, data, cors, reqOrigin);
1008
1019
  if (method === "OPTIONS") {
1009
- setCorsHeaders(res, cors);
1020
+ setCorsHeaders(res, cors, reqOrigin);
1010
1021
  res.writeHead(204);
1011
1022
  res.end();
1012
1023
  return;
@@ -1014,7 +1025,7 @@ function createRouteHandler(server) {
1014
1025
  try {
1015
1026
  if (path === "/tick" && method === "POST") {
1016
1027
  if (!checkAuth(req, apiKey)) {
1017
- json(res, 401, { error: "Unauthorized" }, cors);
1028
+ respond(401, { error: "Unauthorized" });
1018
1029
  return;
1019
1030
  }
1020
1031
  const body = await readBody(req);
@@ -1022,11 +1033,11 @@ function createRouteHandler(server) {
1022
1033
  try {
1023
1034
  parsed = sanitizeJson(JSON.parse(body));
1024
1035
  } catch {
1025
- json(res, 400, { error: "Invalid JSON" }, cors);
1036
+ respond(400, { error: "Invalid JSON" });
1026
1037
  return;
1027
1038
  }
1028
1039
  if (!parsed || typeof parsed !== "object") {
1029
- json(res, 400, { error: "Body must be a JSON object" }, cors);
1040
+ respond(400, { error: "Body must be a JSON object" });
1030
1041
  return;
1031
1042
  }
1032
1043
  const payload = parsed;
@@ -1034,10 +1045,10 @@ function createRouteHandler(server) {
1034
1045
  const events = payload["events"];
1035
1046
  const validation = server.validateState ? validateEconomyState(state) : null;
1036
1047
  if (validation && !validation.valid) {
1037
- json(res, 400, {
1048
+ respond(400, {
1038
1049
  error: "invalid_state",
1039
1050
  validationErrors: validation.errors
1040
- }, cors);
1051
+ });
1041
1052
  return;
1042
1053
  }
1043
1054
  const result = await server.processTick(
@@ -1045,7 +1056,7 @@ function createRouteHandler(server) {
1045
1056
  Array.isArray(events) ? events : void 0
1046
1057
  );
1047
1058
  const warnings = validation?.warnings ?? [];
1048
- json(res, 200, {
1059
+ respond(200, {
1049
1060
  adjustments: result.adjustments,
1050
1061
  alerts: result.alerts.map((a) => ({
1051
1062
  principleId: a.principle.id,
@@ -1057,18 +1068,18 @@ function createRouteHandler(server) {
1057
1068
  health: result.health,
1058
1069
  tick: result.tick,
1059
1070
  ...warnings.length > 0 ? { validationWarnings: warnings } : {}
1060
- }, cors);
1071
+ });
1061
1072
  return;
1062
1073
  }
1063
1074
  if (path === "/health" && method === "GET") {
1064
1075
  const agentE = server.getAgentE();
1065
- json(res, 200, {
1076
+ respond(200, {
1066
1077
  health: agentE.getHealth(),
1067
1078
  tick: agentE.metrics.latest()?.tick ?? 0,
1068
1079
  mode: agentE.getMode(),
1069
1080
  activePlans: agentE.getActivePlans().length,
1070
1081
  uptime: server.getUptime()
1071
- }, cors);
1082
+ });
1072
1083
  return;
1073
1084
  }
1074
1085
  if (path === "/decisions" && method === "GET") {
@@ -1080,19 +1091,19 @@ function createRouteHandler(server) {
1080
1091
  if (sinceParam) {
1081
1092
  const since = parseInt(sinceParam, 10);
1082
1093
  if (Number.isNaN(since)) {
1083
- json(res, 400, { error: 'Invalid "since" parameter \u2014 must be a number' }, cors);
1094
+ respond(400, { error: 'Invalid "since" parameter \u2014 must be a number' });
1084
1095
  return;
1085
1096
  }
1086
1097
  decisions = agentE.getDecisions({ since });
1087
1098
  } else {
1088
1099
  decisions = agentE.log.latest(limit);
1089
1100
  }
1090
- json(res, 200, { decisions }, cors);
1101
+ respond(200, { decisions });
1091
1102
  return;
1092
1103
  }
1093
1104
  if (path === "/config" && method === "POST") {
1094
1105
  if (!checkAuth(req, apiKey)) {
1095
- json(res, 401, { error: "Unauthorized" }, cors);
1106
+ respond(401, { error: "Unauthorized" });
1096
1107
  return;
1097
1108
  }
1098
1109
  const body = await readBody(req);
@@ -1100,7 +1111,7 @@ function createRouteHandler(server) {
1100
1111
  try {
1101
1112
  parsed = sanitizeJson(JSON.parse(body));
1102
1113
  } catch {
1103
- json(res, 400, { error: "Invalid JSON" }, cors);
1114
+ respond(400, { error: "Invalid JSON" });
1104
1115
  return;
1105
1116
  }
1106
1117
  const config = parsed;
@@ -1120,11 +1131,11 @@ function createRouteHandler(server) {
1120
1131
  if (c && typeof c === "object" && typeof c["param"] === "string" && typeof c["min"] === "number" && typeof c["max"] === "number") {
1121
1132
  const constraint = c;
1122
1133
  if (!Number.isFinite(constraint.min) || !Number.isFinite(constraint.max)) {
1123
- json(res, 400, { error: "Constraint bounds must be finite numbers" }, cors);
1134
+ respond(400, { error: "Constraint bounds must be finite numbers" });
1124
1135
  return;
1125
1136
  }
1126
1137
  if (constraint.min > constraint.max) {
1127
- json(res, 400, { error: "Constraint min cannot exceed max" }, cors);
1138
+ respond(400, { error: "Constraint min cannot exceed max" });
1128
1139
  return;
1129
1140
  }
1130
1141
  validated.push(constraint);
@@ -1137,12 +1148,12 @@ function createRouteHandler(server) {
1137
1148
  if (config["mode"] === "autonomous" || config["mode"] === "advisor") {
1138
1149
  server.setMode(config["mode"]);
1139
1150
  }
1140
- json(res, 200, { ok: true }, cors);
1151
+ respond(200, { ok: true });
1141
1152
  return;
1142
1153
  }
1143
1154
  if (path === "/principles" && method === "GET") {
1144
1155
  const principles = server.getAgentE().getPrinciples();
1145
- json(res, 200, {
1156
+ respond(200, {
1146
1157
  count: principles.length,
1147
1158
  principles: principles.map((p) => ({
1148
1159
  id: p.id,
@@ -1150,12 +1161,12 @@ function createRouteHandler(server) {
1150
1161
  category: p.category,
1151
1162
  description: p.description
1152
1163
  }))
1153
- }, cors);
1164
+ });
1154
1165
  return;
1155
1166
  }
1156
1167
  if (path === "/diagnose" && method === "POST") {
1157
1168
  if (!checkAuth(req, apiKey)) {
1158
- json(res, 401, { error: "Unauthorized" }, cors);
1169
+ respond(401, { error: "Unauthorized" });
1159
1170
  return;
1160
1171
  }
1161
1172
  const body = await readBody(req);
@@ -1163,7 +1174,7 @@ function createRouteHandler(server) {
1163
1174
  try {
1164
1175
  parsed = sanitizeJson(JSON.parse(body));
1165
1176
  } catch {
1166
- json(res, 400, { error: "Invalid JSON" }, cors);
1177
+ respond(400, { error: "Invalid JSON" });
1167
1178
  return;
1168
1179
  }
1169
1180
  const payload = parsed;
@@ -1171,12 +1182,12 @@ function createRouteHandler(server) {
1171
1182
  if (server.validateState) {
1172
1183
  const validation = validateEconomyState(state);
1173
1184
  if (!validation.valid) {
1174
- json(res, 400, { error: "invalid_state", validationErrors: validation.errors }, cors);
1185
+ respond(400, { error: "invalid_state", validationErrors: validation.errors });
1175
1186
  return;
1176
1187
  }
1177
1188
  }
1178
1189
  const result = server.diagnoseOnly(state);
1179
- json(res, 200, {
1190
+ respond(200, {
1180
1191
  health: result.health,
1181
1192
  diagnoses: result.diagnoses.map((d) => ({
1182
1193
  principleId: d.principle.id,
@@ -1185,11 +1196,11 @@ function createRouteHandler(server) {
1185
1196
  evidence: d.violation.evidence,
1186
1197
  suggestedAction: d.violation.suggestedAction
1187
1198
  }))
1188
- }, cors);
1199
+ });
1189
1200
  return;
1190
1201
  }
1191
1202
  if (path === "/" && method === "GET" && server.serveDashboard) {
1192
- setCorsHeaders(res, cors);
1203
+ setCorsHeaders(res, cors, reqOrigin);
1193
1204
  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:");
1194
1205
  res.setHeader("Cache-Control", "public, max-age=60");
1195
1206
  res.writeHead(200, { "Content-Type": "text/html; charset=utf-8" });
@@ -1200,7 +1211,7 @@ function createRouteHandler(server) {
1200
1211
  const agentE = server.getAgentE();
1201
1212
  const latest = agentE.store.latest();
1202
1213
  const history = agentE.store.recentHistory(100);
1203
- json(res, 200, { latest, history }, cors);
1214
+ respond(200, { latest, history });
1204
1215
  return;
1205
1216
  }
1206
1217
  if (path === "/metrics/personas" && method === "GET") {
@@ -1208,12 +1219,12 @@ function createRouteHandler(server) {
1208
1219
  const latest = agentE.store.latest();
1209
1220
  const dist = latest.personaDistribution || {};
1210
1221
  const total = Object.values(dist).reduce((s, v) => s + v, 0);
1211
- json(res, 200, { distribution: dist, total }, cors);
1222
+ respond(200, { distribution: dist, total });
1212
1223
  return;
1213
1224
  }
1214
1225
  if (path === "/approve" && method === "POST") {
1215
1226
  if (!checkAuth(req, apiKey)) {
1216
- json(res, 401, { error: "Unauthorized" }, cors);
1227
+ respond(401, { error: "Unauthorized" });
1217
1228
  return;
1218
1229
  }
1219
1230
  const body = await readBody(req);
@@ -1221,42 +1232,42 @@ function createRouteHandler(server) {
1221
1232
  try {
1222
1233
  parsed = sanitizeJson(JSON.parse(body));
1223
1234
  } catch {
1224
- json(res, 400, { error: "Invalid JSON" }, cors);
1235
+ respond(400, { error: "Invalid JSON" });
1225
1236
  return;
1226
1237
  }
1227
1238
  const payload = parsed;
1228
1239
  const decisionId = payload["decisionId"];
1229
1240
  if (!decisionId) {
1230
- json(res, 400, { error: "missing_decision_id" }, cors);
1241
+ respond(400, { error: "missing_decision_id" });
1231
1242
  return;
1232
1243
  }
1233
1244
  const agentE = server.getAgentE();
1234
1245
  if (agentE.getMode() !== "advisor") {
1235
- json(res, 400, { error: "not_in_advisor_mode" }, cors);
1246
+ respond(400, { error: "not_in_advisor_mode" });
1236
1247
  return;
1237
1248
  }
1238
1249
  const entry = agentE.log.getById(decisionId);
1239
1250
  if (!entry) {
1240
- json(res, 404, { error: "decision_not_found" }, cors);
1251
+ respond(404, { error: "decision_not_found" });
1241
1252
  return;
1242
1253
  }
1243
1254
  if (entry.result !== "skipped_override") {
1244
- json(res, 409, { error: "decision_not_pending", currentResult: entry.result }, cors);
1255
+ respond(409, { error: "decision_not_pending", currentResult: entry.result });
1245
1256
  return;
1246
1257
  }
1247
1258
  await agentE.apply(entry.plan);
1248
1259
  agentE.log.updateResult(decisionId, "applied");
1249
1260
  server.broadcast({ type: "advisor_action", action: "approved", decisionId });
1250
- json(res, 200, {
1261
+ respond(200, {
1251
1262
  ok: true,
1252
1263
  parameter: entry.plan.parameter,
1253
1264
  value: entry.plan.targetValue
1254
- }, cors);
1265
+ });
1255
1266
  return;
1256
1267
  }
1257
1268
  if (path === "/reject" && method === "POST") {
1258
1269
  if (!checkAuth(req, apiKey)) {
1259
- json(res, 401, { error: "Unauthorized" }, cors);
1270
+ respond(401, { error: "Unauthorized" });
1260
1271
  return;
1261
1272
  }
1262
1273
  const body = await readBody(req);
@@ -1264,49 +1275,49 @@ function createRouteHandler(server) {
1264
1275
  try {
1265
1276
  parsed = sanitizeJson(JSON.parse(body));
1266
1277
  } catch {
1267
- json(res, 400, { error: "Invalid JSON" }, cors);
1278
+ respond(400, { error: "Invalid JSON" });
1268
1279
  return;
1269
1280
  }
1270
1281
  const payload = parsed;
1271
1282
  const decisionId = payload["decisionId"];
1272
1283
  const reason = payload["reason"] || void 0;
1273
1284
  if (!decisionId) {
1274
- json(res, 400, { error: "missing_decision_id" }, cors);
1285
+ respond(400, { error: "missing_decision_id" });
1275
1286
  return;
1276
1287
  }
1277
1288
  const agentE = server.getAgentE();
1278
1289
  if (agentE.getMode() !== "advisor") {
1279
- json(res, 400, { error: "not_in_advisor_mode" }, cors);
1290
+ respond(400, { error: "not_in_advisor_mode" });
1280
1291
  return;
1281
1292
  }
1282
1293
  const entry = agentE.log.getById(decisionId);
1283
1294
  if (!entry) {
1284
- json(res, 404, { error: "decision_not_found" }, cors);
1295
+ respond(404, { error: "decision_not_found" });
1285
1296
  return;
1286
1297
  }
1287
1298
  if (entry.result !== "skipped_override") {
1288
- json(res, 409, { error: "decision_not_pending", currentResult: entry.result }, cors);
1299
+ respond(409, { error: "decision_not_pending", currentResult: entry.result });
1289
1300
  return;
1290
1301
  }
1291
1302
  agentE.log.updateResult(decisionId, "rejected", reason);
1292
1303
  server.broadcast({ type: "advisor_action", action: "rejected", decisionId, reason });
1293
- json(res, 200, { ok: true, decisionId }, cors);
1304
+ respond(200, { ok: true, decisionId });
1294
1305
  return;
1295
1306
  }
1296
1307
  if (path === "/pending" && method === "GET") {
1297
1308
  const agentE = server.getAgentE();
1298
1309
  const pending = agentE.log.query({ result: "skipped_override" });
1299
- json(res, 200, {
1310
+ respond(200, {
1300
1311
  mode: agentE.getMode(),
1301
1312
  pending,
1302
1313
  count: pending.length
1303
- }, cors);
1314
+ });
1304
1315
  return;
1305
1316
  }
1306
- json(res, 404, { error: "Not found" }, cors);
1317
+ respond(404, { error: "Not found" });
1307
1318
  } catch (err) {
1308
1319
  console.error("[AgentE Server] Unhandled route error:", err);
1309
- json(res, 500, { error: "Internal server error" }, cors);
1320
+ respond(500, { error: "Internal server error" });
1310
1321
  }
1311
1322
  };
1312
1323
  }
@@ -1664,4 +1675,4 @@ var AgentEServer = class {
1664
1675
  export {
1665
1676
  AgentEServer
1666
1677
  };
1667
- //# sourceMappingURL=chunk-MH5XTBNY.mjs.map
1678
+ //# sourceMappingURL=chunk-WUZUDVHX.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/AgentEServer.ts","../src/routes.ts","../src/dashboard.ts","../src/websocket.ts"],"sourcesContent":["// AgentEServer — HTTP + WebSocket transport for AgentE\n\nimport * as http from 'node:http';\nimport {\n AgentE,\n Observer,\n Diagnoser,\n ALL_PRINCIPLES,\n DEFAULT_THRESHOLDS,\n type AgentEConfig,\n type EconomyAdapter,\n type EconomyState,\n type EconomicEvent,\n type Diagnosis,\n type AgentEMode,\n type Thresholds,\n} from '@agent-e/core';\nimport { createRouteHandler } from './routes.js';\nimport { createWebSocketHandler, type WebSocketHandle } from './websocket.js';\n\nexport interface ServerConfig {\n port?: number;\n host?: string;\n agentE?: Partial<Omit<AgentEConfig, 'adapter'>>;\n validateState?: boolean;\n corsOrigin?: string;\n serveDashboard?: boolean;\n /** API key for authenticating mutation routes. When set, POST routes and WebSocket require `Authorization: Bearer <key>`. */\n apiKey?: string;\n}\n\nexport interface EnrichedAdjustment {\n parameter: string;\n value: number;\n scope?: import('@agent-e/core').ParameterScope;\n reasoning: string;\n}\n\ninterface QueuedAdjustment {\n key: string;\n value: number;\n scope: import('@agent-e/core').ParameterScope | undefined;\n}\n\nexport class AgentEServer {\n private readonly agentE: AgentE;\n private readonly server: http.Server;\n private lastState: EconomyState | null = null;\n private adjustmentQueue: QueuedAdjustment[] = [];\n private alerts: Diagnosis[] = [];\n readonly port: number;\n private readonly host: string;\n private readonly thresholds: Thresholds;\n private readonly startedAt = Date.now();\n private wsHandle: WebSocketHandle | null = null;\n readonly validateState: boolean;\n readonly corsOrigin: string;\n readonly serveDashboard: boolean;\n readonly apiKey: string | undefined;\n\n constructor(config: ServerConfig = {}) {\n this.port = config.port ?? 3100;\n this.host = config.host ?? '127.0.0.1';\n this.apiKey = config.apiKey;\n this.validateState = config.validateState ?? true;\n this.corsOrigin = config.corsOrigin ?? 'http://localhost:3100';\n this.serveDashboard = config.serveDashboard ?? true;\n\n // Build a \"remote\" adapter — state comes from HTTP/WS, not polled\n const adapter: EconomyAdapter = {\n getState: () => {\n if (!this.lastState) {\n return {\n tick: 0,\n roles: [],\n resources: [],\n currencies: ['default'],\n agentBalances: {},\n agentRoles: {},\n agentInventories: {},\n marketPrices: {},\n recentTransactions: [],\n };\n }\n return this.lastState;\n },\n setParam: (key: string, value: number, scope?: import('@agent-e/core').ParameterScope) => {\n this.adjustmentQueue.push({ key, value, scope });\n },\n };\n\n const agentECfg = config.agentE ?? {};\n const agentEConfig: AgentEConfig = {\n adapter,\n mode: agentECfg.mode ?? 'autonomous',\n gracePeriod: agentECfg.gracePeriod ?? 0,\n checkInterval: agentECfg.checkInterval ?? 1,\n ...(agentECfg.dominantRoles ? { dominantRoles: agentECfg.dominantRoles } : {}),\n ...(agentECfg.idealDistribution ? { idealDistribution: agentECfg.idealDistribution } : {}),\n ...(agentECfg.maxAdjustmentPercent !== undefined ? { maxAdjustmentPercent: agentECfg.maxAdjustmentPercent } : {}),\n ...(agentECfg.cooldownTicks !== undefined ? { cooldownTicks: agentECfg.cooldownTicks } : {}),\n ...(agentECfg.thresholds ? { thresholds: agentECfg.thresholds } : {}),\n };\n\n this.thresholds = {\n ...DEFAULT_THRESHOLDS,\n ...(agentECfg.thresholds ?? {}),\n ...(agentECfg.maxAdjustmentPercent !== undefined ? { maxAdjustmentPercent: agentECfg.maxAdjustmentPercent } : {}),\n ...(agentECfg.cooldownTicks !== undefined ? { cooldownTicks: agentECfg.cooldownTicks } : {}),\n };\n this.agentE = new AgentE(agentEConfig);\n\n // Capture alerts during tick\n this.agentE.on('alert', (diagnosis: unknown) => {\n this.alerts.push(diagnosis as Diagnosis);\n });\n\n this.agentE.connect(adapter).start();\n\n // Create HTTP server\n const routeHandler = createRouteHandler(this);\n this.server = http.createServer(routeHandler);\n }\n\n async start(): Promise<void> {\n // Wire up WebSocket upgrade\n this.wsHandle = createWebSocketHandler(this.server, this);\n\n return new Promise((resolve) => {\n this.server.listen(this.port, this.host, () => {\n const addr = this.getAddress();\n console.log(`[AgentE Server] Listening on http://${addr.host}:${addr.port}`);\n resolve();\n });\n });\n }\n\n async stop(): Promise<void> {\n this.agentE.stop();\n if (this.wsHandle) this.wsHandle.cleanup();\n return new Promise((resolve, reject) => {\n this.server.close((err) => {\n if (err) reject(err);\n else resolve();\n });\n });\n }\n\n getAgentE(): AgentE {\n return this.agentE;\n }\n\n getAddress(): { port: number; host: string } {\n const addr = this.server.address();\n if (addr && typeof addr === 'object') {\n return { port: addr.port, host: addr.address };\n }\n return { port: this.port, host: this.host };\n }\n\n getUptime(): number {\n return Date.now() - this.startedAt;\n }\n\n /**\n * Process a tick with the given state.\n * 1. Clear adjustment queue\n * 2. Set state\n * 3. Ingest events\n * 4. Run agentE.tick(state)\n * 5. Drain adjustment queue, enrich with reasoning from decisions\n * 6. Return response\n */\n async processTick(\n state: EconomyState,\n events?: EconomicEvent[],\n ): Promise<{\n adjustments: EnrichedAdjustment[];\n alerts: Diagnosis[];\n health: number;\n tick: number;\n decisions: ReturnType<AgentE['getDecisions']>;\n }> {\n // Clear queues\n this.adjustmentQueue = [];\n this.alerts = [];\n\n // Set state\n this.lastState = state;\n\n // Ingest events\n if (events) {\n for (const event of events) {\n this.agentE.ingest(event);\n }\n }\n\n // Run tick\n await this.agentE.tick(state);\n\n // Drain adjustments\n const rawAdj = [...this.adjustmentQueue];\n this.adjustmentQueue = [];\n\n // Cross-reference with decision log to attach reasoning\n const decisions = this.agentE.getDecisions({ since: state.tick, until: state.tick });\n\n const adjustments: EnrichedAdjustment[] = rawAdj.map(adj => {\n const decision = decisions.find(d =>\n d.plan.parameter === adj.key && d.result === 'applied',\n );\n return {\n parameter: adj.key,\n value: adj.value,\n ...(adj.scope ? { scope: adj.scope } : {}),\n reasoning: decision?.diagnosis.violation.suggestedAction.reasoning ?? '',\n };\n });\n\n return {\n adjustments,\n alerts: [...this.alerts],\n health: this.agentE.getHealth(),\n tick: state.tick,\n decisions,\n };\n }\n\n /**\n * Run Observer + Diagnoser on the given state without side effects (no execution).\n * Computes fresh metrics from the state rather than reading stored metrics.\n */\n diagnoseOnly(state: EconomyState): {\n diagnoses: ReturnType<AgentE['diagnoseNow']>;\n health: number;\n } {\n const observer = new Observer();\n const diagnoser = new Diagnoser(ALL_PRINCIPLES);\n const metrics = observer.compute(state, []);\n const diagnoses = diagnoser.diagnose(metrics, this.thresholds);\n\n // Mirrors AgentE.getHealth() — keep in sync if that logic changes\n let health = 100;\n if (metrics.avgSatisfaction < 65) health -= 15;\n if (metrics.avgSatisfaction < 50) health -= 10;\n if (metrics.giniCoefficient > 0.45) health -= 15;\n if (metrics.giniCoefficient > 0.60) health -= 10;\n if (Math.abs(metrics.netFlow) > 10) health -= 15;\n if (Math.abs(metrics.netFlow) > 20) health -= 10;\n if (metrics.churnRate > 0.05) health -= 15;\n health = Math.max(0, Math.min(100, health));\n\n return { diagnoses, health };\n }\n\n setMode(mode: AgentEMode): void {\n this.agentE.setMode(mode);\n }\n\n lock(param: string): void {\n this.agentE.lock(param);\n }\n\n unlock(param: string): void {\n this.agentE.unlock(param);\n }\n\n constrain(param: string, bounds: { min: number; max: number }): void {\n this.agentE.constrain(param, bounds);\n }\n\n broadcast(data: Record<string, unknown>): void {\n if (this.wsHandle) this.wsHandle.broadcast(data);\n }\n}\n","// HTTP routes for AgentE Server\n// Node http module with manual body parsing. CORS on all responses.\n\nimport type * as http from 'node:http';\nimport { validateEconomyState } from '@agent-e/core';\nimport type { AgentEServer } from './AgentEServer.js';\nimport { getDashboardHtml } from './dashboard.js';\n\nfunction setSecurityHeaders(res: http.ServerResponse): void {\n res.setHeader('X-Content-Type-Options', 'nosniff');\n res.setHeader('X-Frame-Options', 'DENY');\n res.setHeader('Referrer-Policy', 'strict-origin-when-cross-origin');\n}\n\nfunction setCorsHeaders(res: http.ServerResponse, allowedOrigin: string, requestOrigin?: string): void {\n setSecurityHeaders(res);\n // If configured as '*', allow all.\n // Otherwise, only reflect the origin if it matches the configured allowedOrigin.\n // If there's no request origin (non-browser / server-to-server), return allowedOrigin directly.\n let origin: string;\n if (allowedOrigin === '*') {\n origin = '*';\n } else if (requestOrigin === undefined) {\n origin = allowedOrigin; // non-browser request, return configured origin\n } else {\n // Reflect origin only if it matches — otherwise don't include a matching CORS header\n origin = requestOrigin === allowedOrigin ? allowedOrigin : '';\n }\n if (origin) {\n res.setHeader('Access-Control-Allow-Origin', origin);\n }\n res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS');\n res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization');\n}\n\n/** Strips prototype-polluting keys from parsed JSON objects (recursive). */\nfunction sanitizeJson(obj: unknown): unknown {\n if (obj === null || typeof obj !== 'object') return obj;\n if (Array.isArray(obj)) return obj.map(sanitizeJson);\n const clean: Record<string, unknown> = {};\n for (const [key, val] of Object.entries(obj as Record<string, unknown>)) {\n if (key === '__proto__' || key === 'constructor' || key === 'prototype') continue;\n clean[key] = sanitizeJson(val);\n }\n return clean;\n}\n\nfunction checkAuth(req: http.IncomingMessage, apiKey: string | undefined): boolean {\n if (!apiKey) return true; // no key configured = open\n const header = req.headers['authorization'];\n return header === `Bearer ${apiKey}`;\n}\n\nfunction json(res: http.ServerResponse, status: number, data: unknown, origin: string, reqOrigin?: string): void {\n setCorsHeaders(res, origin, reqOrigin);\n res.writeHead(status, { 'Content-Type': 'application/json' });\n res.end(JSON.stringify(data));\n}\n\nconst MAX_BODY_BYTES = 1_048_576; // 1 MB\n\nfunction readBody(req: http.IncomingMessage): Promise<string> {\n return new Promise((resolve, reject) => {\n const chunks: Buffer[] = [];\n let totalBytes = 0;\n req.on('data', (chunk: Buffer) => {\n totalBytes += chunk.length;\n if (totalBytes > MAX_BODY_BYTES) {\n req.destroy();\n reject(new Error('Request body too large'));\n return;\n }\n chunks.push(chunk);\n });\n req.on('end', () => resolve(Buffer.concat(chunks).toString('utf-8')));\n req.on('error', reject);\n });\n}\n\nexport function createRouteHandler(\n server: AgentEServer,\n): (req: http.IncomingMessage, res: http.ServerResponse) => void {\n const cors = server.corsOrigin;\n const apiKey = server.apiKey;\n\n return async (req, res) => {\n const url = new URL(req.url ?? '/', `http://${req.headers.host ?? 'localhost'}`);\n const path = url.pathname;\n const method = req.method?.toUpperCase() ?? 'GET';\n const reqOrigin = req.headers['origin'] as string | undefined;\n\n // Scoped json helper — captures cors + reqOrigin for this request\n const respond = (status: number, data: unknown) => json(res, status, data, cors, reqOrigin);\n\n // CORS preflight\n if (method === 'OPTIONS') {\n setCorsHeaders(res, cors, reqOrigin);\n res.writeHead(204);\n res.end();\n return;\n }\n\n try {\n // POST /tick — validate state, run tick, return adjustments/alerts/health\n if (path === '/tick' && method === 'POST') {\n if (!checkAuth(req, apiKey)) {\n respond(401, { error: 'Unauthorized' });\n return;\n }\n const body = await readBody(req);\n let parsed: unknown;\n try {\n parsed = sanitizeJson(JSON.parse(body));\n } catch {\n respond(400, { error: 'Invalid JSON' });\n return;\n }\n\n if (!parsed || typeof parsed !== 'object') {\n respond(400, { error: 'Body must be a JSON object' });\n return;\n }\n\n const payload = parsed as Record<string, unknown>;\n const state = payload['state'] ?? parsed;\n const events = payload['events'];\n\n // Validate state (if enabled)\n const validation = server.validateState ? validateEconomyState(state) : null;\n if (validation && !validation.valid) {\n respond(400, {\n error: 'invalid_state',\n validationErrors: validation.errors,\n });\n return;\n }\n\n const result = await server.processTick(\n state as import('@agent-e/core').EconomyState,\n Array.isArray(events) ? events as import('@agent-e/core').EconomicEvent[] : undefined,\n );\n\n const warnings = validation?.warnings ?? [];\n\n respond(200, {\n adjustments: result.adjustments,\n alerts: result.alerts.map(a => ({\n principleId: a.principle.id,\n principleName: a.principle.name,\n severity: a.violation.severity,\n evidence: a.violation.evidence,\n reasoning: a.violation.suggestedAction.reasoning,\n })),\n health: result.health,\n tick: result.tick,\n ...(warnings.length > 0 ? { validationWarnings: warnings } : {}),\n });\n return;\n }\n\n // GET /health — health, tick, mode, activePlans, uptime\n if (path === '/health' && method === 'GET') {\n const agentE = server.getAgentE();\n respond(200, {\n health: agentE.getHealth(),\n tick: agentE.metrics.latest()?.tick ?? 0,\n mode: agentE.getMode(),\n activePlans: agentE.getActivePlans().length,\n uptime: server.getUptime(),\n });\n return;\n }\n\n // GET /decisions — decision log with optional ?limit and ?since\n if (path === '/decisions' && method === 'GET') {\n const rawLimit = parseInt(url.searchParams.get('limit') ?? '100', 10);\n const limit = Math.min(Math.max(Number.isNaN(rawLimit) ? 100 : rawLimit, 1), 1000);\n const sinceParam = url.searchParams.get('since');\n const agentE = server.getAgentE();\n\n let decisions;\n if (sinceParam) {\n const since = parseInt(sinceParam, 10);\n if (Number.isNaN(since)) {\n respond(400, { error: 'Invalid \"since\" parameter — must be a number' });\n return;\n }\n decisions = agentE.getDecisions({ since });\n } else {\n decisions = agentE.log.latest(limit);\n }\n\n respond(200, { decisions });\n return;\n }\n\n // POST /config — batch lock/unlock/constrain/mode\n if (path === '/config' && method === 'POST') {\n if (!checkAuth(req, apiKey)) {\n respond(401, { error: 'Unauthorized' });\n return;\n }\n const body = await readBody(req);\n let parsed: unknown;\n try {\n parsed = sanitizeJson(JSON.parse(body));\n } catch {\n respond(400, { error: 'Invalid JSON' });\n return;\n }\n\n const config = parsed as Record<string, unknown>;\n\n // Lock parameters\n if (Array.isArray(config['lock'])) {\n for (const param of config['lock']) {\n if (typeof param === 'string') server.lock(param);\n }\n }\n\n // Unlock parameters\n if (Array.isArray(config['unlock'])) {\n for (const param of config['unlock']) {\n if (typeof param === 'string') server.unlock(param);\n }\n }\n\n // Constrain parameters — validate ALL before applying any\n if (Array.isArray(config['constrain'])) {\n const validated: { param: string; min: number; max: number }[] = [];\n for (const c of config['constrain'] as unknown[]) {\n if (\n c && typeof c === 'object' &&\n typeof (c as Record<string, unknown>)['param'] === 'string' &&\n typeof (c as Record<string, unknown>)['min'] === 'number' &&\n typeof (c as Record<string, unknown>)['max'] === 'number'\n ) {\n const constraint = c as { param: string; min: number; max: number };\n if (!Number.isFinite(constraint.min) || !Number.isFinite(constraint.max)) {\n respond(400, { error: 'Constraint bounds must be finite numbers' });\n return;\n }\n if (constraint.min > constraint.max) {\n respond(400, { error: 'Constraint min cannot exceed max' });\n return;\n }\n validated.push(constraint);\n }\n }\n for (const constraint of validated) {\n server.constrain(constraint.param, { min: constraint.min, max: constraint.max });\n }\n }\n\n // Mode switch\n if (config['mode'] === 'autonomous' || config['mode'] === 'advisor') {\n server.setMode(config['mode']);\n }\n\n respond(200, { ok: true });\n return;\n }\n\n // GET /principles — list all principles\n if (path === '/principles' && method === 'GET') {\n const principles = server.getAgentE().getPrinciples();\n respond(200, {\n count: principles.length,\n principles: principles.map(p => ({\n id: p.id,\n name: p.name,\n category: p.category,\n description: p.description,\n })),\n });\n return;\n }\n\n // POST /diagnose — standalone Observer+Diagnoser (no side effects)\n if (path === '/diagnose' && method === 'POST') {\n if (!checkAuth(req, apiKey)) {\n respond(401, { error: 'Unauthorized' });\n return;\n }\n const body = await readBody(req);\n let parsed: unknown;\n try {\n parsed = sanitizeJson(JSON.parse(body));\n } catch {\n respond(400, { error: 'Invalid JSON' });\n return;\n }\n\n const payload = parsed as Record<string, unknown>;\n const state = payload['state'] ?? parsed;\n\n if (server.validateState) {\n const validation = validateEconomyState(state);\n if (!validation.valid) {\n respond(400, { error: 'invalid_state', validationErrors: validation.errors });\n return;\n }\n }\n\n const result = server.diagnoseOnly(state as import('@agent-e/core').EconomyState);\n\n respond(200, {\n health: result.health,\n diagnoses: result.diagnoses.map(d => ({\n principleId: d.principle.id,\n principleName: d.principle.name,\n severity: d.violation.severity,\n evidence: d.violation.evidence,\n suggestedAction: d.violation.suggestedAction,\n })),\n });\n return;\n }\n\n // GET / — Dashboard HTML\n if (path === '/' && method === 'GET' && server.serveDashboard) {\n setCorsHeaders(res, cors, reqOrigin);\n 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:\");\n res.setHeader('Cache-Control', 'public, max-age=60');\n res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });\n res.end(getDashboardHtml());\n return;\n }\n\n // GET /metrics — Latest metrics + history for dashboard charts\n if (path === '/metrics' && method === 'GET') {\n const agentE = server.getAgentE();\n const latest = agentE.store.latest();\n const history = agentE.store.recentHistory(100);\n respond(200, { latest, history });\n return;\n }\n\n // GET /metrics/personas — Persona distribution\n if (path === '/metrics/personas' && method === 'GET') {\n const agentE = server.getAgentE();\n const latest = agentE.store.latest();\n const dist = latest.personaDistribution || {};\n const total = Object.values(dist).reduce((s: number, v) => s + (v as number), 0);\n respond(200, { distribution: dist, total });\n return;\n }\n\n // POST /approve — Approve advisor recommendation\n if (path === '/approve' && method === 'POST') {\n if (!checkAuth(req, apiKey)) {\n respond(401, { error: 'Unauthorized' });\n return;\n }\n const body = await readBody(req);\n let parsed: unknown;\n try { parsed = sanitizeJson(JSON.parse(body)); } catch {\n respond(400, { error: 'Invalid JSON' });\n return;\n }\n const payload = parsed as Record<string, unknown>;\n const decisionId = payload['decisionId'] as string;\n if (!decisionId) {\n respond(400, { error: 'missing_decision_id' });\n return;\n }\n\n const agentE = server.getAgentE();\n if (agentE.getMode() !== 'advisor') {\n respond(400, { error: 'not_in_advisor_mode' });\n return;\n }\n\n const entry = agentE.log.getById(decisionId);\n if (!entry) {\n respond(404, { error: 'decision_not_found' });\n return;\n }\n if (entry.result !== 'skipped_override') {\n respond(409, { error: 'decision_not_pending', currentResult: entry.result });\n return;\n }\n\n await agentE.apply(entry.plan);\n agentE.log.updateResult(decisionId, 'applied');\n server.broadcast({ type: 'advisor_action', action: 'approved', decisionId });\n respond(200, {\n ok: true,\n parameter: entry.plan.parameter,\n value: entry.plan.targetValue,\n });\n return;\n }\n\n // POST /reject — Reject advisor recommendation\n if (path === '/reject' && method === 'POST') {\n if (!checkAuth(req, apiKey)) {\n respond(401, { error: 'Unauthorized' });\n return;\n }\n const body = await readBody(req);\n let parsed: unknown;\n try { parsed = sanitizeJson(JSON.parse(body)); } catch {\n respond(400, { error: 'Invalid JSON' });\n return;\n }\n const payload = parsed as Record<string, unknown>;\n const decisionId = payload['decisionId'] as string;\n const reason = (payload['reason'] as string) || undefined;\n if (!decisionId) {\n respond(400, { error: 'missing_decision_id' });\n return;\n }\n\n const agentE = server.getAgentE();\n if (agentE.getMode() !== 'advisor') {\n respond(400, { error: 'not_in_advisor_mode' });\n return;\n }\n\n const entry = agentE.log.getById(decisionId);\n if (!entry) {\n respond(404, { error: 'decision_not_found' });\n return;\n }\n if (entry.result !== 'skipped_override') {\n respond(409, { error: 'decision_not_pending', currentResult: entry.result });\n return;\n }\n\n agentE.log.updateResult(decisionId, 'rejected', reason);\n server.broadcast({ type: 'advisor_action', action: 'rejected', decisionId, reason });\n respond(200, { ok: true, decisionId });\n return;\n }\n\n // GET /pending — List pending advisor recommendations\n if (path === '/pending' && method === 'GET') {\n const agentE = server.getAgentE();\n const pending = agentE.log.query({ result: 'skipped_override' });\n respond(200, {\n mode: agentE.getMode(),\n pending,\n count: pending.length,\n });\n return;\n }\n\n // 404\n respond(404, { error: 'Not found' });\n } catch (err) {\n console.error('[AgentE Server] Unhandled route error:', err);\n respond(500, { error: 'Internal server error' });\n }\n };\n}\n","// Dashboard HTML — self-contained single-page dashboard served at GET /\n// Inline CSS + Chart.js CDN + WebSocket real-time updates\n\nexport function getDashboardHtml(): string {\n return `<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n<meta charset=\"UTF-8\">\n<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n<title>AgentE Dashboard</title>\n<link rel=\"preconnect\" href=\"https://fonts.googleapis.com\">\n<link rel=\"preconnect\" href=\"https://fonts.gstatic.com\" crossorigin>\n<link href=\"https://fonts.googleapis.com/css2?family=IBM+Plex+Sans:wght@400;500;600&family=JetBrains+Mono:wght@400;500&display=swap\" rel=\"stylesheet\">\n<script src=\"https://cdn.jsdelivr.net/npm/chart.js@4.5.1/dist/chart.umd.min.js\"></script>\n<style>\n :root {\n --bg-root: #09090b;\n --bg-panel: #18181b;\n --bg-panel-hover: #1f1f23;\n --border: #27272a;\n --border-light: #3f3f46;\n --text-primary: #f4f4f5;\n --text-secondary: #a1a1aa;\n --text-muted: #71717a;\n --text-dim: #52525b;\n --accent: #22c55e;\n --accent-dim: #166534;\n --warning: #eab308;\n --warning-dim: #854d0e;\n --danger: #ef4444;\n --danger-dim: #991b1b;\n --blue: #3b82f6;\n --font-sans: 'IBM Plex Sans', system-ui, sans-serif;\n --font-mono: 'JetBrains Mono', monospace;\n }\n\n * { margin: 0; padding: 0; box-sizing: border-box; }\n\n body {\n background: var(--bg-root);\n color: var(--text-primary);\n font-family: var(--font-sans);\n font-size: 14px;\n line-height: 1.5;\n overflow-x: hidden;\n }\n\n /* ── Header ─────────────────────────────────────── */\n .header {\n position: sticky;\n top: 0;\n z-index: 100;\n background: var(--bg-root);\n border-bottom: 1px solid var(--border);\n padding: 12px 24px;\n display: flex;\n align-items: center;\n gap: 24px;\n backdrop-filter: blur(8px);\n }\n\n .header-brand {\n font-weight: 600;\n font-size: 16px;\n color: var(--text-primary);\n white-space: nowrap;\n }\n\n .header-brand span { color: var(--accent); }\n\n .kpi-row {\n display: flex;\n gap: 20px;\n flex-wrap: wrap;\n align-items: center;\n margin-left: auto;\n }\n\n .kpi {\n display: flex;\n align-items: center;\n gap: 6px;\n font-size: 13px;\n color: var(--text-secondary);\n }\n\n .kpi-value {\n font-family: var(--font-mono);\n font-weight: 500;\n color: var(--text-primary);\n font-size: 13px;\n }\n\n .kpi-value.health-good { color: var(--accent); }\n .kpi-value.health-warn { color: var(--warning); }\n .kpi-value.health-bad { color: var(--danger); }\n\n .live-dot {\n width: 8px;\n height: 8px;\n border-radius: 50%;\n background: var(--accent);\n animation: pulse 2s ease-in-out infinite;\n }\n\n .live-dot.disconnected {\n background: var(--danger);\n animation: none;\n }\n\n @keyframes pulse {\n 0%, 100% { opacity: 1; }\n 50% { opacity: 0.4; }\n }\n\n /* ── Layout ─────────────────────────────────────── */\n .container {\n max-width: 1440px;\n margin: 0 auto;\n padding: 20px 24px;\n display: flex;\n flex-direction: column;\n gap: 16px;\n }\n\n .panel {\n background: var(--bg-panel);\n border: 1px solid var(--border);\n border-radius: 8px;\n padding: 16px;\n }\n\n .panel-title {\n font-size: 13px;\n font-weight: 600;\n color: var(--text-secondary);\n text-transform: uppercase;\n letter-spacing: 0.05em;\n margin-bottom: 12px;\n }\n\n /* ── Charts grid ────────────────────────────────── */\n .charts-grid {\n display: grid;\n grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));\n gap: 16px;\n }\n\n .chart-box {\n background: var(--bg-panel);\n border: 1px solid var(--border);\n border-radius: 8px;\n padding: 16px;\n }\n\n .chart-box canvas { width: 100% !important; height: 160px !important; }\n\n .chart-label {\n font-size: 12px;\n color: var(--text-muted);\n font-weight: 500;\n margin-bottom: 8px;\n }\n\n .chart-value {\n font-family: var(--font-mono);\n font-size: 22px;\n font-weight: 500;\n color: var(--text-primary);\n margin-bottom: 8px;\n }\n\n /* ── Terminal (Decision Feed) ───────────────────── */\n .terminal {\n background: var(--bg-root);\n border: 1px solid var(--border);\n border-radius: 8px;\n height: 380px;\n overflow-y: auto;\n font-family: var(--font-mono);\n font-size: 12px;\n line-height: 1.7;\n padding: 12px 16px;\n }\n\n .terminal::-webkit-scrollbar { width: 6px; }\n .terminal::-webkit-scrollbar-track { background: transparent; }\n .terminal::-webkit-scrollbar-thumb { background: var(--border-light); border-radius: 3px; }\n\n .term-line {\n white-space: nowrap;\n opacity: 0;\n transform: translateY(4px);\n animation: termIn 0.3s ease-out forwards;\n }\n\n @keyframes termIn {\n to { opacity: 1; transform: translateY(0); }\n }\n\n .t-tick { color: var(--text-dim); }\n .t-ok { color: var(--accent); }\n .t-skip { color: var(--warning); }\n .t-fail { color: var(--danger); }\n .t-principle { color: var(--text-primary); font-weight: 500; }\n .t-param { color: var(--text-secondary); }\n .t-old { color: #d4d4d8; font-variant-numeric: tabular-nums; }\n .t-arrow { color: var(--text-dim); }\n .t-new { color: var(--accent); font-variant-numeric: tabular-nums; }\n .t-meta { color: var(--text-dim); }\n\n /* ── Alerts ─────────────────────────────────────── */\n .alerts-container {\n display: flex;\n flex-direction: column;\n gap: 8px;\n max-height: 320px;\n overflow-y: auto;\n }\n\n .alert-card {\n display: flex;\n align-items: flex-start;\n gap: 12px;\n padding: 12px;\n border-radius: 6px;\n border: 1px solid var(--border);\n background: var(--bg-panel);\n transition: opacity 0.3s, transform 0.3s;\n }\n\n .alert-card.fade-out {\n opacity: 0;\n transform: translateX(20px);\n }\n\n .alert-severity {\n font-family: var(--font-mono);\n font-weight: 600;\n font-size: 13px;\n padding: 2px 8px;\n border-radius: 4px;\n white-space: nowrap;\n }\n\n .sev-high { background: var(--danger-dim); color: var(--danger); }\n .sev-med { background: var(--warning-dim); color: var(--warning); }\n .sev-low { background: var(--accent-dim); color: var(--accent); }\n\n .alert-body { flex: 1; }\n .alert-principle { font-weight: 500; font-size: 13px; }\n .alert-reason { color: var(--text-secondary); font-size: 12px; margin-top: 2px; }\n\n /* ── Violations table ──────────────────────────── */\n .violations-table {\n width: 100%;\n border-collapse: collapse;\n font-size: 12px;\n }\n\n .violations-table th {\n text-align: left;\n color: var(--text-muted);\n font-weight: 500;\n padding: 6px 10px;\n border-bottom: 1px solid var(--border);\n cursor: pointer;\n user-select: none;\n }\n\n .violations-table th:hover { color: var(--text-secondary); }\n\n .violations-table td {\n padding: 6px 10px;\n border-bottom: 1px solid var(--border);\n color: var(--text-secondary);\n font-family: var(--font-mono);\n font-size: 11px;\n }\n\n .violations-table tr:hover td { background: var(--bg-panel-hover); }\n\n /* ── Split row ─────────────────────────────────── */\n .split-row {\n display: grid;\n grid-template-columns: 1fr 1fr;\n gap: 16px;\n }\n\n @media (max-width: 800px) {\n .split-row { grid-template-columns: 1fr; }\n }\n\n /* ── Persona bar chart ─────────────────────────── */\n .persona-bars { display: flex; flex-direction: column; gap: 6px; }\n\n .persona-row {\n display: flex;\n align-items: center;\n gap: 8px;\n font-size: 12px;\n }\n\n .persona-label {\n width: 100px;\n text-align: right;\n color: var(--text-secondary);\n font-size: 11px;\n flex-shrink: 0;\n }\n\n .persona-bar-track {\n flex: 1;\n height: 16px;\n background: var(--bg-root);\n border-radius: 3px;\n overflow: hidden;\n }\n\n .persona-bar-fill {\n height: 100%;\n background: var(--accent);\n border-radius: 3px;\n transition: width 0.5s ease;\n }\n\n .persona-pct {\n width: 40px;\n font-family: var(--font-mono);\n font-size: 11px;\n color: var(--text-muted);\n }\n\n /* ── Registry list ─────────────────────────────── */\n .registry-list { display: flex; flex-direction: column; gap: 4px; }\n\n .registry-item {\n display: flex;\n justify-content: space-between;\n align-items: center;\n padding: 6px 10px;\n border-radius: 4px;\n font-size: 12px;\n }\n\n .registry-item:nth-child(odd) { background: rgba(255,255,255,0.02); }\n .registry-key { color: var(--text-secondary); font-family: var(--font-mono); }\n .registry-val { color: var(--accent); font-family: var(--font-mono); font-weight: 500; }\n\n /* ── Advisor mode ──────────────────────────────── */\n .advisor-banner {\n display: none;\n background: var(--warning-dim);\n border: 1px solid var(--warning);\n color: var(--warning);\n padding: 8px 16px;\n border-radius: 6px;\n font-size: 13px;\n font-weight: 500;\n align-items: center;\n gap: 8px;\n }\n\n .advisor-mode .advisor-banner { display: flex; }\n\n .pending-pill {\n background: var(--warning);\n color: var(--bg-root);\n font-size: 11px;\n font-weight: 600;\n padding: 1px 8px;\n border-radius: 10px;\n font-family: var(--font-mono);\n }\n\n .advisor-btn {\n font-family: var(--font-mono);\n font-size: 11px;\n padding: 2px 10px;\n border-radius: 4px;\n border: none;\n cursor: pointer;\n font-weight: 500;\n transition: opacity 0.15s;\n }\n\n .advisor-btn:hover { opacity: 0.85; }\n .advisor-btn.approve { background: var(--accent); color: var(--bg-root); }\n .advisor-btn.reject { background: var(--danger); color: #fff; }\n\n .advisor-actions { display: none; gap: 6px; margin-left: 8px; }\n .advisor-mode .advisor-actions { display: inline-flex; }\n\n /* ── Empty state ───────────────────────────────── */\n .empty-state {\n color: var(--text-dim);\n font-size: 13px;\n text-align: center;\n padding: 40px 20px;\n }\n\n /* ── Reduced motion ────────────────────────────── */\n @media (prefers-reduced-motion: reduce) {\n .term-line { animation: none; opacity: 1; transform: none; }\n .live-dot { animation: none; }\n .persona-bar-fill { transition: none; }\n }\n</style>\n</head>\n<body>\n\n<!-- Header -->\n<div class=\"header\" id=\"header\">\n <div class=\"header-brand\">Agent<span>E</span> v1.6</div>\n <div class=\"kpi-row\">\n <div class=\"kpi\">Health <span class=\"kpi-value health-good\" id=\"kpi-health\">--</span></div>\n <div class=\"kpi\">Mode <span class=\"kpi-value\" id=\"kpi-mode\">--</span></div>\n <div class=\"kpi\">Tick <span class=\"kpi-value\" id=\"kpi-tick\">0</span></div>\n <div class=\"kpi\">Uptime <span class=\"kpi-value\" id=\"kpi-uptime\">0s</span></div>\n <div class=\"kpi\">Plans <span class=\"kpi-value\" id=\"kpi-plans\">0</span></div>\n <div class=\"live-dot\" id=\"live-dot\" title=\"WebSocket connected\"></div>\n </div>\n</div>\n\n<div class=\"container\" id=\"app\">\n <!-- Advisor banner -->\n <div class=\"advisor-banner\" id=\"advisor-banner\">\n ADVISOR MODE — Recommendations require manual approval\n <span class=\"pending-pill\" id=\"pending-count\">0</span> pending\n </div>\n\n <!-- Charts -->\n <div class=\"charts-grid\">\n <div class=\"chart-box\">\n <div class=\"chart-label\">Economy Health</div>\n <div class=\"chart-value\" id=\"cv-health\">--</div>\n <canvas id=\"chart-health\"></canvas>\n </div>\n <div class=\"chart-box\">\n <div class=\"chart-label\">Gini Coefficient</div>\n <div class=\"chart-value\" id=\"cv-gini\">--</div>\n <canvas id=\"chart-gini\"></canvas>\n </div>\n <div class=\"chart-box\">\n <div class=\"chart-label\">Net Flow</div>\n <div class=\"chart-value\" id=\"cv-netflow\">--</div>\n <canvas id=\"chart-netflow\"></canvas>\n </div>\n <div class=\"chart-box\">\n <div class=\"chart-label\">Avg Satisfaction</div>\n <div class=\"chart-value\" id=\"cv-satisfaction\">--</div>\n <canvas id=\"chart-satisfaction\"></canvas>\n </div>\n </div>\n\n <!-- Decision Feed -->\n <div class=\"panel\">\n <div class=\"panel-title\">Decision Feed</div>\n <div class=\"terminal\" id=\"terminal\"></div>\n </div>\n\n <!-- Active Alerts -->\n <div class=\"panel\">\n <div class=\"panel-title\">Active Alerts</div>\n <div class=\"alerts-container\" id=\"alerts-container\">\n <div class=\"empty-state\" id=\"alerts-empty\">No active violations</div>\n </div>\n </div>\n\n <!-- Violation History -->\n <div class=\"panel\">\n <div class=\"panel-title\">Violation History</div>\n <div style=\"max-height:320px;overflow-y:auto\">\n <table class=\"violations-table\" id=\"violations-table\">\n <thead>\n <tr>\n <th data-sort=\"tick\">Tick</th>\n <th data-sort=\"principle\">Principle</th>\n <th data-sort=\"severity\">Severity</th>\n <th data-sort=\"parameter\">Parameter</th>\n <th data-sort=\"result\">Result</th>\n </tr>\n </thead>\n <tbody id=\"violations-body\"></tbody>\n </table>\n </div>\n </div>\n\n <!-- Split: Personas + Registry -->\n <div class=\"split-row\">\n <div class=\"panel\">\n <div class=\"panel-title\">Persona Distribution</div>\n <div class=\"persona-bars\" id=\"persona-bars\">\n <div class=\"empty-state\">No persona data yet</div>\n </div>\n </div>\n <div class=\"panel\">\n <div class=\"panel-title\">Parameter Registry</div>\n <div class=\"registry-list\" id=\"registry-list\">\n <div class=\"empty-state\">No parameters registered</div>\n </div>\n </div>\n </div>\n</div>\n\n<script>\n(function() {\n 'use strict';\n\n // ── State ────────────────────────────────────────\n let ws = null;\n let reconnectDelay = 1000;\n const MAX_RECONNECT = 30000;\n let isAdvisor = false;\n let pendingDecisions = [];\n const MAX_TERMINAL_LINES = 80;\n const MAX_VIOLATIONS = 100;\n let violationSortKey = 'tick';\n let violationSortAsc = false;\n let violations = [];\n\n // Chart instances\n let chartHealth, chartGini, chartNetflow, chartSatisfaction;\n\n // ── DOM refs ─────────────────────────────────────\n const $kpiHealth = document.getElementById('kpi-health');\n const $kpiMode = document.getElementById('kpi-mode');\n const $kpiTick = document.getElementById('kpi-tick');\n const $kpiUptime = document.getElementById('kpi-uptime');\n const $kpiPlans = document.getElementById('kpi-plans');\n const $liveDot = document.getElementById('live-dot');\n const $terminal = document.getElementById('terminal');\n const $alertsContainer = document.getElementById('alerts-container');\n const $alertsEmpty = document.getElementById('alerts-empty');\n const $violationsBody = document.getElementById('violations-body');\n const $personaBars = document.getElementById('persona-bars');\n const $registryList = document.getElementById('registry-list');\n const $pendingCount = document.getElementById('pending-count');\n const $app = document.getElementById('app');\n\n // ── Helpers ──────────────────────────────────────\n function esc(s) { return String(s).replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;').replace(/\"/g,'&quot;').replace(/'/g,'&#39;').replace(/\\\\\\\\/g,'&#92;'); }\n function pad(n, w) { return String(n).padStart(w || 4, ' '); }\n function fmt(n) { return typeof n === 'number' ? n.toFixed(3) : '—'; }\n function pct(n) { return typeof n === 'number' ? (n * 100).toFixed(0) + '%' : '—'; }\n\n function formatUptime(ms) {\n const s = Math.floor(ms / 1000);\n if (s < 60) return s + 's';\n if (s < 3600) return Math.floor(s / 60) + 'm ' + (s % 60) + 's';\n const h = Math.floor(s / 3600);\n return h + 'h ' + Math.floor((s % 3600) / 60) + 'm';\n }\n\n function healthClass(h) {\n if (h >= 70) return 'health-good';\n if (h >= 40) return 'health-warn';\n return 'health-bad';\n }\n\n function sevClass(s) {\n if (s >= 7) return 'sev-high';\n if (s >= 4) return 'sev-med';\n return 'sev-low';\n }\n\n // ── Chart setup ──────────────────────────────────\n const chartOpts = {\n responsive: true,\n maintainAspectRatio: false,\n animation: { duration: 300 },\n plugins: { legend: { display: false } },\n scales: {\n x: { display: false },\n y: {\n ticks: { color: '#71717a', font: { family: \"'JetBrains Mono'\", size: 10 } },\n grid: { color: 'rgba(63,63,70,0.3)' },\n border: { display: false },\n }\n },\n elements: {\n point: { radius: 0 },\n line: { borderWidth: 1.5, tension: 0.3 },\n }\n };\n\n function makeChart(id, color, minY, maxY) {\n const ctx = document.getElementById(id).getContext('2d');\n const opts = JSON.parse(JSON.stringify(chartOpts));\n if (minY !== undefined) opts.scales.y.min = minY;\n if (maxY !== undefined) opts.scales.y.max = maxY;\n return new Chart(ctx, {\n type: 'line',\n data: {\n labels: [],\n datasets: [{\n data: [],\n borderColor: color,\n backgroundColor: color + '18',\n fill: true,\n }]\n },\n options: opts,\n });\n }\n\n function initCharts() {\n chartHealth = makeChart('chart-health', '#22c55e', 0, 100);\n chartGini = makeChart('chart-gini', '#eab308', 0, 1);\n chartNetflow = makeChart('chart-netflow', '#3b82f6');\n chartSatisfaction = makeChart('chart-satisfaction', '#22c55e', 0, 100);\n }\n\n function updateChart(chart, labels, data) {\n chart.data.labels = labels;\n chart.data.datasets[0].data = data;\n chart.update('none');\n }\n\n // ── Terminal ─────────────────────────────────────\n function addTerminalLine(html) {\n const el = document.createElement('div');\n el.className = 'term-line';\n el.innerHTML = html;\n $terminal.appendChild(el);\n while ($terminal.children.length > MAX_TERMINAL_LINES) {\n $terminal.removeChild($terminal.firstChild);\n }\n $terminal.scrollTop = $terminal.scrollHeight;\n }\n\n function decisionToTerminal(d) {\n const resultIcon = d.result === 'applied'\n ? '<span class=\"t-ok\">\\\\u2705 </span>'\n : d.result === 'rejected'\n ? '<span class=\"t-fail\">\\\\u274c </span>'\n : '<span class=\"t-skip\">\\\\u23f8 </span>';\n\n const principle = d.diagnosis?.principle || {};\n const plan = d.plan || {};\n const severity = d.diagnosis?.violation?.severity ?? '?';\n const confidence = d.diagnosis?.violation?.confidence;\n const confStr = confidence != null ? (confidence * 100).toFixed(0) + '%' : '?';\n\n let advisorBtns = '';\n if (isAdvisor && d.result === 'skipped_override') {\n advisorBtns = '<span class=\"advisor-actions\">'\n + '<button class=\"advisor-btn approve\" onclick=\"window._approve(\\\\'' + esc(d.id) + '\\\\')\">[Approve]</button>'\n + '<button class=\"advisor-btn reject\" onclick=\"window._reject(\\\\'' + esc(d.id) + '\\\\')\">[Reject]</button>'\n + '</span>';\n }\n\n return '<span class=\"t-tick\">[Tick ' + pad(d.tick) + ']</span> '\n + resultIcon\n + '<span class=\"t-principle\">[' + esc(principle.id || '?') + '] ' + esc(principle.name || '') + ':</span> '\n + '<span class=\"t-param\">' + esc(plan.parameter || '—') + ' </span>'\n + '<span class=\"t-old\">' + fmt(plan.currentValue) + '</span>'\n + '<span class=\"t-arrow\"> \\\\u2192 </span>'\n + '<span class=\"t-new\">' + fmt(plan.targetValue) + '</span>'\n + '<span class=\"t-meta\"> severity ' + severity + '/10, confidence ' + confStr + '</span>'\n + advisorBtns;\n }\n\n // ── Alerts ───────────────────────────────────────\n function renderAlerts(alerts) {\n if (!alerts || alerts.length === 0) {\n $alertsContainer.innerHTML = '<div class=\"empty-state\">No active violations</div>';\n return;\n }\n const sorted = [...alerts].sort((a, b) => (b.severity || 0) - (a.severity || 0));\n $alertsContainer.innerHTML = sorted.map(function(a) {\n const sev = a.severity || a.violation?.severity || 0;\n const sc = sevClass(sev);\n const name = a.principleName || a.principle?.name || '?';\n const pid = a.principleId || a.principle?.id || '?';\n const reason = a.reasoning || a.violation?.suggestedAction?.reasoning || '';\n return '<div class=\"alert-card\">'\n + '<span class=\"alert-severity ' + sc + '\">' + sev + '/10</span>'\n + '<div class=\"alert-body\">'\n + '<div class=\"alert-principle\">[' + esc(pid) + '] ' + esc(name) + '</div>'\n + '<div class=\"alert-reason\">' + esc(reason) + '</div>'\n + '</div></div>';\n }).join('');\n }\n\n // ── Violations table ─────────────────────────────\n function addViolation(d) {\n violations.push({\n tick: d.tick,\n principle: (d.diagnosis?.principle?.id || '?') + ' ' + (d.diagnosis?.principle?.name || ''),\n severity: d.diagnosis?.violation?.severity || 0,\n parameter: d.plan?.parameter || '—',\n result: d.result,\n });\n if (violations.length > MAX_VIOLATIONS) violations.shift();\n renderViolations();\n }\n\n function renderViolations() {\n const sorted = [...violations].sort(function(a, b) {\n const va = a[violationSortKey], vb = b[violationSortKey];\n if (va < vb) return violationSortAsc ? -1 : 1;\n if (va > vb) return violationSortAsc ? 1 : -1;\n return 0;\n });\n $violationsBody.innerHTML = sorted.map(function(v) {\n return '<tr>'\n + '<td>' + v.tick + '</td>'\n + '<td style=\"color:var(--text-primary);font-family:var(--font-sans)\">' + esc(v.principle) + '</td>'\n + '<td><span class=\"alert-severity ' + sevClass(v.severity) + '\">' + v.severity + '</span></td>'\n + '<td>' + esc(v.parameter) + '</td>'\n + '<td>' + esc(v.result) + '</td>'\n + '</tr>';\n }).join('');\n }\n\n // Table sorting\n document.querySelectorAll('.violations-table th').forEach(function(th) {\n th.addEventListener('click', function() {\n const key = th.dataset.sort;\n if (violationSortKey === key) violationSortAsc = !violationSortAsc;\n else { violationSortKey = key; violationSortAsc = true; }\n renderViolations();\n });\n });\n\n // ── Personas ─────────────────────────────────────\n function renderPersonas(dist) {\n if (!dist || Object.keys(dist).length === 0) {\n $personaBars.innerHTML = '<div class=\"empty-state\">No persona data yet</div>';\n return;\n }\n const total = Object.values(dist).reduce(function(s, v) { return s + v; }, 0);\n const entries = Object.entries(dist).sort(function(a, b) { return b[1] - a[1]; });\n $personaBars.innerHTML = entries.map(function(e) {\n const pctVal = total > 0 ? (e[1] / total * 100) : 0;\n return '<div class=\"persona-row\">'\n + '<div class=\"persona-label\">' + esc(e[0]) + '</div>'\n + '<div class=\"persona-bar-track\"><div class=\"persona-bar-fill\" style=\"width:' + pctVal + '%\"></div></div>'\n + '<div class=\"persona-pct\">' + pctVal.toFixed(0) + '%</div>'\n + '</div>';\n }).join('');\n }\n\n // ── Registry ─────────────────────────────────────\n function renderRegistry(principles) {\n if (!principles || principles.length === 0) {\n $registryList.innerHTML = '<div class=\"empty-state\">No parameters registered</div>';\n return;\n }\n $registryList.innerHTML = principles.slice(0, 30).map(function(p) {\n return '<div class=\"registry-item\">'\n + '<span class=\"registry-key\">[' + esc(p.id) + ']</span>'\n + '<span class=\"registry-val\">' + esc(p.name) + '</span>'\n + '</div>';\n }).join('');\n }\n\n // ── KPI update ───────────────────────────────────\n function updateKPIs(data) {\n if (data.health != null) {\n $kpiHealth.textContent = data.health + '/100';\n $kpiHealth.className = 'kpi-value ' + healthClass(data.health);\n document.getElementById('cv-health').textContent = data.health + '/100';\n }\n if (data.mode != null) {\n $kpiMode.textContent = data.mode;\n isAdvisor = data.mode === 'advisor';\n $app.classList.toggle('advisor-mode', isAdvisor);\n }\n if (data.tick != null) $kpiTick.textContent = data.tick;\n if (data.uptime != null) $kpiUptime.textContent = formatUptime(data.uptime);\n if (data.activePlans != null) $kpiPlans.textContent = data.activePlans;\n }\n\n // ── Metrics history ──────────────────────────────\n function updateChartsFromHistory(history) {\n if (!history || history.length === 0) return;\n const ticks = history.map(function(h) { return h.tick; });\n updateChart(chartHealth, ticks, history.map(function(h) { return h.health; }));\n updateChart(chartGini, ticks, history.map(function(h) { return h.giniCoefficient; }));\n updateChart(chartNetflow, ticks, history.map(function(h) { return h.netFlow; }));\n updateChart(chartSatisfaction, ticks, history.map(function(h) { return h.avgSatisfaction; }));\n\n const last = history[history.length - 1];\n document.getElementById('cv-gini').textContent = last.giniCoefficient.toFixed(3);\n document.getElementById('cv-netflow').textContent = last.netFlow.toFixed(1);\n document.getElementById('cv-satisfaction').textContent = last.avgSatisfaction.toFixed(0) + '/100';\n }\n\n // ── API calls ────────────────────────────────────\n function fetchJSON(path) {\n return fetch(path).then(function(r) { return r.json(); });\n }\n\n function postJSON(path, body) {\n return fetch(path, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify(body),\n }).then(function(r) { return r.json(); });\n }\n\n function loadInitialData() {\n fetchJSON('/health').then(function(data) {\n updateKPIs(data);\n }).catch(function() {});\n\n fetchJSON('/decisions?limit=50').then(function(data) {\n if (data.decisions) {\n data.decisions.reverse().forEach(function(d) {\n addTerminalLine(decisionToTerminal(d));\n addViolation(d);\n });\n }\n }).catch(function() {});\n\n fetchJSON('/metrics').then(function(data) {\n if (data.history) updateChartsFromHistory(data.history);\n if (data.latest) {\n renderPersonas(data.latest.personaDistribution);\n }\n }).catch(function() {});\n\n fetchJSON('/principles').then(function(data) {\n if (data.principles) renderRegistry(data.principles);\n }).catch(function() {});\n\n fetchJSON('/pending').then(function(data) {\n if (data.pending) {\n pendingDecisions = data.pending;\n $pendingCount.textContent = data.count || 0;\n }\n }).catch(function() {});\n }\n\n // ── Polling fallback ─────────────────────────────\n let pollInterval = null;\n\n function startPolling() {\n if (pollInterval) return;\n pollInterval = setInterval(function() {\n fetchJSON('/health').then(updateKPIs).catch(function() {});\n fetchJSON('/metrics').then(function(data) {\n if (data.history) updateChartsFromHistory(data.history);\n if (data.latest) renderPersonas(data.latest.personaDistribution);\n }).catch(function() {});\n }, 5000);\n }\n\n function stopPolling() {\n if (pollInterval) { clearInterval(pollInterval); pollInterval = null; }\n }\n\n // ── WebSocket ────────────────────────────────────\n function connectWS() {\n const proto = location.protocol === 'https:' ? 'wss:' : 'ws:';\n ws = new WebSocket(proto + '//' + location.host);\n\n ws.onopen = function() {\n reconnectDelay = 1000;\n $liveDot.classList.remove('disconnected');\n $liveDot.title = 'WebSocket connected';\n stopPolling();\n // Request fresh health\n ws.send(JSON.stringify({ type: 'health' }));\n };\n\n ws.onclose = function() {\n $liveDot.classList.add('disconnected');\n $liveDot.title = 'WebSocket disconnected — reconnecting...';\n startPolling();\n setTimeout(connectWS, reconnectDelay);\n reconnectDelay = Math.min(reconnectDelay * 1.5, MAX_RECONNECT);\n };\n\n ws.onerror = function() { ws.close(); };\n\n ws.onmessage = function(ev) {\n var msg;\n try { msg = JSON.parse(ev.data); } catch(e) { return; }\n\n switch (msg.type) {\n case 'tick_result':\n updateKPIs({ health: msg.health, tick: msg.tick });\n if (msg.alerts) renderAlerts(msg.alerts);\n // Refresh charts\n fetchJSON('/metrics').then(function(data) {\n if (data.history) updateChartsFromHistory(data.history);\n if (data.latest) renderPersonas(data.latest.personaDistribution);\n }).catch(function() {});\n break;\n\n case 'health_result':\n updateKPIs(msg);\n break;\n\n case 'advisor_action':\n if (msg.action === 'approved' || msg.action === 'rejected') {\n pendingDecisions = pendingDecisions.filter(function(d) {\n return d.id !== msg.decisionId;\n });\n $pendingCount.textContent = pendingDecisions.length;\n }\n break;\n }\n };\n }\n\n // ── Advisor actions ──────────────────────────────\n window._approve = function(id) {\n postJSON('/approve', { decisionId: id }).then(function(data) {\n if (data.ok) {\n addTerminalLine('<span class=\"t-tick\">[Advisor]</span> <span class=\"t-ok\">\\\\u2705 Approved ' + esc(id) + '</span>');\n }\n }).catch(function() {});\n };\n\n window._reject = function(id) {\n var reason = prompt('Rejection reason (optional):');\n postJSON('/reject', { decisionId: id, reason: reason || undefined }).then(function(data) {\n if (data.ok) {\n addTerminalLine('<span class=\"t-tick\">[Advisor]</span> <span class=\"t-fail\">\\\\u274c Rejected ' + esc(id) + '</span>');\n }\n }).catch(function() {});\n };\n\n // ── Init ─────────────────────────────────────────\n initCharts();\n loadInitialData();\n connectWS();\n\n})();\n</script>\n</body>\n</html>`;\n}\n","// WebSocket handler for AgentE Server\n// Same port via HTTP upgrade. JSON messages with `type` field.\n\nimport type * as http from 'node:http';\nimport { WebSocketServer, WebSocket } from 'ws';\nimport { validateEconomyState, type EconomyState, type EconomicEvent } from '@agent-e/core';\nimport type { AgentEServer } from './AgentEServer.js';\n\ninterface IncomingMessage {\n type: string;\n [key: string]: unknown;\n}\n\nfunction send(ws: WebSocket, data: Record<string, unknown>): void {\n if (ws.readyState === WebSocket.OPEN) {\n ws.send(JSON.stringify(data));\n }\n}\n\nexport interface WebSocketHandle {\n cleanup: () => void;\n broadcast: (data: Record<string, unknown>) => void;\n}\n\nconst MAX_WS_PAYLOAD = 1_048_576; // 1 MB\nconst MAX_WS_CONNECTIONS = 100;\nconst MIN_TICK_INTERVAL_MS = 100; // rate limit: max 10 ticks/sec per connection\n\n/** Strips prototype-polluting keys from parsed JSON objects (recursive). */\nfunction sanitizeJson(obj: unknown): unknown {\n if (obj === null || typeof obj !== 'object') return obj;\n if (Array.isArray(obj)) return obj.map(sanitizeJson);\n const clean: Record<string, unknown> = {};\n for (const [key, val] of Object.entries(obj as Record<string, unknown>)) {\n if (key === '__proto__' || key === 'constructor' || key === 'prototype') continue;\n clean[key] = sanitizeJson(val);\n }\n return clean;\n}\n\nexport function createWebSocketHandler(\n httpServer: http.Server,\n server: AgentEServer,\n): WebSocketHandle {\n const wss = new WebSocketServer({ server: httpServer, maxPayload: MAX_WS_PAYLOAD });\n\n // Heartbeat: ping every 30s, disconnect if no pong within 10s\n const aliveMap = new WeakMap<WebSocket, boolean>();\n\n const heartbeatInterval = setInterval(() => {\n for (const ws of wss.clients) {\n if (ws.readyState === WebSocket.OPEN) {\n if (aliveMap.get(ws) === false) {\n // No pong received since last ping — terminate\n ws.terminate();\n continue;\n }\n aliveMap.set(ws, false);\n ws.ping();\n }\n }\n }, 30_000);\n\n wss.on('connection', (ws, req) => {\n if (wss.clients.size > MAX_WS_CONNECTIONS) {\n ws.close(1013, 'Server at capacity');\n return;\n }\n\n // Auth check: if apiKey is configured, require it via query param or protocol header\n if (server.apiKey) {\n const url = new URL(req.url ?? '/', `http://${req.headers.host ?? 'localhost'}`);\n const token = url.searchParams.get('token') ?? req.headers['authorization']?.replace('Bearer ', '');\n if (token !== server.apiKey) {\n ws.close(1008, 'Unauthorized');\n return;\n }\n }\n\n console.log('[AgentE Server] Client connected');\n aliveMap.set(ws, true);\n\n let lastTickTime = 0;\n\n ws.on('pong', () => {\n aliveMap.set(ws, true);\n });\n\n ws.on('close', () => {\n console.log('[AgentE Server] Client disconnected');\n });\n\n ws.on('message', async (raw) => {\n let msg: IncomingMessage;\n try {\n msg = sanitizeJson(JSON.parse(raw.toString())) as IncomingMessage;\n } catch {\n send(ws, { type: 'error', message: 'Malformed JSON' });\n return;\n }\n\n if (!msg.type || typeof msg.type !== 'string') {\n send(ws, { type: 'error', message: 'Missing \"type\" field' });\n return;\n }\n\n switch (msg.type) {\n case 'tick': {\n const now = Date.now();\n if (now - lastTickTime < MIN_TICK_INTERVAL_MS) {\n send(ws, { type: 'error', message: 'Rate limited — min 100ms between ticks' });\n break;\n }\n lastTickTime = now;\n\n const state = msg['state'];\n const events = msg['events'];\n\n if (server.validateState) {\n const validation = validateEconomyState(state);\n if (!validation.valid) {\n send(ws, { type: 'validation_error', validationErrors: validation.errors });\n return;\n }\n\n // Forward warnings even if valid\n if (validation.warnings.length > 0) {\n send(ws, { type: 'validation_warning', validationWarnings: validation.warnings });\n }\n }\n\n try {\n const result = await server.processTick(\n state as EconomyState,\n Array.isArray(events) ? events as EconomicEvent[] : undefined,\n );\n\n send(ws, {\n type: 'tick_result',\n adjustments: result.adjustments,\n alerts: result.alerts.map(a => ({\n principleId: a.principle.id,\n principleName: a.principle.name,\n severity: a.violation.severity,\n reasoning: a.violation.suggestedAction.reasoning,\n })),\n health: result.health,\n tick: result.tick,\n });\n } catch (_err) {\n send(ws, { type: 'error', message: 'Tick processing failed' });\n }\n break;\n }\n\n case 'event': {\n const event = msg['event'] as EconomicEvent | undefined;\n if (event) {\n server.getAgentE().ingest(event);\n send(ws, { type: 'event_ack' });\n } else {\n send(ws, { type: 'error', message: 'Missing \"event\" field' });\n }\n break;\n }\n\n case 'health': {\n const agentE = server.getAgentE();\n send(ws, {\n type: 'health_result',\n health: agentE.getHealth(),\n tick: agentE.metrics.latest()?.tick ?? 0,\n mode: agentE.getMode(),\n activePlans: agentE.getActivePlans().length,\n uptime: server.getUptime(),\n });\n break;\n }\n\n case 'diagnose': {\n const state = msg['state'];\n\n if (server.validateState) {\n const validation = validateEconomyState(state);\n if (!validation.valid) {\n send(ws, { type: 'validation_error', validationErrors: validation.errors });\n return;\n }\n }\n\n const result = server.diagnoseOnly(state as EconomyState);\n send(ws, {\n type: 'diagnose_result',\n health: result.health,\n diagnoses: result.diagnoses.map(d => ({\n principleId: d.principle.id,\n principleName: d.principle.name,\n severity: d.violation.severity,\n suggestedAction: d.violation.suggestedAction,\n })),\n });\n break;\n }\n\n default:\n send(ws, { type: 'error', message: `Unknown message type: \"${String(msg.type).slice(0, 100)}\"` });\n }\n });\n });\n\n function broadcast(data: Record<string, unknown>): void {\n const payload = JSON.stringify(data);\n for (const ws of wss.clients) {\n if (ws.readyState === WebSocket.OPEN) {\n ws.send(payload);\n }\n }\n }\n\n return {\n cleanup: () => {\n clearInterval(heartbeatInterval);\n wss.close();\n },\n broadcast,\n };\n}\n"],"mappings":";AAEA,YAAY,UAAU;AACtB;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OAQK;;;ACZP,SAAS,4BAA4B;;;ACD9B,SAAS,mBAA2B;AACzC,SAAO;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAo6BT;;;ADh6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","names":["validateEconomyState","sanitizeJson"]}