@agent-e/server 1.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,167 @@
1
+ # @agent-e/server
2
+
3
+ Plug-and-play HTTP + WebSocket server for AgentE. Send your game's economy state, get back parameter adjustments.
4
+
5
+ ## Quick Start
6
+
7
+ ```ts
8
+ import { startServer } from '@agent-e/server';
9
+
10
+ const server = await startServer({ port: 3000 });
11
+ // Server is running — POST /tick, GET /health, etc.
12
+ ```
13
+
14
+ Or with full configuration:
15
+
16
+ ```ts
17
+ import { AgentEServer } from '@agent-e/server';
18
+
19
+ const server = new AgentEServer({
20
+ port: 3000,
21
+ host: '0.0.0.0',
22
+ agentEConfig: {
23
+ mode: 'autonomous', // or 'advisor' for recommendations only
24
+ gracePeriod: 50, // no interventions before tick 50
25
+ checkInterval: 5, // analyze every 5 ticks
26
+ dominantRoles: ['Fighter'],
27
+ maxAdjustmentPercent: 0.15,
28
+ cooldownTicks: 15,
29
+ },
30
+ });
31
+
32
+ await server.start();
33
+ ```
34
+
35
+ ## API Reference
36
+
37
+ ### POST /tick
38
+
39
+ Send economy state, receive parameter adjustments.
40
+
41
+ **Request:**
42
+ ```json
43
+ {
44
+ "state": {
45
+ "tick": 100,
46
+ "roles": ["Fighter", "Crafter"],
47
+ "resources": ["ore", "weapons"],
48
+ "currencies": ["gold"],
49
+ "agentBalances": { "a1": { "gold": 150 } },
50
+ "agentRoles": { "a1": "Fighter" },
51
+ "agentInventories": { "a1": { "weapons": 2 } },
52
+ "marketPrices": { "gold": { "ore": 15 } },
53
+ "recentTransactions": []
54
+ },
55
+ "events": []
56
+ }
57
+ ```
58
+
59
+ **Response (200):**
60
+ ```json
61
+ {
62
+ "adjustments": [{ "key": "craftingCost", "value": 12.5 }],
63
+ "alerts": [{ "principle": "P1", "severity": 7 }],
64
+ "health": 85,
65
+ "decisions": [{ "id": "d_1", "parameter": "craftingCost", "result": "applied" }]
66
+ }
67
+ ```
68
+
69
+ **Error (400):** Invalid state returns validation errors.
70
+
71
+ ### GET /health
72
+
73
+ ```json
74
+ {
75
+ "health": 85,
76
+ "tick": 100,
77
+ "mode": "autonomous",
78
+ "activePlans": 1,
79
+ "uptime": 60000
80
+ }
81
+ ```
82
+
83
+ ### GET /decisions
84
+
85
+ Query parameters: `?limit=50`, `?since=100`
86
+
87
+ ```json
88
+ {
89
+ "decisions": [{
90
+ "id": "d_1",
91
+ "tick": 100,
92
+ "principle": "P1",
93
+ "parameter": "craftingCost",
94
+ "result": "applied",
95
+ "reasoning": "..."
96
+ }]
97
+ }
98
+ ```
99
+
100
+ ### POST /config
101
+
102
+ Lock/unlock parameters, change mode.
103
+
104
+ ```json
105
+ { "action": "lock", "param": "craftingCost" }
106
+ { "action": "unlock", "param": "craftingCost" }
107
+ { "action": "constrain", "param": "miningYield", "min": 0.5, "max": 2.0 }
108
+ { "action": "mode", "mode": "advisor" }
109
+ ```
110
+
111
+ ### GET /principles
112
+
113
+ Lists all 60 built-in principles.
114
+
115
+ ### POST /diagnose
116
+
117
+ Run Observer + Diagnoser without side effects (no parameter changes).
118
+
119
+ ```json
120
+ { "state": { ... } }
121
+ ```
122
+
123
+ ## WebSocket
124
+
125
+ Connect to the same port via WebSocket upgrade.
126
+
127
+ ### Client → Server Messages
128
+
129
+ ```json
130
+ { "type": "tick", "state": {...}, "events": [...] }
131
+ { "type": "event", "event": { "type": "trade", ... } }
132
+ { "type": "health" }
133
+ { "type": "diagnose", "state": {...} }
134
+ ```
135
+
136
+ ### Server → Client Messages
137
+
138
+ ```json
139
+ { "type": "tick_result", "adjustments": [...], "health": 85 }
140
+ { "type": "health_result", "health": 85, "uptime": 60000 }
141
+ { "type": "diagnose_result", "diagnoses": [...] }
142
+ { "type": "validation_error", "validation": {...} }
143
+ { "type": "validation_warning", "warning": {...} }
144
+ { "type": "error", "error": "..." }
145
+ ```
146
+
147
+ Heartbeat: Server pings every 30 seconds.
148
+
149
+ ## State Validation
150
+
151
+ All incoming state is validated before processing. Invalid state returns detailed errors with paths:
152
+
153
+ ```json
154
+ {
155
+ "error": "Invalid state",
156
+ "validation": {
157
+ "valid": false,
158
+ "errors": [{
159
+ "path": "agentBalances.a1.gold",
160
+ "expected": "number >= 0",
161
+ "received": "string",
162
+ "message": "agentBalances.a1.gold must be a non-negative number"
163
+ }],
164
+ "warnings": []
165
+ }
166
+ }
167
+ ```
@@ -0,0 +1,7 @@
1
+ import {
2
+ AgentEServer
3
+ } from "./chunk-VNNX52KX.mjs";
4
+ export {
5
+ AgentEServer
6
+ };
7
+ //# sourceMappingURL=AgentEServer-IF7QK2SK.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
@@ -0,0 +1,468 @@
1
+ // src/AgentEServer.ts
2
+ import * as http from "http";
3
+ import {
4
+ AgentE
5
+ } from "@agent-e/core";
6
+
7
+ // src/routes.ts
8
+ import { validateEconomyState } from "@agent-e/core";
9
+ function setCorsHeaders(res) {
10
+ res.setHeader("Access-Control-Allow-Origin", "*");
11
+ res.setHeader("Access-Control-Allow-Methods", "GET, POST, OPTIONS");
12
+ res.setHeader("Access-Control-Allow-Headers", "Content-Type");
13
+ }
14
+ function json(res, status, data) {
15
+ setCorsHeaders(res);
16
+ res.writeHead(status, { "Content-Type": "application/json" });
17
+ res.end(JSON.stringify(data));
18
+ }
19
+ function readBody(req) {
20
+ return new Promise((resolve, reject) => {
21
+ const chunks = [];
22
+ req.on("data", (chunk) => chunks.push(chunk));
23
+ req.on("end", () => resolve(Buffer.concat(chunks).toString("utf-8")));
24
+ req.on("error", reject);
25
+ });
26
+ }
27
+ function createRouteHandler(server) {
28
+ return async (req, res) => {
29
+ const url = new URL(req.url ?? "/", `http://${req.headers.host ?? "localhost"}`);
30
+ const path = url.pathname;
31
+ const method = req.method?.toUpperCase() ?? "GET";
32
+ if (method === "OPTIONS") {
33
+ setCorsHeaders(res);
34
+ res.writeHead(204);
35
+ res.end();
36
+ return;
37
+ }
38
+ try {
39
+ if (path === "/tick" && method === "POST") {
40
+ const body = await readBody(req);
41
+ let parsed;
42
+ try {
43
+ parsed = JSON.parse(body);
44
+ } catch {
45
+ json(res, 400, { error: "Invalid JSON" });
46
+ return;
47
+ }
48
+ if (!parsed || typeof parsed !== "object") {
49
+ json(res, 400, { error: "Body must be a JSON object" });
50
+ return;
51
+ }
52
+ const payload = parsed;
53
+ const state = payload["state"] ?? parsed;
54
+ const events = payload["events"];
55
+ const validation = validateEconomyState(state);
56
+ if (!validation.valid) {
57
+ json(res, 400, {
58
+ error: "Invalid state",
59
+ validation
60
+ });
61
+ return;
62
+ }
63
+ const result = await server.processTick(
64
+ state,
65
+ Array.isArray(events) ? events : void 0
66
+ );
67
+ json(res, 200, {
68
+ adjustments: result.adjustments,
69
+ alerts: result.alerts.map((a) => ({
70
+ principle: a.principle.id,
71
+ name: a.principle.name,
72
+ severity: a.violation.severity,
73
+ evidence: a.violation.evidence,
74
+ suggestedAction: a.violation.suggestedAction
75
+ })),
76
+ health: result.health,
77
+ decisions: result.decisions.map((d) => ({
78
+ id: d.id,
79
+ tick: d.tick,
80
+ principle: d.diagnosis.principle.id,
81
+ parameter: d.plan.parameter,
82
+ result: d.result,
83
+ reasoning: d.reasoning
84
+ }))
85
+ });
86
+ return;
87
+ }
88
+ if (path === "/health" && method === "GET") {
89
+ const agentE = server.getAgentE();
90
+ json(res, 200, {
91
+ health: agentE.getHealth(),
92
+ tick: agentE.metrics.latest()?.tick ?? 0,
93
+ mode: agentE.getMode(),
94
+ activePlans: agentE.getActivePlans().length,
95
+ uptime: server.getUptime()
96
+ });
97
+ return;
98
+ }
99
+ if (path === "/decisions" && method === "GET") {
100
+ const limit = parseInt(url.searchParams.get("limit") ?? "50", 10);
101
+ const since = url.searchParams.get("since");
102
+ const agentE = server.getAgentE();
103
+ let decisions;
104
+ if (since) {
105
+ decisions = agentE.getDecisions({ since: parseInt(since, 10) });
106
+ } else {
107
+ decisions = agentE.log.latest(limit);
108
+ }
109
+ json(res, 200, {
110
+ decisions: decisions.map((d) => ({
111
+ id: d.id,
112
+ tick: d.tick,
113
+ timestamp: d.timestamp,
114
+ principle: d.diagnosis.principle.id,
115
+ principeName: d.diagnosis.principle.name,
116
+ parameter: d.plan.parameter,
117
+ currentValue: d.plan.currentValue,
118
+ targetValue: d.plan.targetValue,
119
+ result: d.result,
120
+ reasoning: d.reasoning
121
+ }))
122
+ });
123
+ return;
124
+ }
125
+ if (path === "/config" && method === "POST") {
126
+ const body = await readBody(req);
127
+ let parsed;
128
+ try {
129
+ parsed = JSON.parse(body);
130
+ } catch {
131
+ json(res, 400, { error: "Invalid JSON" });
132
+ return;
133
+ }
134
+ const config = parsed;
135
+ if (config["action"] === "lock" && typeof config["param"] === "string") {
136
+ server.lock(config["param"]);
137
+ json(res, 200, { ok: true, action: "lock", param: config["param"] });
138
+ } else if (config["action"] === "unlock" && typeof config["param"] === "string") {
139
+ server.unlock(config["param"]);
140
+ json(res, 200, { ok: true, action: "unlock", param: config["param"] });
141
+ } else if (config["action"] === "constrain" && typeof config["param"] === "string" && typeof config["min"] === "number" && typeof config["max"] === "number") {
142
+ server.constrain(config["param"], { min: config["min"], max: config["max"] });
143
+ json(res, 200, { ok: true, action: "constrain", param: config["param"] });
144
+ } else if (config["action"] === "mode" && (config["mode"] === "autonomous" || config["mode"] === "advisor")) {
145
+ server.setMode(config["mode"]);
146
+ json(res, 200, { ok: true, action: "mode", mode: config["mode"] });
147
+ } else {
148
+ json(res, 400, {
149
+ error: "Invalid config action. Use: lock, unlock, constrain, or mode"
150
+ });
151
+ }
152
+ return;
153
+ }
154
+ if (path === "/principles" && method === "GET") {
155
+ const principles = server.getAgentE().getPrinciples();
156
+ json(res, 200, {
157
+ count: principles.length,
158
+ principles: principles.map((p) => ({
159
+ id: p.id,
160
+ name: p.name,
161
+ category: p.category,
162
+ description: p.description
163
+ }))
164
+ });
165
+ return;
166
+ }
167
+ if (path === "/diagnose" && method === "POST") {
168
+ const body = await readBody(req);
169
+ let parsed;
170
+ try {
171
+ parsed = JSON.parse(body);
172
+ } catch {
173
+ json(res, 400, { error: "Invalid JSON" });
174
+ return;
175
+ }
176
+ const payload = parsed;
177
+ const state = payload["state"] ?? parsed;
178
+ const validation = validateEconomyState(state);
179
+ if (!validation.valid) {
180
+ json(res, 400, { error: "Invalid state", validation });
181
+ return;
182
+ }
183
+ const result = server.diagnoseOnly(state);
184
+ json(res, 200, {
185
+ health: result.health,
186
+ diagnoses: result.diagnoses.map((d) => ({
187
+ principle: d.principle.id,
188
+ name: d.principle.name,
189
+ severity: d.violation.severity,
190
+ evidence: d.violation.evidence,
191
+ suggestedAction: d.violation.suggestedAction
192
+ }))
193
+ });
194
+ return;
195
+ }
196
+ json(res, 404, { error: "Not found" });
197
+ } catch (err) {
198
+ json(res, 500, { error: "Internal server error" });
199
+ }
200
+ };
201
+ }
202
+
203
+ // src/websocket.ts
204
+ import { WebSocketServer, WebSocket } from "ws";
205
+ import { validateEconomyState as validateEconomyState2 } from "@agent-e/core";
206
+ function send(ws, data) {
207
+ if (ws.readyState === WebSocket.OPEN) {
208
+ ws.send(JSON.stringify(data));
209
+ }
210
+ }
211
+ function createWebSocketHandler(httpServer, server) {
212
+ const wss = new WebSocketServer({ server: httpServer });
213
+ const heartbeatInterval = setInterval(() => {
214
+ for (const ws of wss.clients) {
215
+ if (ws.readyState === WebSocket.OPEN) {
216
+ ws.ping();
217
+ }
218
+ }
219
+ }, 3e4);
220
+ wss.on("connection", (ws) => {
221
+ ws.on("message", async (raw) => {
222
+ let msg;
223
+ try {
224
+ msg = JSON.parse(raw.toString());
225
+ } catch {
226
+ send(ws, { type: "error", error: "Invalid JSON" });
227
+ return;
228
+ }
229
+ if (!msg.type || typeof msg.type !== "string") {
230
+ send(ws, { type: "error", error: 'Missing "type" field' });
231
+ return;
232
+ }
233
+ switch (msg.type) {
234
+ case "tick": {
235
+ const state = msg["state"];
236
+ const events = msg["events"];
237
+ const validation = validateEconomyState2(state);
238
+ if (!validation.valid) {
239
+ send(ws, { type: "validation_error", validation });
240
+ for (const w of validation.warnings) {
241
+ send(ws, { type: "validation_warning", warning: w });
242
+ }
243
+ return;
244
+ }
245
+ for (const w of validation.warnings) {
246
+ send(ws, { type: "validation_warning", warning: w });
247
+ }
248
+ try {
249
+ const result = await server.processTick(
250
+ state,
251
+ Array.isArray(events) ? events : void 0
252
+ );
253
+ send(ws, {
254
+ type: "tick_result",
255
+ adjustments: result.adjustments,
256
+ alerts: result.alerts.map((a) => ({
257
+ principle: a.principle.id,
258
+ name: a.principle.name,
259
+ severity: a.violation.severity,
260
+ suggestedAction: a.violation.suggestedAction
261
+ })),
262
+ health: result.health,
263
+ decisions: result.decisions.map((d) => ({
264
+ id: d.id,
265
+ tick: d.tick,
266
+ principle: d.diagnosis.principle.id,
267
+ parameter: d.plan.parameter,
268
+ result: d.result
269
+ }))
270
+ });
271
+ } catch (err) {
272
+ send(ws, { type: "error", error: "Tick processing failed" });
273
+ }
274
+ break;
275
+ }
276
+ case "event": {
277
+ const event = msg["event"];
278
+ if (event) {
279
+ server.getAgentE().ingest(event);
280
+ send(ws, { type: "event_ack" });
281
+ } else {
282
+ send(ws, { type: "error", error: 'Missing "event" field' });
283
+ }
284
+ break;
285
+ }
286
+ case "health": {
287
+ const agentE = server.getAgentE();
288
+ send(ws, {
289
+ type: "health_result",
290
+ health: agentE.getHealth(),
291
+ tick: agentE.metrics.latest()?.tick ?? 0,
292
+ mode: agentE.getMode(),
293
+ activePlans: agentE.getActivePlans().length,
294
+ uptime: server.getUptime()
295
+ });
296
+ break;
297
+ }
298
+ case "diagnose": {
299
+ const state = msg["state"];
300
+ const validation = validateEconomyState2(state);
301
+ if (!validation.valid) {
302
+ send(ws, { type: "validation_error", validation });
303
+ return;
304
+ }
305
+ const result = server.diagnoseOnly(state);
306
+ send(ws, {
307
+ type: "diagnose_result",
308
+ health: result.health,
309
+ diagnoses: result.diagnoses.map((d) => ({
310
+ principle: d.principle.id,
311
+ name: d.principle.name,
312
+ severity: d.violation.severity,
313
+ suggestedAction: d.violation.suggestedAction
314
+ }))
315
+ });
316
+ break;
317
+ }
318
+ default:
319
+ send(ws, { type: "error", error: `Unknown message type: "${msg.type}"` });
320
+ }
321
+ });
322
+ });
323
+ return () => {
324
+ clearInterval(heartbeatInterval);
325
+ wss.close();
326
+ };
327
+ }
328
+
329
+ // src/AgentEServer.ts
330
+ var AgentEServer = class {
331
+ constructor(config = {}) {
332
+ this.lastState = null;
333
+ this.adjustmentQueue = [];
334
+ this.alerts = [];
335
+ this.startedAt = Date.now();
336
+ this.cleanupWs = null;
337
+ this.port = config.port ?? 3e3;
338
+ this.host = config.host ?? "0.0.0.0";
339
+ const adapter = {
340
+ getState: () => {
341
+ if (!this.lastState) {
342
+ return {
343
+ tick: 0,
344
+ roles: [],
345
+ resources: [],
346
+ currencies: ["default"],
347
+ agentBalances: {},
348
+ agentRoles: {},
349
+ agentInventories: {},
350
+ marketPrices: {},
351
+ recentTransactions: []
352
+ };
353
+ }
354
+ return this.lastState;
355
+ },
356
+ setParam: (key, value) => {
357
+ this.adjustmentQueue.push({ key, value });
358
+ }
359
+ };
360
+ const agentEConfig = {
361
+ adapter,
362
+ mode: config.agentEConfig?.mode ?? "autonomous",
363
+ gracePeriod: config.agentEConfig?.gracePeriod ?? 0,
364
+ checkInterval: config.agentEConfig?.checkInterval ?? 1,
365
+ ...config.agentEConfig?.dominantRoles ? { dominantRoles: config.agentEConfig.dominantRoles } : {},
366
+ ...config.agentEConfig?.idealDistribution ? { idealDistribution: config.agentEConfig.idealDistribution } : {},
367
+ ...config.agentEConfig?.maxAdjustmentPercent !== void 0 ? { maxAdjustmentPercent: config.agentEConfig.maxAdjustmentPercent } : {},
368
+ ...config.agentEConfig?.cooldownTicks !== void 0 ? { cooldownTicks: config.agentEConfig.cooldownTicks } : {},
369
+ ...config.agentEConfig?.thresholds ? { thresholds: config.agentEConfig.thresholds } : {}
370
+ };
371
+ this.agentE = new AgentE(agentEConfig);
372
+ this.agentE.on("alert", (diagnosis) => {
373
+ this.alerts.push(diagnosis);
374
+ });
375
+ this.agentE.connect(adapter).start();
376
+ const routeHandler = createRouteHandler(this);
377
+ this.server = http.createServer(routeHandler);
378
+ }
379
+ async start() {
380
+ this.cleanupWs = createWebSocketHandler(this.server, this);
381
+ return new Promise((resolve) => {
382
+ this.server.listen(this.port, this.host, () => {
383
+ resolve();
384
+ });
385
+ });
386
+ }
387
+ async stop() {
388
+ this.agentE.stop();
389
+ if (this.cleanupWs) this.cleanupWs();
390
+ return new Promise((resolve, reject) => {
391
+ this.server.close((err) => {
392
+ if (err) reject(err);
393
+ else resolve();
394
+ });
395
+ });
396
+ }
397
+ getAgentE() {
398
+ return this.agentE;
399
+ }
400
+ getAddress() {
401
+ const addr = this.server.address();
402
+ if (addr && typeof addr === "object") {
403
+ return { port: addr.port, host: addr.address };
404
+ }
405
+ return { port: this.port, host: this.host };
406
+ }
407
+ getUptime() {
408
+ return Date.now() - this.startedAt;
409
+ }
410
+ /**
411
+ * Process a tick with the given state.
412
+ * 1. Clear adjustment queue
413
+ * 2. Set state
414
+ * 3. Ingest events
415
+ * 4. Run agentE.tick(state)
416
+ * 5. Drain adjustment queue
417
+ * 6. Return response
418
+ */
419
+ async processTick(state, events) {
420
+ this.adjustmentQueue = [];
421
+ this.alerts = [];
422
+ this.lastState = state;
423
+ if (events) {
424
+ for (const event of events) {
425
+ this.agentE.ingest(event);
426
+ }
427
+ }
428
+ await this.agentE.tick(state);
429
+ const adjustments = [...this.adjustmentQueue];
430
+ this.adjustmentQueue = [];
431
+ const decisions = this.agentE.getDecisions({ since: state.tick, until: state.tick });
432
+ return {
433
+ adjustments,
434
+ alerts: [...this.alerts],
435
+ health: this.agentE.getHealth(),
436
+ decisions
437
+ };
438
+ }
439
+ /**
440
+ * Run Observer + Diagnoser without side effects (no execution)
441
+ */
442
+ diagnoseOnly(state) {
443
+ const prevState = this.lastState;
444
+ this.lastState = state;
445
+ const diagnoser = this.agentE;
446
+ const diagnoses = diagnoser.diagnoseNow();
447
+ const health = diagnoser.getHealth();
448
+ this.lastState = prevState;
449
+ return { diagnoses, health };
450
+ }
451
+ setMode(mode) {
452
+ this.agentE.setMode(mode);
453
+ }
454
+ lock(param) {
455
+ this.agentE.lock(param);
456
+ }
457
+ unlock(param) {
458
+ this.agentE.unlock(param);
459
+ }
460
+ constrain(param, bounds) {
461
+ this.agentE.constrain(param, bounds);
462
+ }
463
+ };
464
+
465
+ export {
466
+ AgentEServer
467
+ };
468
+ //# sourceMappingURL=chunk-VNNX52KX.mjs.map