@agent-e/server 1.6.10 → 1.6.12
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/AgentEServer-PXMTFBHR.mjs +7 -0
- package/dist/{chunk-MH5XTBNY.mjs → chunk-O2UFQVI2.mjs} +72 -50
- package/dist/chunk-O2UFQVI2.mjs.map +1 -0
- package/dist/cli.js +72 -50
- package/dist/cli.js.map +1 -1
- package/dist/cli.mjs +2 -2
- package/dist/cli.mjs.map +1 -1
- package/dist/index.js +72 -50
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +2 -2
- package/package.json +2 -2
- package/dist/AgentEServer-WOYIMZWG.mjs +0 -7
- package/dist/chunk-MH5XTBNY.mjs.map +0 -1
- /package/dist/{AgentEServer-WOYIMZWG.mjs.map → AgentEServer-PXMTFBHR.mjs.map} +0 -0
|
@@ -9,6 +9,7 @@ import {
|
|
|
9
9
|
} from "@agent-e/core";
|
|
10
10
|
|
|
11
11
|
// src/routes.ts
|
|
12
|
+
import { timingSafeEqual } from "crypto";
|
|
12
13
|
import { validateEconomyState } from "@agent-e/core";
|
|
13
14
|
|
|
14
15
|
// src/dashboard.ts
|
|
@@ -955,8 +956,17 @@ function setSecurityHeaders(res) {
|
|
|
955
956
|
}
|
|
956
957
|
function setCorsHeaders(res, allowedOrigin, requestOrigin) {
|
|
957
958
|
setSecurityHeaders(res);
|
|
958
|
-
|
|
959
|
-
|
|
959
|
+
let origin;
|
|
960
|
+
if (allowedOrigin === "*") {
|
|
961
|
+
origin = "*";
|
|
962
|
+
} else if (requestOrigin === void 0) {
|
|
963
|
+
origin = allowedOrigin;
|
|
964
|
+
} else {
|
|
965
|
+
origin = requestOrigin.toLowerCase() === allowedOrigin.toLowerCase() ? requestOrigin : "";
|
|
966
|
+
}
|
|
967
|
+
if (origin) {
|
|
968
|
+
res.setHeader("Access-Control-Allow-Origin", origin);
|
|
969
|
+
}
|
|
960
970
|
res.setHeader("Access-Control-Allow-Methods", "GET, POST, OPTIONS");
|
|
961
971
|
res.setHeader("Access-Control-Allow-Headers", "Content-Type, Authorization");
|
|
962
972
|
}
|
|
@@ -973,7 +983,10 @@ function sanitizeJson(obj) {
|
|
|
973
983
|
function checkAuth(req, apiKey) {
|
|
974
984
|
if (!apiKey) return true;
|
|
975
985
|
const header = req.headers["authorization"];
|
|
976
|
-
|
|
986
|
+
if (typeof header !== "string") return false;
|
|
987
|
+
const expected = `Bearer ${apiKey}`;
|
|
988
|
+
if (header.length !== expected.length) return false;
|
|
989
|
+
return timingSafeEqual(Buffer.from(header), Buffer.from(expected));
|
|
977
990
|
}
|
|
978
991
|
function json(res, status, data, origin, reqOrigin) {
|
|
979
992
|
setCorsHeaders(res, origin, reqOrigin);
|
|
@@ -1005,8 +1018,10 @@ function createRouteHandler(server) {
|
|
|
1005
1018
|
const url = new URL(req.url ?? "/", `http://${req.headers.host ?? "localhost"}`);
|
|
1006
1019
|
const path = url.pathname;
|
|
1007
1020
|
const method = req.method?.toUpperCase() ?? "GET";
|
|
1021
|
+
const reqOrigin = req.headers["origin"];
|
|
1022
|
+
const respond = (status, data) => json(res, status, data, cors, reqOrigin);
|
|
1008
1023
|
if (method === "OPTIONS") {
|
|
1009
|
-
setCorsHeaders(res, cors);
|
|
1024
|
+
setCorsHeaders(res, cors, reqOrigin);
|
|
1010
1025
|
res.writeHead(204);
|
|
1011
1026
|
res.end();
|
|
1012
1027
|
return;
|
|
@@ -1014,7 +1029,7 @@ function createRouteHandler(server) {
|
|
|
1014
1029
|
try {
|
|
1015
1030
|
if (path === "/tick" && method === "POST") {
|
|
1016
1031
|
if (!checkAuth(req, apiKey)) {
|
|
1017
|
-
|
|
1032
|
+
respond(401, { error: "Unauthorized" });
|
|
1018
1033
|
return;
|
|
1019
1034
|
}
|
|
1020
1035
|
const body = await readBody(req);
|
|
@@ -1022,11 +1037,11 @@ function createRouteHandler(server) {
|
|
|
1022
1037
|
try {
|
|
1023
1038
|
parsed = sanitizeJson(JSON.parse(body));
|
|
1024
1039
|
} catch {
|
|
1025
|
-
|
|
1040
|
+
respond(400, { error: "Invalid JSON" });
|
|
1026
1041
|
return;
|
|
1027
1042
|
}
|
|
1028
1043
|
if (!parsed || typeof parsed !== "object") {
|
|
1029
|
-
|
|
1044
|
+
respond(400, { error: "Body must be a JSON object" });
|
|
1030
1045
|
return;
|
|
1031
1046
|
}
|
|
1032
1047
|
const payload = parsed;
|
|
@@ -1034,10 +1049,10 @@ function createRouteHandler(server) {
|
|
|
1034
1049
|
const events = payload["events"];
|
|
1035
1050
|
const validation = server.validateState ? validateEconomyState(state) : null;
|
|
1036
1051
|
if (validation && !validation.valid) {
|
|
1037
|
-
|
|
1052
|
+
respond(400, {
|
|
1038
1053
|
error: "invalid_state",
|
|
1039
1054
|
validationErrors: validation.errors
|
|
1040
|
-
}
|
|
1055
|
+
});
|
|
1041
1056
|
return;
|
|
1042
1057
|
}
|
|
1043
1058
|
const result = await server.processTick(
|
|
@@ -1045,7 +1060,7 @@ function createRouteHandler(server) {
|
|
|
1045
1060
|
Array.isArray(events) ? events : void 0
|
|
1046
1061
|
);
|
|
1047
1062
|
const warnings = validation?.warnings ?? [];
|
|
1048
|
-
|
|
1063
|
+
respond(200, {
|
|
1049
1064
|
adjustments: result.adjustments,
|
|
1050
1065
|
alerts: result.alerts.map((a) => ({
|
|
1051
1066
|
principleId: a.principle.id,
|
|
@@ -1057,18 +1072,18 @@ function createRouteHandler(server) {
|
|
|
1057
1072
|
health: result.health,
|
|
1058
1073
|
tick: result.tick,
|
|
1059
1074
|
...warnings.length > 0 ? { validationWarnings: warnings } : {}
|
|
1060
|
-
}
|
|
1075
|
+
});
|
|
1061
1076
|
return;
|
|
1062
1077
|
}
|
|
1063
1078
|
if (path === "/health" && method === "GET") {
|
|
1064
1079
|
const agentE = server.getAgentE();
|
|
1065
|
-
|
|
1080
|
+
respond(200, {
|
|
1066
1081
|
health: agentE.getHealth(),
|
|
1067
1082
|
tick: agentE.metrics.latest()?.tick ?? 0,
|
|
1068
1083
|
mode: agentE.getMode(),
|
|
1069
1084
|
activePlans: agentE.getActivePlans().length,
|
|
1070
1085
|
uptime: server.getUptime()
|
|
1071
|
-
}
|
|
1086
|
+
});
|
|
1072
1087
|
return;
|
|
1073
1088
|
}
|
|
1074
1089
|
if (path === "/decisions" && method === "GET") {
|
|
@@ -1080,19 +1095,19 @@ function createRouteHandler(server) {
|
|
|
1080
1095
|
if (sinceParam) {
|
|
1081
1096
|
const since = parseInt(sinceParam, 10);
|
|
1082
1097
|
if (Number.isNaN(since)) {
|
|
1083
|
-
|
|
1098
|
+
respond(400, { error: 'Invalid "since" parameter \u2014 must be a number' });
|
|
1084
1099
|
return;
|
|
1085
1100
|
}
|
|
1086
1101
|
decisions = agentE.getDecisions({ since });
|
|
1087
1102
|
} else {
|
|
1088
1103
|
decisions = agentE.log.latest(limit);
|
|
1089
1104
|
}
|
|
1090
|
-
|
|
1105
|
+
respond(200, { decisions });
|
|
1091
1106
|
return;
|
|
1092
1107
|
}
|
|
1093
1108
|
if (path === "/config" && method === "POST") {
|
|
1094
1109
|
if (!checkAuth(req, apiKey)) {
|
|
1095
|
-
|
|
1110
|
+
respond(401, { error: "Unauthorized" });
|
|
1096
1111
|
return;
|
|
1097
1112
|
}
|
|
1098
1113
|
const body = await readBody(req);
|
|
@@ -1100,7 +1115,7 @@ function createRouteHandler(server) {
|
|
|
1100
1115
|
try {
|
|
1101
1116
|
parsed = sanitizeJson(JSON.parse(body));
|
|
1102
1117
|
} catch {
|
|
1103
|
-
|
|
1118
|
+
respond(400, { error: "Invalid JSON" });
|
|
1104
1119
|
return;
|
|
1105
1120
|
}
|
|
1106
1121
|
const config = parsed;
|
|
@@ -1120,11 +1135,11 @@ function createRouteHandler(server) {
|
|
|
1120
1135
|
if (c && typeof c === "object" && typeof c["param"] === "string" && typeof c["min"] === "number" && typeof c["max"] === "number") {
|
|
1121
1136
|
const constraint = c;
|
|
1122
1137
|
if (!Number.isFinite(constraint.min) || !Number.isFinite(constraint.max)) {
|
|
1123
|
-
|
|
1138
|
+
respond(400, { error: "Constraint bounds must be finite numbers" });
|
|
1124
1139
|
return;
|
|
1125
1140
|
}
|
|
1126
1141
|
if (constraint.min > constraint.max) {
|
|
1127
|
-
|
|
1142
|
+
respond(400, { error: "Constraint min cannot exceed max" });
|
|
1128
1143
|
return;
|
|
1129
1144
|
}
|
|
1130
1145
|
validated.push(constraint);
|
|
@@ -1137,12 +1152,12 @@ function createRouteHandler(server) {
|
|
|
1137
1152
|
if (config["mode"] === "autonomous" || config["mode"] === "advisor") {
|
|
1138
1153
|
server.setMode(config["mode"]);
|
|
1139
1154
|
}
|
|
1140
|
-
|
|
1155
|
+
respond(200, { ok: true });
|
|
1141
1156
|
return;
|
|
1142
1157
|
}
|
|
1143
1158
|
if (path === "/principles" && method === "GET") {
|
|
1144
1159
|
const principles = server.getAgentE().getPrinciples();
|
|
1145
|
-
|
|
1160
|
+
respond(200, {
|
|
1146
1161
|
count: principles.length,
|
|
1147
1162
|
principles: principles.map((p) => ({
|
|
1148
1163
|
id: p.id,
|
|
@@ -1150,12 +1165,12 @@ function createRouteHandler(server) {
|
|
|
1150
1165
|
category: p.category,
|
|
1151
1166
|
description: p.description
|
|
1152
1167
|
}))
|
|
1153
|
-
}
|
|
1168
|
+
});
|
|
1154
1169
|
return;
|
|
1155
1170
|
}
|
|
1156
1171
|
if (path === "/diagnose" && method === "POST") {
|
|
1157
1172
|
if (!checkAuth(req, apiKey)) {
|
|
1158
|
-
|
|
1173
|
+
respond(401, { error: "Unauthorized" });
|
|
1159
1174
|
return;
|
|
1160
1175
|
}
|
|
1161
1176
|
const body = await readBody(req);
|
|
@@ -1163,7 +1178,7 @@ function createRouteHandler(server) {
|
|
|
1163
1178
|
try {
|
|
1164
1179
|
parsed = sanitizeJson(JSON.parse(body));
|
|
1165
1180
|
} catch {
|
|
1166
|
-
|
|
1181
|
+
respond(400, { error: "Invalid JSON" });
|
|
1167
1182
|
return;
|
|
1168
1183
|
}
|
|
1169
1184
|
const payload = parsed;
|
|
@@ -1171,12 +1186,12 @@ function createRouteHandler(server) {
|
|
|
1171
1186
|
if (server.validateState) {
|
|
1172
1187
|
const validation = validateEconomyState(state);
|
|
1173
1188
|
if (!validation.valid) {
|
|
1174
|
-
|
|
1189
|
+
respond(400, { error: "invalid_state", validationErrors: validation.errors });
|
|
1175
1190
|
return;
|
|
1176
1191
|
}
|
|
1177
1192
|
}
|
|
1178
1193
|
const result = server.diagnoseOnly(state);
|
|
1179
|
-
|
|
1194
|
+
respond(200, {
|
|
1180
1195
|
health: result.health,
|
|
1181
1196
|
diagnoses: result.diagnoses.map((d) => ({
|
|
1182
1197
|
principleId: d.principle.id,
|
|
@@ -1185,11 +1200,11 @@ function createRouteHandler(server) {
|
|
|
1185
1200
|
evidence: d.violation.evidence,
|
|
1186
1201
|
suggestedAction: d.violation.suggestedAction
|
|
1187
1202
|
}))
|
|
1188
|
-
}
|
|
1203
|
+
});
|
|
1189
1204
|
return;
|
|
1190
1205
|
}
|
|
1191
1206
|
if (path === "/" && method === "GET" && server.serveDashboard) {
|
|
1192
|
-
setCorsHeaders(res, cors);
|
|
1207
|
+
setCorsHeaders(res, cors, reqOrigin);
|
|
1193
1208
|
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
1209
|
res.setHeader("Cache-Control", "public, max-age=60");
|
|
1195
1210
|
res.writeHead(200, { "Content-Type": "text/html; charset=utf-8" });
|
|
@@ -1200,7 +1215,7 @@ function createRouteHandler(server) {
|
|
|
1200
1215
|
const agentE = server.getAgentE();
|
|
1201
1216
|
const latest = agentE.store.latest();
|
|
1202
1217
|
const history = agentE.store.recentHistory(100);
|
|
1203
|
-
|
|
1218
|
+
respond(200, { latest, history });
|
|
1204
1219
|
return;
|
|
1205
1220
|
}
|
|
1206
1221
|
if (path === "/metrics/personas" && method === "GET") {
|
|
@@ -1208,12 +1223,12 @@ function createRouteHandler(server) {
|
|
|
1208
1223
|
const latest = agentE.store.latest();
|
|
1209
1224
|
const dist = latest.personaDistribution || {};
|
|
1210
1225
|
const total = Object.values(dist).reduce((s, v) => s + v, 0);
|
|
1211
|
-
|
|
1226
|
+
respond(200, { distribution: dist, total });
|
|
1212
1227
|
return;
|
|
1213
1228
|
}
|
|
1214
1229
|
if (path === "/approve" && method === "POST") {
|
|
1215
1230
|
if (!checkAuth(req, apiKey)) {
|
|
1216
|
-
|
|
1231
|
+
respond(401, { error: "Unauthorized" });
|
|
1217
1232
|
return;
|
|
1218
1233
|
}
|
|
1219
1234
|
const body = await readBody(req);
|
|
@@ -1221,42 +1236,42 @@ function createRouteHandler(server) {
|
|
|
1221
1236
|
try {
|
|
1222
1237
|
parsed = sanitizeJson(JSON.parse(body));
|
|
1223
1238
|
} catch {
|
|
1224
|
-
|
|
1239
|
+
respond(400, { error: "Invalid JSON" });
|
|
1225
1240
|
return;
|
|
1226
1241
|
}
|
|
1227
1242
|
const payload = parsed;
|
|
1228
1243
|
const decisionId = payload["decisionId"];
|
|
1229
1244
|
if (!decisionId) {
|
|
1230
|
-
|
|
1245
|
+
respond(400, { error: "missing_decision_id" });
|
|
1231
1246
|
return;
|
|
1232
1247
|
}
|
|
1233
1248
|
const agentE = server.getAgentE();
|
|
1234
1249
|
if (agentE.getMode() !== "advisor") {
|
|
1235
|
-
|
|
1250
|
+
respond(400, { error: "not_in_advisor_mode" });
|
|
1236
1251
|
return;
|
|
1237
1252
|
}
|
|
1238
1253
|
const entry = agentE.log.getById(decisionId);
|
|
1239
1254
|
if (!entry) {
|
|
1240
|
-
|
|
1255
|
+
respond(404, { error: "decision_not_found" });
|
|
1241
1256
|
return;
|
|
1242
1257
|
}
|
|
1243
1258
|
if (entry.result !== "skipped_override") {
|
|
1244
|
-
|
|
1259
|
+
respond(409, { error: "decision_not_pending", currentResult: entry.result });
|
|
1245
1260
|
return;
|
|
1246
1261
|
}
|
|
1247
1262
|
await agentE.apply(entry.plan);
|
|
1248
1263
|
agentE.log.updateResult(decisionId, "applied");
|
|
1249
1264
|
server.broadcast({ type: "advisor_action", action: "approved", decisionId });
|
|
1250
|
-
|
|
1265
|
+
respond(200, {
|
|
1251
1266
|
ok: true,
|
|
1252
1267
|
parameter: entry.plan.parameter,
|
|
1253
1268
|
value: entry.plan.targetValue
|
|
1254
|
-
}
|
|
1269
|
+
});
|
|
1255
1270
|
return;
|
|
1256
1271
|
}
|
|
1257
1272
|
if (path === "/reject" && method === "POST") {
|
|
1258
1273
|
if (!checkAuth(req, apiKey)) {
|
|
1259
|
-
|
|
1274
|
+
respond(401, { error: "Unauthorized" });
|
|
1260
1275
|
return;
|
|
1261
1276
|
}
|
|
1262
1277
|
const body = await readBody(req);
|
|
@@ -1264,49 +1279,49 @@ function createRouteHandler(server) {
|
|
|
1264
1279
|
try {
|
|
1265
1280
|
parsed = sanitizeJson(JSON.parse(body));
|
|
1266
1281
|
} catch {
|
|
1267
|
-
|
|
1282
|
+
respond(400, { error: "Invalid JSON" });
|
|
1268
1283
|
return;
|
|
1269
1284
|
}
|
|
1270
1285
|
const payload = parsed;
|
|
1271
1286
|
const decisionId = payload["decisionId"];
|
|
1272
1287
|
const reason = payload["reason"] || void 0;
|
|
1273
1288
|
if (!decisionId) {
|
|
1274
|
-
|
|
1289
|
+
respond(400, { error: "missing_decision_id" });
|
|
1275
1290
|
return;
|
|
1276
1291
|
}
|
|
1277
1292
|
const agentE = server.getAgentE();
|
|
1278
1293
|
if (agentE.getMode() !== "advisor") {
|
|
1279
|
-
|
|
1294
|
+
respond(400, { error: "not_in_advisor_mode" });
|
|
1280
1295
|
return;
|
|
1281
1296
|
}
|
|
1282
1297
|
const entry = agentE.log.getById(decisionId);
|
|
1283
1298
|
if (!entry) {
|
|
1284
|
-
|
|
1299
|
+
respond(404, { error: "decision_not_found" });
|
|
1285
1300
|
return;
|
|
1286
1301
|
}
|
|
1287
1302
|
if (entry.result !== "skipped_override") {
|
|
1288
|
-
|
|
1303
|
+
respond(409, { error: "decision_not_pending", currentResult: entry.result });
|
|
1289
1304
|
return;
|
|
1290
1305
|
}
|
|
1291
1306
|
agentE.log.updateResult(decisionId, "rejected", reason);
|
|
1292
1307
|
server.broadcast({ type: "advisor_action", action: "rejected", decisionId, reason });
|
|
1293
|
-
|
|
1308
|
+
respond(200, { ok: true, decisionId });
|
|
1294
1309
|
return;
|
|
1295
1310
|
}
|
|
1296
1311
|
if (path === "/pending" && method === "GET") {
|
|
1297
1312
|
const agentE = server.getAgentE();
|
|
1298
1313
|
const pending = agentE.log.query({ result: "skipped_override" });
|
|
1299
|
-
|
|
1314
|
+
respond(200, {
|
|
1300
1315
|
mode: agentE.getMode(),
|
|
1301
1316
|
pending,
|
|
1302
1317
|
count: pending.length
|
|
1303
|
-
}
|
|
1318
|
+
});
|
|
1304
1319
|
return;
|
|
1305
1320
|
}
|
|
1306
|
-
|
|
1321
|
+
respond(404, { error: "Not found" });
|
|
1307
1322
|
} catch (err) {
|
|
1308
1323
|
console.error("[AgentE Server] Unhandled route error:", err);
|
|
1309
|
-
|
|
1324
|
+
respond(500, { error: "Internal server error" });
|
|
1310
1325
|
}
|
|
1311
1326
|
};
|
|
1312
1327
|
}
|
|
@@ -1352,6 +1367,13 @@ function createWebSocketHandler(httpServer, server) {
|
|
|
1352
1367
|
ws.close(1013, "Server at capacity");
|
|
1353
1368
|
return;
|
|
1354
1369
|
}
|
|
1370
|
+
const wsOrigin = req.headers["origin"];
|
|
1371
|
+
if (wsOrigin && server.corsOrigin !== "*") {
|
|
1372
|
+
if (wsOrigin.toLowerCase() !== server.corsOrigin.toLowerCase()) {
|
|
1373
|
+
ws.close(1008, "Origin not allowed");
|
|
1374
|
+
return;
|
|
1375
|
+
}
|
|
1376
|
+
}
|
|
1355
1377
|
if (server.apiKey) {
|
|
1356
1378
|
const url = new URL(req.url ?? "/", `http://${req.headers.host ?? "localhost"}`);
|
|
1357
1379
|
const token = url.searchParams.get("token") ?? req.headers["authorization"]?.replace("Bearer ", "");
|
|
@@ -1664,4 +1686,4 @@ var AgentEServer = class {
|
|
|
1664
1686
|
export {
|
|
1665
1687
|
AgentEServer
|
|
1666
1688
|
};
|
|
1667
|
-
//# sourceMappingURL=chunk-
|
|
1689
|
+
//# sourceMappingURL=chunk-O2UFQVI2.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 { timingSafeEqual } from 'node:crypto';\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 (case-insensitive) — otherwise don't include a matching CORS header\n origin = requestOrigin.toLowerCase() === allowedOrigin.toLowerCase() ? requestOrigin : '';\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 if (typeof header !== 'string') return false;\n const expected = `Bearer ${apiKey}`;\n if (header.length !== expected.length) return false;\n return timingSafeEqual(Buffer.from(header), Buffer.from(expected));\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,'&').replace(/</g,'<').replace(/>/g,'>').replace(/\"/g,'"').replace(/'/g,''').replace(/\\\\\\\\/g,'\'); }\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 // Origin check: validate against CORS policy (skip for non-browser / missing origin)\n const wsOrigin = req.headers['origin'];\n if (wsOrigin && server.corsOrigin !== '*') {\n if (wsOrigin.toLowerCase() !== server.corsOrigin.toLowerCase()) {\n ws.close(1008, 'Origin not allowed');\n return;\n }\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,uBAAuB;AAChC,SAAS,4BAA4B;;;ACF9B,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;;;AD/5BA,SAAS,mBAAmB,KAAgC;AAC1D,MAAI,UAAU,0BAA0B,SAAS;AACjD,MAAI,UAAU,mBAAmB,MAAM;AACvC,MAAI,UAAU,mBAAmB,iCAAiC;AACpE;AAEA,SAAS,eAAe,KAA0B,eAAuB,eAA8B;AACrG,qBAAmB,GAAG;AAItB,MAAI;AACJ,MAAI,kBAAkB,KAAK;AACzB,aAAS;AAAA,EACX,WAAW,kBAAkB,QAAW;AACtC,aAAS;AAAA,EACX,OAAO;AAEL,aAAS,cAAc,YAAY,MAAM,cAAc,YAAY,IAAI,gBAAgB;AAAA,EACzF;AACA,MAAI,QAAQ;AACV,QAAI,UAAU,+BAA+B,MAAM;AAAA,EACrD;AACA,MAAI,UAAU,gCAAgC,oBAAoB;AAClE,MAAI,UAAU,gCAAgC,6BAA6B;AAC7E;AAGA,SAAS,aAAa,KAAuB;AAC3C,MAAI,QAAQ,QAAQ,OAAO,QAAQ,SAAU,QAAO;AACpD,MAAI,MAAM,QAAQ,GAAG,EAAG,QAAO,IAAI,IAAI,YAAY;AACnD,QAAM,QAAiC,CAAC;AACxC,aAAW,CAAC,KAAK,GAAG,KAAK,OAAO,QAAQ,GAA8B,GAAG;AACvE,QAAI,QAAQ,eAAe,QAAQ,iBAAiB,QAAQ,YAAa;AACzE,UAAM,GAAG,IAAI,aAAa,GAAG;AAAA,EAC/B;AACA,SAAO;AACT;AAEA,SAAS,UAAU,KAA2B,QAAqC;AACjF,MAAI,CAAC,OAAQ,QAAO;AACpB,QAAM,SAAS,IAAI,QAAQ,eAAe;AAC1C,MAAI,OAAO,WAAW,SAAU,QAAO;AACvC,QAAM,WAAW,UAAU,MAAM;AACjC,MAAI,OAAO,WAAW,SAAS,OAAQ,QAAO;AAC9C,SAAO,gBAAgB,OAAO,KAAK,MAAM,GAAG,OAAO,KAAK,QAAQ,CAAC;AACnE;AAEA,SAAS,KAAK,KAA0B,QAAgB,MAAe,QAAgB,WAA0B;AAC/G,iBAAe,KAAK,QAAQ,SAAS;AACrC,MAAI,UAAU,QAAQ,EAAE,gBAAgB,mBAAmB,CAAC;AAC5D,MAAI,IAAI,KAAK,UAAU,IAAI,CAAC;AAC9B;AAEA,IAAM,iBAAiB;AAEvB,SAAS,SAAS,KAA4C;AAC5D,SAAO,IAAI,QAAQ,CAAC,SAAS,WAAW;AACtC,UAAM,SAAmB,CAAC;AAC1B,QAAI,aAAa;AACjB,QAAI,GAAG,QAAQ,CAAC,UAAkB;AAChC,oBAAc,MAAM;AACpB,UAAI,aAAa,gBAAgB;AAC/B,YAAI,QAAQ;AACZ,eAAO,IAAI,MAAM,wBAAwB,CAAC;AAC1C;AAAA,MACF;AACA,aAAO,KAAK,KAAK;AAAA,IACnB,CAAC;AACD,QAAI,GAAG,OAAO,MAAM,QAAQ,OAAO,OAAO,MAAM,EAAE,SAAS,OAAO,CAAC,CAAC;AACpE,QAAI,GAAG,SAAS,MAAM;AAAA,EACxB,CAAC;AACH;AAEO,SAAS,mBACd,QAC+D;AAC/D,QAAM,OAAO,OAAO;AACpB,QAAM,SAAS,OAAO;AAEtB,SAAO,OAAO,KAAK,QAAQ;AACzB,UAAM,MAAM,IAAI,IAAI,IAAI,OAAO,KAAK,UAAU,IAAI,QAAQ,QAAQ,WAAW,EAAE;AAC/E,UAAM,OAAO,IAAI;AACjB,UAAM,SAAS,IAAI,QAAQ,YAAY,KAAK;AAC5C,UAAM,YAAY,IAAI,QAAQ,QAAQ;AAGtC,UAAM,UAAU,CAAC,QAAgB,SAAkB,KAAK,KAAK,QAAQ,MAAM,MAAM,SAAS;AAG1F,QAAI,WAAW,WAAW;AACxB,qBAAe,KAAK,MAAM,SAAS;AACnC,UAAI,UAAU,GAAG;AACjB,UAAI,IAAI;AACR;AAAA,IACF;AAEA,QAAI;AAEF,UAAI,SAAS,WAAW,WAAW,QAAQ;AACzC,YAAI,CAAC,UAAU,KAAK,MAAM,GAAG;AAC3B,kBAAQ,KAAK,EAAE,OAAO,eAAe,CAAC;AACtC;AAAA,QACF;AACA,cAAM,OAAO,MAAM,SAAS,GAAG;AAC/B,YAAI;AACJ,YAAI;AACF,mBAAS,aAAa,KAAK,MAAM,IAAI,CAAC;AAAA,QACxC,QAAQ;AACN,kBAAQ,KAAK,EAAE,OAAO,eAAe,CAAC;AACtC;AAAA,QACF;AAEA,YAAI,CAAC,UAAU,OAAO,WAAW,UAAU;AACzC,kBAAQ,KAAK,EAAE,OAAO,6BAA6B,CAAC;AACpD;AAAA,QACF;AAEA,cAAM,UAAU;AAChB,cAAM,QAAQ,QAAQ,OAAO,KAAK;AAClC,cAAM,SAAS,QAAQ,QAAQ;AAG/B,cAAM,aAAa,OAAO,gBAAgB,qBAAqB,KAAK,IAAI;AACxE,YAAI,cAAc,CAAC,WAAW,OAAO;AACnC,kBAAQ,KAAK;AAAA,YACX,OAAO;AAAA,YACP,kBAAkB,WAAW;AAAA,UAC/B,CAAC;AACD;AAAA,QACF;AAEA,cAAM,SAAS,MAAM,OAAO;AAAA,UAC1B;AAAA,UACA,MAAM,QAAQ,MAAM,IAAI,SAAoD;AAAA,QAC9E;AAEA,cAAM,WAAW,YAAY,YAAY,CAAC;AAE1C,gBAAQ,KAAK;AAAA,UACX,aAAa,OAAO;AAAA,UACpB,QAAQ,OAAO,OAAO,IAAI,QAAM;AAAA,YAC9B,aAAa,EAAE,UAAU;AAAA,YACzB,eAAe,EAAE,UAAU;AAAA,YAC3B,UAAU,EAAE,UAAU;AAAA,YACtB,UAAU,EAAE,UAAU;AAAA,YACtB,WAAW,EAAE,UAAU,gBAAgB;AAAA,UACzC,EAAE;AAAA,UACF,QAAQ,OAAO;AAAA,UACf,MAAM,OAAO;AAAA,UACb,GAAI,SAAS,SAAS,IAAI,EAAE,oBAAoB,SAAS,IAAI,CAAC;AAAA,QAChE,CAAC;AACD;AAAA,MACF;AAGA,UAAI,SAAS,aAAa,WAAW,OAAO;AAC1C,cAAM,SAAS,OAAO,UAAU;AAChC,gBAAQ,KAAK;AAAA,UACX,QAAQ,OAAO,UAAU;AAAA,UACzB,MAAM,OAAO,QAAQ,OAAO,GAAG,QAAQ;AAAA,UACvC,MAAM,OAAO,QAAQ;AAAA,UACrB,aAAa,OAAO,eAAe,EAAE;AAAA,UACrC,QAAQ,OAAO,UAAU;AAAA,QAC3B,CAAC;AACD;AAAA,MACF;AAGA,UAAI,SAAS,gBAAgB,WAAW,OAAO;AAC7C,cAAM,WAAW,SAAS,IAAI,aAAa,IAAI,OAAO,KAAK,OAAO,EAAE;AACpE,cAAM,QAAQ,KAAK,IAAI,KAAK,IAAI,OAAO,MAAM,QAAQ,IAAI,MAAM,UAAU,CAAC,GAAG,GAAI;AACjF,cAAM,aAAa,IAAI,aAAa,IAAI,OAAO;AAC/C,cAAM,SAAS,OAAO,UAAU;AAEhC,YAAI;AACJ,YAAI,YAAY;AACd,gBAAM,QAAQ,SAAS,YAAY,EAAE;AACrC,cAAI,OAAO,MAAM,KAAK,GAAG;AACvB,oBAAQ,KAAK,EAAE,OAAO,oDAA+C,CAAC;AACtE;AAAA,UACF;AACA,sBAAY,OAAO,aAAa,EAAE,MAAM,CAAC;AAAA,QAC3C,OAAO;AACL,sBAAY,OAAO,IAAI,OAAO,KAAK;AAAA,QACrC;AAEA,gBAAQ,KAAK,EAAE,UAAU,CAAC;AAC1B;AAAA,MACF;AAGA,UAAI,SAAS,aAAa,WAAW,QAAQ;AAC3C,YAAI,CAAC,UAAU,KAAK,MAAM,GAAG;AAC3B,kBAAQ,KAAK,EAAE,OAAO,eAAe,CAAC;AACtC;AAAA,QACF;AACA,cAAM,OAAO,MAAM,SAAS,GAAG;AAC/B,YAAI;AACJ,YAAI;AACF,mBAAS,aAAa,KAAK,MAAM,IAAI,CAAC;AAAA,QACxC,QAAQ;AACN,kBAAQ,KAAK,EAAE,OAAO,eAAe,CAAC;AACtC;AAAA,QACF;AAEA,cAAM,SAAS;AAGf,YAAI,MAAM,QAAQ,OAAO,MAAM,CAAC,GAAG;AACjC,qBAAW,SAAS,OAAO,MAAM,GAAG;AAClC,gBAAI,OAAO,UAAU,SAAU,QAAO,KAAK,KAAK;AAAA,UAClD;AAAA,QACF;AAGA,YAAI,MAAM,QAAQ,OAAO,QAAQ,CAAC,GAAG;AACnC,qBAAW,SAAS,OAAO,QAAQ,GAAG;AACpC,gBAAI,OAAO,UAAU,SAAU,QAAO,OAAO,KAAK;AAAA,UACpD;AAAA,QACF;AAGA,YAAI,MAAM,QAAQ,OAAO,WAAW,CAAC,GAAG;AACtC,gBAAM,YAA2D,CAAC;AAClE,qBAAW,KAAK,OAAO,WAAW,GAAgB;AAChD,gBACE,KAAK,OAAO,MAAM,YAClB,OAAQ,EAA8B,OAAO,MAAM,YACnD,OAAQ,EAA8B,KAAK,MAAM,YACjD,OAAQ,EAA8B,KAAK,MAAM,UACjD;AACA,oBAAM,aAAa;AACnB,kBAAI,CAAC,OAAO,SAAS,WAAW,GAAG,KAAK,CAAC,OAAO,SAAS,WAAW,GAAG,GAAG;AACxE,wBAAQ,KAAK,EAAE,OAAO,2CAA2C,CAAC;AAClE;AAAA,cACF;AACA,kBAAI,WAAW,MAAM,WAAW,KAAK;AACnC,wBAAQ,KAAK,EAAE,OAAO,mCAAmC,CAAC;AAC1D;AAAA,cACF;AACA,wBAAU,KAAK,UAAU;AAAA,YAC3B;AAAA,UACF;AACA,qBAAW,cAAc,WAAW;AAClC,mBAAO,UAAU,WAAW,OAAO,EAAE,KAAK,WAAW,KAAK,KAAK,WAAW,IAAI,CAAC;AAAA,UACjF;AAAA,QACF;AAGA,YAAI,OAAO,MAAM,MAAM,gBAAgB,OAAO,MAAM,MAAM,WAAW;AACnE,iBAAO,QAAQ,OAAO,MAAM,CAAC;AAAA,QAC/B;AAEA,gBAAQ,KAAK,EAAE,IAAI,KAAK,CAAC;AACzB;AAAA,MACF;AAGA,UAAI,SAAS,iBAAiB,WAAW,OAAO;AAC9C,cAAM,aAAa,OAAO,UAAU,EAAE,cAAc;AACpD,gBAAQ,KAAK;AAAA,UACX,OAAO,WAAW;AAAA,UAClB,YAAY,WAAW,IAAI,QAAM;AAAA,YAC/B,IAAI,EAAE;AAAA,YACN,MAAM,EAAE;AAAA,YACR,UAAU,EAAE;AAAA,YACZ,aAAa,EAAE;AAAA,UACjB,EAAE;AAAA,QACJ,CAAC;AACD;AAAA,MACF;AAGA,UAAI,SAAS,eAAe,WAAW,QAAQ;AAC7C,YAAI,CAAC,UAAU,KAAK,MAAM,GAAG;AAC3B,kBAAQ,KAAK,EAAE,OAAO,eAAe,CAAC;AACtC;AAAA,QACF;AACA,cAAM,OAAO,MAAM,SAAS,GAAG;AAC/B,YAAI;AACJ,YAAI;AACF,mBAAS,aAAa,KAAK,MAAM,IAAI,CAAC;AAAA,QACxC,QAAQ;AACN,kBAAQ,KAAK,EAAE,OAAO,eAAe,CAAC;AACtC;AAAA,QACF;AAEA,cAAM,UAAU;AAChB,cAAM,QAAQ,QAAQ,OAAO,KAAK;AAElC,YAAI,OAAO,eAAe;AACxB,gBAAM,aAAa,qBAAqB,KAAK;AAC7C,cAAI,CAAC,WAAW,OAAO;AACrB,oBAAQ,KAAK,EAAE,OAAO,iBAAiB,kBAAkB,WAAW,OAAO,CAAC;AAC5E;AAAA,UACF;AAAA,QACF;AAEA,cAAM,SAAS,OAAO,aAAa,KAA6C;AAEhF,gBAAQ,KAAK;AAAA,UACX,QAAQ,OAAO;AAAA,UACf,WAAW,OAAO,UAAU,IAAI,QAAM;AAAA,YACpC,aAAa,EAAE,UAAU;AAAA,YACzB,eAAe,EAAE,UAAU;AAAA,YAC3B,UAAU,EAAE,UAAU;AAAA,YACtB,UAAU,EAAE,UAAU;AAAA,YACtB,iBAAiB,EAAE,UAAU;AAAA,UAC/B,EAAE;AAAA,QACJ,CAAC;AACD;AAAA,MACF;AAGA,UAAI,SAAS,OAAO,WAAW,SAAS,OAAO,gBAAgB;AAC7D,uBAAe,KAAK,MAAM,SAAS;AACnC,YAAI,UAAU,2BAA2B,wNAAwN;AACjQ,YAAI,UAAU,iBAAiB,oBAAoB;AACnD,YAAI,UAAU,KAAK,EAAE,gBAAgB,2BAA2B,CAAC;AACjE,YAAI,IAAI,iBAAiB,CAAC;AAC1B;AAAA,MACF;AAGA,UAAI,SAAS,cAAc,WAAW,OAAO;AAC3C,cAAM,SAAS,OAAO,UAAU;AAChC,cAAM,SAAS,OAAO,MAAM,OAAO;AACnC,cAAM,UAAU,OAAO,MAAM,cAAc,GAAG;AAC9C,gBAAQ,KAAK,EAAE,QAAQ,QAAQ,CAAC;AAChC;AAAA,MACF;AAGA,UAAI,SAAS,uBAAuB,WAAW,OAAO;AACpD,cAAM,SAAS,OAAO,UAAU;AAChC,cAAM,SAAS,OAAO,MAAM,OAAO;AACnC,cAAM,OAAO,OAAO,uBAAuB,CAAC;AAC5C,cAAM,QAAQ,OAAO,OAAO,IAAI,EAAE,OAAO,CAAC,GAAW,MAAM,IAAK,GAAc,CAAC;AAC/E,gBAAQ,KAAK,EAAE,cAAc,MAAM,MAAM,CAAC;AAC1C;AAAA,MACF;AAGA,UAAI,SAAS,cAAc,WAAW,QAAQ;AAC5C,YAAI,CAAC,UAAU,KAAK,MAAM,GAAG;AAC3B,kBAAQ,KAAK,EAAE,OAAO,eAAe,CAAC;AACtC;AAAA,QACF;AACA,cAAM,OAAO,MAAM,SAAS,GAAG;AAC/B,YAAI;AACJ,YAAI;AAAE,mBAAS,aAAa,KAAK,MAAM,IAAI,CAAC;AAAA,QAAG,QAAQ;AACrD,kBAAQ,KAAK,EAAE,OAAO,eAAe,CAAC;AACtC;AAAA,QACF;AACA,cAAM,UAAU;AAChB,cAAM,aAAa,QAAQ,YAAY;AACvC,YAAI,CAAC,YAAY;AACf,kBAAQ,KAAK,EAAE,OAAO,sBAAsB,CAAC;AAC7C;AAAA,QACF;AAEA,cAAM,SAAS,OAAO,UAAU;AAChC,YAAI,OAAO,QAAQ,MAAM,WAAW;AAClC,kBAAQ,KAAK,EAAE,OAAO,sBAAsB,CAAC;AAC7C;AAAA,QACF;AAEA,cAAM,QAAQ,OAAO,IAAI,QAAQ,UAAU;AAC3C,YAAI,CAAC,OAAO;AACV,kBAAQ,KAAK,EAAE,OAAO,qBAAqB,CAAC;AAC5C;AAAA,QACF;AACA,YAAI,MAAM,WAAW,oBAAoB;AACvC,kBAAQ,KAAK,EAAE,OAAO,wBAAwB,eAAe,MAAM,OAAO,CAAC;AAC3E;AAAA,QACF;AAEA,cAAM,OAAO,MAAM,MAAM,IAAI;AAC7B,eAAO,IAAI,aAAa,YAAY,SAAS;AAC7C,eAAO,UAAU,EAAE,MAAM,kBAAkB,QAAQ,YAAY,WAAW,CAAC;AAC3E,gBAAQ,KAAK;AAAA,UACX,IAAI;AAAA,UACJ,WAAW,MAAM,KAAK;AAAA,UACtB,OAAO,MAAM,KAAK;AAAA,QACpB,CAAC;AACD;AAAA,MACF;AAGA,UAAI,SAAS,aAAa,WAAW,QAAQ;AAC3C,YAAI,CAAC,UAAU,KAAK,MAAM,GAAG;AAC3B,kBAAQ,KAAK,EAAE,OAAO,eAAe,CAAC;AACtC;AAAA,QACF;AACA,cAAM,OAAO,MAAM,SAAS,GAAG;AAC/B,YAAI;AACJ,YAAI;AAAE,mBAAS,aAAa,KAAK,MAAM,IAAI,CAAC;AAAA,QAAG,QAAQ;AACrD,kBAAQ,KAAK,EAAE,OAAO,eAAe,CAAC;AACtC;AAAA,QACF;AACA,cAAM,UAAU;AAChB,cAAM,aAAa,QAAQ,YAAY;AACvC,cAAM,SAAU,QAAQ,QAAQ,KAAgB;AAChD,YAAI,CAAC,YAAY;AACf,kBAAQ,KAAK,EAAE,OAAO,sBAAsB,CAAC;AAC7C;AAAA,QACF;AAEA,cAAM,SAAS,OAAO,UAAU;AAChC,YAAI,OAAO,QAAQ,MAAM,WAAW;AAClC,kBAAQ,KAAK,EAAE,OAAO,sBAAsB,CAAC;AAC7C;AAAA,QACF;AAEA,cAAM,QAAQ,OAAO,IAAI,QAAQ,UAAU;AAC3C,YAAI,CAAC,OAAO;AACV,kBAAQ,KAAK,EAAE,OAAO,qBAAqB,CAAC;AAC5C;AAAA,QACF;AACA,YAAI,MAAM,WAAW,oBAAoB;AACvC,kBAAQ,KAAK,EAAE,OAAO,wBAAwB,eAAe,MAAM,OAAO,CAAC;AAC3E;AAAA,QACF;AAEA,eAAO,IAAI,aAAa,YAAY,YAAY,MAAM;AACtD,eAAO,UAAU,EAAE,MAAM,kBAAkB,QAAQ,YAAY,YAAY,OAAO,CAAC;AACnF,gBAAQ,KAAK,EAAE,IAAI,MAAM,WAAW,CAAC;AACrC;AAAA,MACF;AAGA,UAAI,SAAS,cAAc,WAAW,OAAO;AAC3C,cAAM,SAAS,OAAO,UAAU;AAChC,cAAM,UAAU,OAAO,IAAI,MAAM,EAAE,QAAQ,mBAAmB,CAAC;AAC/D,gBAAQ,KAAK;AAAA,UACX,MAAM,OAAO,QAAQ;AAAA,UACrB;AAAA,UACA,OAAO,QAAQ;AAAA,QACjB,CAAC;AACD;AAAA,MACF;AAGA,cAAQ,KAAK,EAAE,OAAO,YAAY,CAAC;AAAA,IACrC,SAAS,KAAK;AACZ,cAAQ,MAAM,0CAA0C,GAAG;AAC3D,cAAQ,KAAK,EAAE,OAAO,wBAAwB,CAAC;AAAA,IACjD;AAAA,EACF;AACF;;;AEvcA,SAAS,iBAAiB,iBAAiB;AAC3C,SAAS,wBAAAA,6BAAmE;AAQ5E,SAAS,KAAK,IAAe,MAAqC;AAChE,MAAI,GAAG,eAAe,UAAU,MAAM;AACpC,OAAG,KAAK,KAAK,UAAU,IAAI,CAAC;AAAA,EAC9B;AACF;AAOA,IAAM,iBAAiB;AACvB,IAAM,qBAAqB;AAC3B,IAAM,uBAAuB;AAG7B,SAASC,cAAa,KAAuB;AAC3C,MAAI,QAAQ,QAAQ,OAAO,QAAQ,SAAU,QAAO;AACpD,MAAI,MAAM,QAAQ,GAAG,EAAG,QAAO,IAAI,IAAIA,aAAY;AACnD,QAAM,QAAiC,CAAC;AACxC,aAAW,CAAC,KAAK,GAAG,KAAK,OAAO,QAAQ,GAA8B,GAAG;AACvE,QAAI,QAAQ,eAAe,QAAQ,iBAAiB,QAAQ,YAAa;AACzE,UAAM,GAAG,IAAIA,cAAa,GAAG;AAAA,EAC/B;AACA,SAAO;AACT;AAEO,SAAS,uBACd,YACA,QACiB;AACjB,QAAM,MAAM,IAAI,gBAAgB,EAAE,QAAQ,YAAY,YAAY,eAAe,CAAC;AAGlF,QAAM,WAAW,oBAAI,QAA4B;AAEjD,QAAM,oBAAoB,YAAY,MAAM;AAC1C,eAAW,MAAM,IAAI,SAAS;AAC5B,UAAI,GAAG,eAAe,UAAU,MAAM;AACpC,YAAI,SAAS,IAAI,EAAE,MAAM,OAAO;AAE9B,aAAG,UAAU;AACb;AAAA,QACF;AACA,iBAAS,IAAI,IAAI,KAAK;AACtB,WAAG,KAAK;AAAA,MACV;AAAA,IACF;AAAA,EACF,GAAG,GAAM;AAET,MAAI,GAAG,cAAc,CAAC,IAAI,QAAQ;AAChC,QAAI,IAAI,QAAQ,OAAO,oBAAoB;AACzC,SAAG,MAAM,MAAM,oBAAoB;AACnC;AAAA,IACF;AAGA,UAAM,WAAW,IAAI,QAAQ,QAAQ;AACrC,QAAI,YAAY,OAAO,eAAe,KAAK;AACzC,UAAI,SAAS,YAAY,MAAM,OAAO,WAAW,YAAY,GAAG;AAC9D,WAAG,MAAM,MAAM,oBAAoB;AACnC;AAAA,MACF;AAAA,IACF;AAGA,QAAI,OAAO,QAAQ;AACjB,YAAM,MAAM,IAAI,IAAI,IAAI,OAAO,KAAK,UAAU,IAAI,QAAQ,QAAQ,WAAW,EAAE;AAC/E,YAAM,QAAQ,IAAI,aAAa,IAAI,OAAO,KAAK,IAAI,QAAQ,eAAe,GAAG,QAAQ,WAAW,EAAE;AAClG,UAAI,UAAU,OAAO,QAAQ;AAC3B,WAAG,MAAM,MAAM,cAAc;AAC7B;AAAA,MACF;AAAA,IACF;AAEA,YAAQ,IAAI,kCAAkC;AAC9C,aAAS,IAAI,IAAI,IAAI;AAErB,QAAI,eAAe;AAEnB,OAAG,GAAG,QAAQ,MAAM;AAClB,eAAS,IAAI,IAAI,IAAI;AAAA,IACvB,CAAC;AAED,OAAG,GAAG,SAAS,MAAM;AACnB,cAAQ,IAAI,qCAAqC;AAAA,IACnD,CAAC;AAED,OAAG,GAAG,WAAW,OAAO,QAAQ;AAC9B,UAAI;AACJ,UAAI;AACF,cAAMA,cAAa,KAAK,MAAM,IAAI,SAAS,CAAC,CAAC;AAAA,MAC/C,QAAQ;AACN,aAAK,IAAI,EAAE,MAAM,SAAS,SAAS,iBAAiB,CAAC;AACrD;AAAA,MACF;AAEA,UAAI,CAAC,IAAI,QAAQ,OAAO,IAAI,SAAS,UAAU;AAC7C,aAAK,IAAI,EAAE,MAAM,SAAS,SAAS,uBAAuB,CAAC;AAC3D;AAAA,MACF;AAEA,cAAQ,IAAI,MAAM;AAAA,QAChB,KAAK,QAAQ;AACX,gBAAM,MAAM,KAAK,IAAI;AACrB,cAAI,MAAM,eAAe,sBAAsB;AAC7C,iBAAK,IAAI,EAAE,MAAM,SAAS,SAAS,8CAAyC,CAAC;AAC7E;AAAA,UACF;AACA,yBAAe;AAEf,gBAAM,QAAQ,IAAI,OAAO;AACzB,gBAAM,SAAS,IAAI,QAAQ;AAE3B,cAAI,OAAO,eAAe;AACxB,kBAAM,aAAaD,sBAAqB,KAAK;AAC7C,gBAAI,CAAC,WAAW,OAAO;AACrB,mBAAK,IAAI,EAAE,MAAM,oBAAoB,kBAAkB,WAAW,OAAO,CAAC;AAC1E;AAAA,YACF;AAGA,gBAAI,WAAW,SAAS,SAAS,GAAG;AAClC,mBAAK,IAAI,EAAE,MAAM,sBAAsB,oBAAoB,WAAW,SAAS,CAAC;AAAA,YAClF;AAAA,UACF;AAEA,cAAI;AACF,kBAAM,SAAS,MAAM,OAAO;AAAA,cAC1B;AAAA,cACA,MAAM,QAAQ,MAAM,IAAI,SAA4B;AAAA,YACtD;AAEA,iBAAK,IAAI;AAAA,cACP,MAAM;AAAA,cACN,aAAa,OAAO;AAAA,cACpB,QAAQ,OAAO,OAAO,IAAI,QAAM;AAAA,gBAC9B,aAAa,EAAE,UAAU;AAAA,gBACzB,eAAe,EAAE,UAAU;AAAA,gBAC3B,UAAU,EAAE,UAAU;AAAA,gBACtB,WAAW,EAAE,UAAU,gBAAgB;AAAA,cACzC,EAAE;AAAA,cACF,QAAQ,OAAO;AAAA,cACf,MAAM,OAAO;AAAA,YACf,CAAC;AAAA,UACH,SAAS,MAAM;AACb,iBAAK,IAAI,EAAE,MAAM,SAAS,SAAS,yBAAyB,CAAC;AAAA,UAC/D;AACA;AAAA,QACF;AAAA,QAEA,KAAK,SAAS;AACZ,gBAAM,QAAQ,IAAI,OAAO;AACzB,cAAI,OAAO;AACT,mBAAO,UAAU,EAAE,OAAO,KAAK;AAC/B,iBAAK,IAAI,EAAE,MAAM,YAAY,CAAC;AAAA,UAChC,OAAO;AACL,iBAAK,IAAI,EAAE,MAAM,SAAS,SAAS,wBAAwB,CAAC;AAAA,UAC9D;AACA;AAAA,QACF;AAAA,QAEA,KAAK,UAAU;AACb,gBAAM,SAAS,OAAO,UAAU;AAChC,eAAK,IAAI;AAAA,YACP,MAAM;AAAA,YACN,QAAQ,OAAO,UAAU;AAAA,YACzB,MAAM,OAAO,QAAQ,OAAO,GAAG,QAAQ;AAAA,YACvC,MAAM,OAAO,QAAQ;AAAA,YACrB,aAAa,OAAO,eAAe,EAAE;AAAA,YACrC,QAAQ,OAAO,UAAU;AAAA,UAC3B,CAAC;AACD;AAAA,QACF;AAAA,QAEA,KAAK,YAAY;AACf,gBAAM,QAAQ,IAAI,OAAO;AAEzB,cAAI,OAAO,eAAe;AACxB,kBAAM,aAAaA,sBAAqB,KAAK;AAC7C,gBAAI,CAAC,WAAW,OAAO;AACrB,mBAAK,IAAI,EAAE,MAAM,oBAAoB,kBAAkB,WAAW,OAAO,CAAC;AAC1E;AAAA,YACF;AAAA,UACF;AAEA,gBAAM,SAAS,OAAO,aAAa,KAAqB;AACxD,eAAK,IAAI;AAAA,YACP,MAAM;AAAA,YACN,QAAQ,OAAO;AAAA,YACf,WAAW,OAAO,UAAU,IAAI,QAAM;AAAA,cACpC,aAAa,EAAE,UAAU;AAAA,cACzB,eAAe,EAAE,UAAU;AAAA,cAC3B,UAAU,EAAE,UAAU;AAAA,cACtB,iBAAiB,EAAE,UAAU;AAAA,YAC/B,EAAE;AAAA,UACJ,CAAC;AACD;AAAA,QACF;AAAA,QAEA;AACE,eAAK,IAAI,EAAE,MAAM,SAAS,SAAS,0BAA0B,OAAO,IAAI,IAAI,EAAE,MAAM,GAAG,GAAG,CAAC,IAAI,CAAC;AAAA,MACpG;AAAA,IACF,CAAC;AAAA,EACH,CAAC;AAED,WAAS,UAAU,MAAqC;AACtD,UAAM,UAAU,KAAK,UAAU,IAAI;AACnC,eAAW,MAAM,IAAI,SAAS;AAC5B,UAAI,GAAG,eAAe,UAAU,MAAM;AACpC,WAAG,KAAK,OAAO;AAAA,MACjB;AAAA,IACF;AAAA,EACF;AAEA,SAAO;AAAA,IACL,SAAS,MAAM;AACb,oBAAc,iBAAiB;AAC/B,UAAI,MAAM;AAAA,IACZ;AAAA,IACA;AAAA,EACF;AACF;;;AH/LO,IAAM,eAAN,MAAmB;AAAA,EAgBxB,YAAY,SAAuB,CAAC,GAAG;AAbvC,SAAQ,YAAiC;AACzC,SAAQ,kBAAsC,CAAC;AAC/C,SAAQ,SAAsB,CAAC;AAI/B,SAAiB,YAAY,KAAK,IAAI;AACtC,SAAQ,WAAmC;AAOzC,SAAK,OAAO,OAAO,QAAQ;AAC3B,SAAK,OAAO,OAAO,QAAQ;AAC3B,SAAK,SAAS,OAAO;AACrB,SAAK,gBAAgB,OAAO,iBAAiB;AAC7C,SAAK,aAAa,OAAO,cAAc;AACvC,SAAK,iBAAiB,OAAO,kBAAkB;AAG/C,UAAM,UAA0B;AAAA,MAC9B,UAAU,MAAM;AACd,YAAI,CAAC,KAAK,WAAW;AACnB,iBAAO;AAAA,YACL,MAAM;AAAA,YACN,OAAO,CAAC;AAAA,YACR,WAAW,CAAC;AAAA,YACZ,YAAY,CAAC,SAAS;AAAA,YACtB,eAAe,CAAC;AAAA,YAChB,YAAY,CAAC;AAAA,YACb,kBAAkB,CAAC;AAAA,YACnB,cAAc,CAAC;AAAA,YACf,oBAAoB,CAAC;AAAA,UACvB;AAAA,QACF;AACA,eAAO,KAAK;AAAA,MACd;AAAA,MACA,UAAU,CAAC,KAAa,OAAe,UAAmD;AACxF,aAAK,gBAAgB,KAAK,EAAE,KAAK,OAAO,MAAM,CAAC;AAAA,MACjD;AAAA,IACF;AAEA,UAAM,YAAY,OAAO,UAAU,CAAC;AACpC,UAAM,eAA6B;AAAA,MACjC;AAAA,MACA,MAAM,UAAU,QAAQ;AAAA,MACxB,aAAa,UAAU,eAAe;AAAA,MACtC,eAAe,UAAU,iBAAiB;AAAA,MAC1C,GAAI,UAAU,gBAAgB,EAAE,eAAe,UAAU,cAAc,IAAI,CAAC;AAAA,MAC5E,GAAI,UAAU,oBAAoB,EAAE,mBAAmB,UAAU,kBAAkB,IAAI,CAAC;AAAA,MACxF,GAAI,UAAU,yBAAyB,SAAY,EAAE,sBAAsB,UAAU,qBAAqB,IAAI,CAAC;AAAA,MAC/G,GAAI,UAAU,kBAAkB,SAAY,EAAE,eAAe,UAAU,cAAc,IAAI,CAAC;AAAA,MAC1F,GAAI,UAAU,aAAa,EAAE,YAAY,UAAU,WAAW,IAAI,CAAC;AAAA,IACrE;AAEA,SAAK,aAAa;AAAA,MAChB,GAAG;AAAA,MACH,GAAI,UAAU,cAAc,CAAC;AAAA,MAC7B,GAAI,UAAU,yBAAyB,SAAY,EAAE,sBAAsB,UAAU,qBAAqB,IAAI,CAAC;AAAA,MAC/G,GAAI,UAAU,kBAAkB,SAAY,EAAE,eAAe,UAAU,cAAc,IAAI,CAAC;AAAA,IAC5F;AACA,SAAK,SAAS,IAAI,OAAO,YAAY;AAGrC,SAAK,OAAO,GAAG,SAAS,CAAC,cAAuB;AAC9C,WAAK,OAAO,KAAK,SAAsB;AAAA,IACzC,CAAC;AAED,SAAK,OAAO,QAAQ,OAAO,EAAE,MAAM;AAGnC,UAAM,eAAe,mBAAmB,IAAI;AAC5C,SAAK,SAAc,kBAAa,YAAY;AAAA,EAC9C;AAAA,EAEA,MAAM,QAAuB;AAE3B,SAAK,WAAW,uBAAuB,KAAK,QAAQ,IAAI;AAExD,WAAO,IAAI,QAAQ,CAAC,YAAY;AAC9B,WAAK,OAAO,OAAO,KAAK,MAAM,KAAK,MAAM,MAAM;AAC7C,cAAM,OAAO,KAAK,WAAW;AAC7B,gBAAQ,IAAI,uCAAuC,KAAK,IAAI,IAAI,KAAK,IAAI,EAAE;AAC3E,gBAAQ;AAAA,MACV,CAAC;AAAA,IACH,CAAC;AAAA,EACH;AAAA,EAEA,MAAM,OAAsB;AAC1B,SAAK,OAAO,KAAK;AACjB,QAAI,KAAK,SAAU,MAAK,SAAS,QAAQ;AACzC,WAAO,IAAI,QAAQ,CAAC,SAAS,WAAW;AACtC,WAAK,OAAO,MAAM,CAAC,QAAQ;AACzB,YAAI,IAAK,QAAO,GAAG;AAAA,YACd,SAAQ;AAAA,MACf,CAAC;AAAA,IACH,CAAC;AAAA,EACH;AAAA,EAEA,YAAoB;AAClB,WAAO,KAAK;AAAA,EACd;AAAA,EAEA,aAA6C;AAC3C,UAAM,OAAO,KAAK,OAAO,QAAQ;AACjC,QAAI,QAAQ,OAAO,SAAS,UAAU;AACpC,aAAO,EAAE,MAAM,KAAK,MAAM,MAAM,KAAK,QAAQ;AAAA,IAC/C;AACA,WAAO,EAAE,MAAM,KAAK,MAAM,MAAM,KAAK,KAAK;AAAA,EAC5C;AAAA,EAEA,YAAoB;AAClB,WAAO,KAAK,IAAI,IAAI,KAAK;AAAA,EAC3B;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAWA,MAAM,YACJ,OACA,QAOC;AAED,SAAK,kBAAkB,CAAC;AACxB,SAAK,SAAS,CAAC;AAGf,SAAK,YAAY;AAGjB,QAAI,QAAQ;AACV,iBAAW,SAAS,QAAQ;AAC1B,aAAK,OAAO,OAAO,KAAK;AAAA,MAC1B;AAAA,IACF;AAGA,UAAM,KAAK,OAAO,KAAK,KAAK;AAG5B,UAAM,SAAS,CAAC,GAAG,KAAK,eAAe;AACvC,SAAK,kBAAkB,CAAC;AAGxB,UAAM,YAAY,KAAK,OAAO,aAAa,EAAE,OAAO,MAAM,MAAM,OAAO,MAAM,KAAK,CAAC;AAEnF,UAAM,cAAoC,OAAO,IAAI,SAAO;AAC1D,YAAM,WAAW,UAAU;AAAA,QAAK,OAC9B,EAAE,KAAK,cAAc,IAAI,OAAO,EAAE,WAAW;AAAA,MAC/C;AACA,aAAO;AAAA,QACL,WAAW,IAAI;AAAA,QACf,OAAO,IAAI;AAAA,QACX,GAAI,IAAI,QAAQ,EAAE,OAAO,IAAI,MAAM,IAAI,CAAC;AAAA,QACxC,WAAW,UAAU,UAAU,UAAU,gBAAgB,aAAa;AAAA,MACxE;AAAA,IACF,CAAC;AAED,WAAO;AAAA,MACL;AAAA,MACA,QAAQ,CAAC,GAAG,KAAK,MAAM;AAAA,MACvB,QAAQ,KAAK,OAAO,UAAU;AAAA,MAC9B,MAAM,MAAM;AAAA,MACZ;AAAA,IACF;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,aAAa,OAGX;AACA,UAAM,WAAW,IAAI,SAAS;AAC9B,UAAM,YAAY,IAAI,UAAU,cAAc;AAC9C,UAAM,UAAU,SAAS,QAAQ,OAAO,CAAC,CAAC;AAC1C,UAAM,YAAY,UAAU,SAAS,SAAS,KAAK,UAAU;AAG7D,QAAI,SAAS;AACb,QAAI,QAAQ,kBAAkB,GAAI,WAAU;AAC5C,QAAI,QAAQ,kBAAkB,GAAI,WAAU;AAC5C,QAAI,QAAQ,kBAAkB,KAAM,WAAU;AAC9C,QAAI,QAAQ,kBAAkB,IAAM,WAAU;AAC9C,QAAI,KAAK,IAAI,QAAQ,OAAO,IAAI,GAAI,WAAU;AAC9C,QAAI,KAAK,IAAI,QAAQ,OAAO,IAAI,GAAI,WAAU;AAC9C,QAAI,QAAQ,YAAY,KAAM,WAAU;AACxC,aAAS,KAAK,IAAI,GAAG,KAAK,IAAI,KAAK,MAAM,CAAC;AAE1C,WAAO,EAAE,WAAW,OAAO;AAAA,EAC7B;AAAA,EAEA,QAAQ,MAAwB;AAC9B,SAAK,OAAO,QAAQ,IAAI;AAAA,EAC1B;AAAA,EAEA,KAAK,OAAqB;AACxB,SAAK,OAAO,KAAK,KAAK;AAAA,EACxB;AAAA,EAEA,OAAO,OAAqB;AAC1B,SAAK,OAAO,OAAO,KAAK;AAAA,EAC1B;AAAA,EAEA,UAAU,OAAe,QAA4C;AACnE,SAAK,OAAO,UAAU,OAAO,MAAM;AAAA,EACrC;AAAA,EAEA,UAAU,MAAqC;AAC7C,QAAI,KAAK,SAAU,MAAK,SAAS,UAAU,IAAI;AAAA,EACjD;AACF;","names":["validateEconomyState","sanitizeJson"]}
|