@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/index.js CHANGED
@@ -979,8 +979,17 @@ function setSecurityHeaders(res) {
979
979
  }
980
980
  function setCorsHeaders(res, allowedOrigin, requestOrigin) {
981
981
  setSecurityHeaders(res);
982
- const origin = allowedOrigin === "*" ? "*" : requestOrigin === allowedOrigin ? allowedOrigin : allowedOrigin;
983
- res.setHeader("Access-Control-Allow-Origin", origin);
982
+ let origin;
983
+ if (allowedOrigin === "*") {
984
+ origin = "*";
985
+ } else if (requestOrigin === void 0) {
986
+ origin = allowedOrigin;
987
+ } else {
988
+ origin = requestOrigin.toLowerCase() === allowedOrigin.toLowerCase() ? requestOrigin : "";
989
+ }
990
+ if (origin) {
991
+ res.setHeader("Access-Control-Allow-Origin", origin);
992
+ }
984
993
  res.setHeader("Access-Control-Allow-Methods", "GET, POST, OPTIONS");
985
994
  res.setHeader("Access-Control-Allow-Headers", "Content-Type, Authorization");
986
995
  }
@@ -997,7 +1006,10 @@ function sanitizeJson(obj) {
997
1006
  function checkAuth(req, apiKey) {
998
1007
  if (!apiKey) return true;
999
1008
  const header = req.headers["authorization"];
1000
- return header === `Bearer ${apiKey}`;
1009
+ if (typeof header !== "string") return false;
1010
+ const expected = `Bearer ${apiKey}`;
1011
+ if (header.length !== expected.length) return false;
1012
+ return (0, import_node_crypto.timingSafeEqual)(Buffer.from(header), Buffer.from(expected));
1001
1013
  }
1002
1014
  function json(res, status, data, origin, reqOrigin) {
1003
1015
  setCorsHeaders(res, origin, reqOrigin);
@@ -1028,8 +1040,10 @@ function createRouteHandler(server) {
1028
1040
  const url = new URL(req.url ?? "/", `http://${req.headers.host ?? "localhost"}`);
1029
1041
  const path = url.pathname;
1030
1042
  const method = req.method?.toUpperCase() ?? "GET";
1043
+ const reqOrigin = req.headers["origin"];
1044
+ const respond = (status, data) => json(res, status, data, cors, reqOrigin);
1031
1045
  if (method === "OPTIONS") {
1032
- setCorsHeaders(res, cors);
1046
+ setCorsHeaders(res, cors, reqOrigin);
1033
1047
  res.writeHead(204);
1034
1048
  res.end();
1035
1049
  return;
@@ -1037,7 +1051,7 @@ function createRouteHandler(server) {
1037
1051
  try {
1038
1052
  if (path === "/tick" && method === "POST") {
1039
1053
  if (!checkAuth(req, apiKey)) {
1040
- json(res, 401, { error: "Unauthorized" }, cors);
1054
+ respond(401, { error: "Unauthorized" });
1041
1055
  return;
1042
1056
  }
1043
1057
  const body = await readBody(req);
@@ -1045,11 +1059,11 @@ function createRouteHandler(server) {
1045
1059
  try {
1046
1060
  parsed = sanitizeJson(JSON.parse(body));
1047
1061
  } catch {
1048
- json(res, 400, { error: "Invalid JSON" }, cors);
1062
+ respond(400, { error: "Invalid JSON" });
1049
1063
  return;
1050
1064
  }
1051
1065
  if (!parsed || typeof parsed !== "object") {
1052
- json(res, 400, { error: "Body must be a JSON object" }, cors);
1066
+ respond(400, { error: "Body must be a JSON object" });
1053
1067
  return;
1054
1068
  }
1055
1069
  const payload = parsed;
@@ -1057,10 +1071,10 @@ function createRouteHandler(server) {
1057
1071
  const events = payload["events"];
1058
1072
  const validation = server.validateState ? (0, import_core.validateEconomyState)(state) : null;
1059
1073
  if (validation && !validation.valid) {
1060
- json(res, 400, {
1074
+ respond(400, {
1061
1075
  error: "invalid_state",
1062
1076
  validationErrors: validation.errors
1063
- }, cors);
1077
+ });
1064
1078
  return;
1065
1079
  }
1066
1080
  const result = await server.processTick(
@@ -1068,7 +1082,7 @@ function createRouteHandler(server) {
1068
1082
  Array.isArray(events) ? events : void 0
1069
1083
  );
1070
1084
  const warnings = validation?.warnings ?? [];
1071
- json(res, 200, {
1085
+ respond(200, {
1072
1086
  adjustments: result.adjustments,
1073
1087
  alerts: result.alerts.map((a) => ({
1074
1088
  principleId: a.principle.id,
@@ -1080,18 +1094,18 @@ function createRouteHandler(server) {
1080
1094
  health: result.health,
1081
1095
  tick: result.tick,
1082
1096
  ...warnings.length > 0 ? { validationWarnings: warnings } : {}
1083
- }, cors);
1097
+ });
1084
1098
  return;
1085
1099
  }
1086
1100
  if (path === "/health" && method === "GET") {
1087
1101
  const agentE = server.getAgentE();
1088
- json(res, 200, {
1102
+ respond(200, {
1089
1103
  health: agentE.getHealth(),
1090
1104
  tick: agentE.metrics.latest()?.tick ?? 0,
1091
1105
  mode: agentE.getMode(),
1092
1106
  activePlans: agentE.getActivePlans().length,
1093
1107
  uptime: server.getUptime()
1094
- }, cors);
1108
+ });
1095
1109
  return;
1096
1110
  }
1097
1111
  if (path === "/decisions" && method === "GET") {
@@ -1103,19 +1117,19 @@ function createRouteHandler(server) {
1103
1117
  if (sinceParam) {
1104
1118
  const since = parseInt(sinceParam, 10);
1105
1119
  if (Number.isNaN(since)) {
1106
- json(res, 400, { error: 'Invalid "since" parameter \u2014 must be a number' }, cors);
1120
+ respond(400, { error: 'Invalid "since" parameter \u2014 must be a number' });
1107
1121
  return;
1108
1122
  }
1109
1123
  decisions = agentE.getDecisions({ since });
1110
1124
  } else {
1111
1125
  decisions = agentE.log.latest(limit);
1112
1126
  }
1113
- json(res, 200, { decisions }, cors);
1127
+ respond(200, { decisions });
1114
1128
  return;
1115
1129
  }
1116
1130
  if (path === "/config" && method === "POST") {
1117
1131
  if (!checkAuth(req, apiKey)) {
1118
- json(res, 401, { error: "Unauthorized" }, cors);
1132
+ respond(401, { error: "Unauthorized" });
1119
1133
  return;
1120
1134
  }
1121
1135
  const body = await readBody(req);
@@ -1123,7 +1137,7 @@ function createRouteHandler(server) {
1123
1137
  try {
1124
1138
  parsed = sanitizeJson(JSON.parse(body));
1125
1139
  } catch {
1126
- json(res, 400, { error: "Invalid JSON" }, cors);
1140
+ respond(400, { error: "Invalid JSON" });
1127
1141
  return;
1128
1142
  }
1129
1143
  const config = parsed;
@@ -1143,11 +1157,11 @@ function createRouteHandler(server) {
1143
1157
  if (c && typeof c === "object" && typeof c["param"] === "string" && typeof c["min"] === "number" && typeof c["max"] === "number") {
1144
1158
  const constraint = c;
1145
1159
  if (!Number.isFinite(constraint.min) || !Number.isFinite(constraint.max)) {
1146
- json(res, 400, { error: "Constraint bounds must be finite numbers" }, cors);
1160
+ respond(400, { error: "Constraint bounds must be finite numbers" });
1147
1161
  return;
1148
1162
  }
1149
1163
  if (constraint.min > constraint.max) {
1150
- json(res, 400, { error: "Constraint min cannot exceed max" }, cors);
1164
+ respond(400, { error: "Constraint min cannot exceed max" });
1151
1165
  return;
1152
1166
  }
1153
1167
  validated.push(constraint);
@@ -1160,12 +1174,12 @@ function createRouteHandler(server) {
1160
1174
  if (config["mode"] === "autonomous" || config["mode"] === "advisor") {
1161
1175
  server.setMode(config["mode"]);
1162
1176
  }
1163
- json(res, 200, { ok: true }, cors);
1177
+ respond(200, { ok: true });
1164
1178
  return;
1165
1179
  }
1166
1180
  if (path === "/principles" && method === "GET") {
1167
1181
  const principles = server.getAgentE().getPrinciples();
1168
- json(res, 200, {
1182
+ respond(200, {
1169
1183
  count: principles.length,
1170
1184
  principles: principles.map((p) => ({
1171
1185
  id: p.id,
@@ -1173,12 +1187,12 @@ function createRouteHandler(server) {
1173
1187
  category: p.category,
1174
1188
  description: p.description
1175
1189
  }))
1176
- }, cors);
1190
+ });
1177
1191
  return;
1178
1192
  }
1179
1193
  if (path === "/diagnose" && method === "POST") {
1180
1194
  if (!checkAuth(req, apiKey)) {
1181
- json(res, 401, { error: "Unauthorized" }, cors);
1195
+ respond(401, { error: "Unauthorized" });
1182
1196
  return;
1183
1197
  }
1184
1198
  const body = await readBody(req);
@@ -1186,7 +1200,7 @@ function createRouteHandler(server) {
1186
1200
  try {
1187
1201
  parsed = sanitizeJson(JSON.parse(body));
1188
1202
  } catch {
1189
- json(res, 400, { error: "Invalid JSON" }, cors);
1203
+ respond(400, { error: "Invalid JSON" });
1190
1204
  return;
1191
1205
  }
1192
1206
  const payload = parsed;
@@ -1194,12 +1208,12 @@ function createRouteHandler(server) {
1194
1208
  if (server.validateState) {
1195
1209
  const validation = (0, import_core.validateEconomyState)(state);
1196
1210
  if (!validation.valid) {
1197
- json(res, 400, { error: "invalid_state", validationErrors: validation.errors }, cors);
1211
+ respond(400, { error: "invalid_state", validationErrors: validation.errors });
1198
1212
  return;
1199
1213
  }
1200
1214
  }
1201
1215
  const result = server.diagnoseOnly(state);
1202
- json(res, 200, {
1216
+ respond(200, {
1203
1217
  health: result.health,
1204
1218
  diagnoses: result.diagnoses.map((d) => ({
1205
1219
  principleId: d.principle.id,
@@ -1208,11 +1222,11 @@ function createRouteHandler(server) {
1208
1222
  evidence: d.violation.evidence,
1209
1223
  suggestedAction: d.violation.suggestedAction
1210
1224
  }))
1211
- }, cors);
1225
+ });
1212
1226
  return;
1213
1227
  }
1214
1228
  if (path === "/" && method === "GET" && server.serveDashboard) {
1215
- setCorsHeaders(res, cors);
1229
+ setCorsHeaders(res, cors, reqOrigin);
1216
1230
  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:");
1217
1231
  res.setHeader("Cache-Control", "public, max-age=60");
1218
1232
  res.writeHead(200, { "Content-Type": "text/html; charset=utf-8" });
@@ -1223,7 +1237,7 @@ function createRouteHandler(server) {
1223
1237
  const agentE = server.getAgentE();
1224
1238
  const latest = agentE.store.latest();
1225
1239
  const history = agentE.store.recentHistory(100);
1226
- json(res, 200, { latest, history }, cors);
1240
+ respond(200, { latest, history });
1227
1241
  return;
1228
1242
  }
1229
1243
  if (path === "/metrics/personas" && method === "GET") {
@@ -1231,12 +1245,12 @@ function createRouteHandler(server) {
1231
1245
  const latest = agentE.store.latest();
1232
1246
  const dist = latest.personaDistribution || {};
1233
1247
  const total = Object.values(dist).reduce((s, v) => s + v, 0);
1234
- json(res, 200, { distribution: dist, total }, cors);
1248
+ respond(200, { distribution: dist, total });
1235
1249
  return;
1236
1250
  }
1237
1251
  if (path === "/approve" && method === "POST") {
1238
1252
  if (!checkAuth(req, apiKey)) {
1239
- json(res, 401, { error: "Unauthorized" }, cors);
1253
+ respond(401, { error: "Unauthorized" });
1240
1254
  return;
1241
1255
  }
1242
1256
  const body = await readBody(req);
@@ -1244,42 +1258,42 @@ function createRouteHandler(server) {
1244
1258
  try {
1245
1259
  parsed = sanitizeJson(JSON.parse(body));
1246
1260
  } catch {
1247
- json(res, 400, { error: "Invalid JSON" }, cors);
1261
+ respond(400, { error: "Invalid JSON" });
1248
1262
  return;
1249
1263
  }
1250
1264
  const payload = parsed;
1251
1265
  const decisionId = payload["decisionId"];
1252
1266
  if (!decisionId) {
1253
- json(res, 400, { error: "missing_decision_id" }, cors);
1267
+ respond(400, { error: "missing_decision_id" });
1254
1268
  return;
1255
1269
  }
1256
1270
  const agentE = server.getAgentE();
1257
1271
  if (agentE.getMode() !== "advisor") {
1258
- json(res, 400, { error: "not_in_advisor_mode" }, cors);
1272
+ respond(400, { error: "not_in_advisor_mode" });
1259
1273
  return;
1260
1274
  }
1261
1275
  const entry = agentE.log.getById(decisionId);
1262
1276
  if (!entry) {
1263
- json(res, 404, { error: "decision_not_found" }, cors);
1277
+ respond(404, { error: "decision_not_found" });
1264
1278
  return;
1265
1279
  }
1266
1280
  if (entry.result !== "skipped_override") {
1267
- json(res, 409, { error: "decision_not_pending", currentResult: entry.result }, cors);
1281
+ respond(409, { error: "decision_not_pending", currentResult: entry.result });
1268
1282
  return;
1269
1283
  }
1270
1284
  await agentE.apply(entry.plan);
1271
1285
  agentE.log.updateResult(decisionId, "applied");
1272
1286
  server.broadcast({ type: "advisor_action", action: "approved", decisionId });
1273
- json(res, 200, {
1287
+ respond(200, {
1274
1288
  ok: true,
1275
1289
  parameter: entry.plan.parameter,
1276
1290
  value: entry.plan.targetValue
1277
- }, cors);
1291
+ });
1278
1292
  return;
1279
1293
  }
1280
1294
  if (path === "/reject" && method === "POST") {
1281
1295
  if (!checkAuth(req, apiKey)) {
1282
- json(res, 401, { error: "Unauthorized" }, cors);
1296
+ respond(401, { error: "Unauthorized" });
1283
1297
  return;
1284
1298
  }
1285
1299
  const body = await readBody(req);
@@ -1287,56 +1301,57 @@ function createRouteHandler(server) {
1287
1301
  try {
1288
1302
  parsed = sanitizeJson(JSON.parse(body));
1289
1303
  } catch {
1290
- json(res, 400, { error: "Invalid JSON" }, cors);
1304
+ respond(400, { error: "Invalid JSON" });
1291
1305
  return;
1292
1306
  }
1293
1307
  const payload = parsed;
1294
1308
  const decisionId = payload["decisionId"];
1295
1309
  const reason = payload["reason"] || void 0;
1296
1310
  if (!decisionId) {
1297
- json(res, 400, { error: "missing_decision_id" }, cors);
1311
+ respond(400, { error: "missing_decision_id" });
1298
1312
  return;
1299
1313
  }
1300
1314
  const agentE = server.getAgentE();
1301
1315
  if (agentE.getMode() !== "advisor") {
1302
- json(res, 400, { error: "not_in_advisor_mode" }, cors);
1316
+ respond(400, { error: "not_in_advisor_mode" });
1303
1317
  return;
1304
1318
  }
1305
1319
  const entry = agentE.log.getById(decisionId);
1306
1320
  if (!entry) {
1307
- json(res, 404, { error: "decision_not_found" }, cors);
1321
+ respond(404, { error: "decision_not_found" });
1308
1322
  return;
1309
1323
  }
1310
1324
  if (entry.result !== "skipped_override") {
1311
- json(res, 409, { error: "decision_not_pending", currentResult: entry.result }, cors);
1325
+ respond(409, { error: "decision_not_pending", currentResult: entry.result });
1312
1326
  return;
1313
1327
  }
1314
1328
  agentE.log.updateResult(decisionId, "rejected", reason);
1315
1329
  server.broadcast({ type: "advisor_action", action: "rejected", decisionId, reason });
1316
- json(res, 200, { ok: true, decisionId }, cors);
1330
+ respond(200, { ok: true, decisionId });
1317
1331
  return;
1318
1332
  }
1319
1333
  if (path === "/pending" && method === "GET") {
1320
1334
  const agentE = server.getAgentE();
1321
1335
  const pending = agentE.log.query({ result: "skipped_override" });
1322
- json(res, 200, {
1336
+ respond(200, {
1323
1337
  mode: agentE.getMode(),
1324
1338
  pending,
1325
1339
  count: pending.length
1326
- }, cors);
1340
+ });
1327
1341
  return;
1328
1342
  }
1329
- json(res, 404, { error: "Not found" }, cors);
1343
+ respond(404, { error: "Not found" });
1330
1344
  } catch (err) {
1331
1345
  console.error("[AgentE Server] Unhandled route error:", err);
1332
- json(res, 500, { error: "Internal server error" }, cors);
1346
+ respond(500, { error: "Internal server error" });
1333
1347
  }
1334
1348
  };
1335
1349
  }
1336
- var import_core, MAX_BODY_BYTES;
1350
+ var import_node_crypto, import_core, MAX_BODY_BYTES;
1337
1351
  var init_routes = __esm({
1338
1352
  "src/routes.ts"() {
1339
1353
  "use strict";
1354
+ import_node_crypto = require("crypto");
1340
1355
  import_core = require("@agent-e/core");
1341
1356
  init_dashboard();
1342
1357
  MAX_BODY_BYTES = 1048576;
@@ -1379,6 +1394,13 @@ function createWebSocketHandler(httpServer, server) {
1379
1394
  ws.close(1013, "Server at capacity");
1380
1395
  return;
1381
1396
  }
1397
+ const wsOrigin = req.headers["origin"];
1398
+ if (wsOrigin && server.corsOrigin !== "*") {
1399
+ if (wsOrigin.toLowerCase() !== server.corsOrigin.toLowerCase()) {
1400
+ ws.close(1008, "Origin not allowed");
1401
+ return;
1402
+ }
1403
+ }
1382
1404
  if (server.apiKey) {
1383
1405
  const url = new URL(req.url ?? "/", `http://${req.headers.host ?? "localhost"}`);
1384
1406
  const token = url.searchParams.get("token") ?? req.headers["authorization"]?.replace("Bearer ", "");