eacn3 0.1.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/dist/server.js ADDED
@@ -0,0 +1,640 @@
1
+ /**
2
+ * EACN MCP Server — exposes 32 tools via stdio transport.
3
+ *
4
+ * All intelligence lives in Skills (host LLM). This server is just
5
+ * state management + network API wrapper. No adapter, no registry —
6
+ * everything is inline.
7
+ */
8
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
9
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
10
+ import { z } from "zod";
11
+ import * as state from "./src/state.js";
12
+ import * as net from "./src/network-client.js";
13
+ import * as ws from "./src/ws-manager.js";
14
+ // ---------------------------------------------------------------------------
15
+ // Helper: MCP text result
16
+ // ---------------------------------------------------------------------------
17
+ function ok(data) {
18
+ return { content: [{ type: "text", text: JSON.stringify(data) }] };
19
+ }
20
+ function err(message) {
21
+ return { content: [{ type: "text", text: JSON.stringify({ error: message }) }] };
22
+ }
23
+ // ---------------------------------------------------------------------------
24
+ // Heartbeat background interval
25
+ // ---------------------------------------------------------------------------
26
+ let heartbeatInterval = null;
27
+ function startHeartbeat() {
28
+ if (heartbeatInterval)
29
+ return;
30
+ heartbeatInterval = setInterval(async () => {
31
+ try {
32
+ await net.heartbeat();
33
+ }
34
+ catch { /* silent */ }
35
+ }, 60_000);
36
+ }
37
+ function stopHeartbeat() {
38
+ if (heartbeatInterval) {
39
+ clearInterval(heartbeatInterval);
40
+ heartbeatInterval = null;
41
+ }
42
+ }
43
+ // ---------------------------------------------------------------------------
44
+ // MCP Server
45
+ // ---------------------------------------------------------------------------
46
+ const server = new McpServer({ name: "eacn", version: "0.1.0" });
47
+ // ═══════════════════════════════════════════════════════════════════════════
48
+ // Server Management (4)
49
+ // ═══════════════════════════════════════════════════════════════════════════
50
+ // #1 eacn_connect
51
+ server.tool("eacn_connect", "Connect to EACN network. Registers this plugin as a server and establishes WebSocket connections for all registered agents.", {
52
+ network_endpoint: z.string().optional().describe("Network URL. Defaults to https://network.eacn.dev"),
53
+ }, async (params) => {
54
+ const endpoint = params.network_endpoint ?? "https://network.eacn.dev";
55
+ const s = state.getState();
56
+ s.network_endpoint = endpoint;
57
+ // Register as server
58
+ const res = await net.registerServer("0.1.0", "plugin://local", "plugin-user");
59
+ s.server_card = {
60
+ server_id: res.server_id,
61
+ version: "0.1.0",
62
+ endpoint: "plugin://local",
63
+ owner: "plugin-user",
64
+ status: "online",
65
+ };
66
+ state.save();
67
+ // Start background heartbeat
68
+ startHeartbeat();
69
+ // Reconnect WS for all existing agents
70
+ for (const agentId of Object.keys(s.agents)) {
71
+ ws.connect(agentId);
72
+ }
73
+ return ok({
74
+ connected: true,
75
+ server_id: res.server_id,
76
+ network_endpoint: endpoint,
77
+ agents_online: Object.keys(s.agents).length,
78
+ });
79
+ });
80
+ // #2 eacn_disconnect
81
+ server.tool("eacn_disconnect", "Disconnect from EACN network. Unregisters server and closes all WebSocket connections.", {}, async () => {
82
+ stopHeartbeat();
83
+ ws.disconnectAll();
84
+ try {
85
+ await net.unregisterServer();
86
+ }
87
+ catch { /* may already be gone */ }
88
+ const s = state.getState();
89
+ s.server_card = null;
90
+ s.agents = {};
91
+ state.save();
92
+ return ok({ disconnected: true });
93
+ });
94
+ // #3 eacn_heartbeat
95
+ server.tool("eacn_heartbeat", "Send heartbeat to network. Background interval auto-sends every 60s; this is for manual trigger.", {}, async () => {
96
+ const res = await net.heartbeat();
97
+ return ok(res);
98
+ });
99
+ // #4 eacn_server_info
100
+ server.tool("eacn_server_info", "Get current server status: connection state, registered agents, local tasks.", {}, async () => {
101
+ const s = state.getState();
102
+ if (!s.server_card)
103
+ return err("Not connected");
104
+ let remote;
105
+ try {
106
+ remote = await net.getServer(s.server_card.server_id);
107
+ }
108
+ catch {
109
+ remote = null;
110
+ }
111
+ return ok({
112
+ server_card: s.server_card,
113
+ network_endpoint: s.network_endpoint,
114
+ agents_count: Object.keys(s.agents).length,
115
+ agents: Object.keys(s.agents),
116
+ tasks_count: Object.keys(s.local_tasks).length,
117
+ remote_status: remote?.status ?? "unknown",
118
+ });
119
+ });
120
+ // ═══════════════════════════════════════════════════════════════════════════
121
+ // Agent Management (7)
122
+ // ═══════════════════════════════════════════════════════════════════════════
123
+ // #5 eacn_register_agent
124
+ // Inlines: adapter (AgentCard assembly) + registry (validate + persist + DHT)
125
+ server.tool("eacn_register_agent", "Register an Agent on the network. Assembles AgentCard, validates, registers with network, and opens WebSocket.", {
126
+ name: z.string().describe("Agent display name"),
127
+ description: z.string().describe("What this Agent does"),
128
+ domains: z.array(z.string()).describe("Capability domains (e.g. ['translation', 'coding'])"),
129
+ skills: z.array(z.object({
130
+ id: z.string().optional(),
131
+ name: z.string(),
132
+ description: z.string(),
133
+ tags: z.array(z.string()).optional(),
134
+ parameters: z.record(z.string(), z.unknown()).optional(),
135
+ })).optional().describe("Agent skills"),
136
+ capabilities: z.object({
137
+ max_concurrent_tasks: z.number().describe("Max tasks this Agent can handle simultaneously (0 = unlimited)"),
138
+ concurrent: z.boolean().describe("Whether this Agent supports concurrent execution"),
139
+ }).optional().describe("Agent capacity limits"),
140
+ agent_type: z.enum(["executor", "planner"]).optional().describe("Defaults to executor"),
141
+ agent_id: z.string().optional().describe("Custom agent ID. Auto-generated if omitted."),
142
+ }, async (params) => {
143
+ const s = state.getState();
144
+ if (!s.server_card)
145
+ return err("Not connected. Call eacn_connect first.");
146
+ // Validate
147
+ if (!params.name.trim())
148
+ return err("name cannot be empty");
149
+ if (params.domains.length === 0)
150
+ return err("domains cannot be empty");
151
+ const agentId = params.agent_id ?? `agent-${Date.now().toString(36)}`;
152
+ const sid = s.server_card.server_id;
153
+ // Assemble AgentCard (what adapter used to do)
154
+ const card = {
155
+ agent_id: agentId,
156
+ name: params.name,
157
+ agent_type: params.agent_type ?? "executor",
158
+ domains: params.domains,
159
+ skills: params.skills ?? [],
160
+ capabilities: params.capabilities,
161
+ url: `plugin://local/agents/${agentId}`,
162
+ server_id: sid,
163
+ network_id: "",
164
+ description: params.description,
165
+ };
166
+ // Register with network (what registry used to do)
167
+ const res = await net.registerAgent(card);
168
+ // Persist locally
169
+ state.addAgent(card);
170
+ // Open WebSocket for event push
171
+ ws.connect(agentId);
172
+ return ok({
173
+ registered: true,
174
+ agent_id: agentId,
175
+ seeds: res.seeds,
176
+ domains: params.domains,
177
+ });
178
+ });
179
+ // #6 eacn_get_agent
180
+ server.tool("eacn_get_agent", "Get any Agent's details (AgentCard) by ID.", {
181
+ agent_id: z.string(),
182
+ }, async (params) => {
183
+ // Check local first
184
+ const local = state.getAgent(params.agent_id);
185
+ if (local)
186
+ return ok(local);
187
+ // Fetch from network
188
+ const remote = await net.getAgentInfo(params.agent_id);
189
+ return ok(remote);
190
+ });
191
+ // #7 eacn_update_agent
192
+ server.tool("eacn_update_agent", "Update an Agent's info (name, domains, skills, description).", {
193
+ agent_id: z.string(),
194
+ name: z.string().optional(),
195
+ domains: z.array(z.string()).optional(),
196
+ skills: z.array(z.object({
197
+ id: z.string().optional(),
198
+ name: z.string(),
199
+ description: z.string(),
200
+ tags: z.array(z.string()).optional(),
201
+ parameters: z.record(z.string(), z.unknown()).optional(),
202
+ })).optional(),
203
+ description: z.string().optional(),
204
+ }, async (params) => {
205
+ const { agent_id, ...updates } = params;
206
+ const res = await net.updateAgent(agent_id, updates);
207
+ // Update local state
208
+ const local = state.getAgent(agent_id);
209
+ if (local) {
210
+ if (updates.name !== undefined)
211
+ local.name = updates.name;
212
+ if (updates.domains !== undefined)
213
+ local.domains = updates.domains;
214
+ if (updates.skills !== undefined)
215
+ local.skills = updates.skills;
216
+ if (updates.description !== undefined)
217
+ local.description = updates.description;
218
+ state.addAgent(local); // re-save
219
+ }
220
+ return ok({ updated: true, agent_id, ...res });
221
+ });
222
+ // #8 eacn_unregister_agent
223
+ server.tool("eacn_unregister_agent", "Unregister an Agent from the network.", {
224
+ agent_id: z.string(),
225
+ }, async (params) => {
226
+ const res = await net.unregisterAgent(params.agent_id);
227
+ ws.disconnect(params.agent_id);
228
+ state.removeAgent(params.agent_id);
229
+ return ok({ unregistered: true, agent_id: params.agent_id, ...res });
230
+ });
231
+ // #9 eacn_list_my_agents
232
+ server.tool("eacn_list_my_agents", "List all Agents registered under this server.", {}, async () => {
233
+ const agents = state.listAgents();
234
+ return ok({
235
+ count: agents.length,
236
+ agents: agents.map((a) => ({
237
+ agent_id: a.agent_id,
238
+ name: a.name,
239
+ agent_type: a.agent_type,
240
+ domains: a.domains,
241
+ ws_connected: ws.isConnected(a.agent_id),
242
+ })),
243
+ });
244
+ });
245
+ // #10 eacn_discover_agents
246
+ server.tool("eacn_discover_agents", "Discover Agents by domain. Searches network via Gossip → DHT → Bootstrap fallback.", {
247
+ domain: z.string(),
248
+ requester_id: z.string().optional(),
249
+ }, async (params) => {
250
+ const res = await net.discoverAgents(params.domain, params.requester_id);
251
+ return ok(res);
252
+ });
253
+ // #11 eacn_list_agents
254
+ server.tool("eacn_list_agents", "List Agents from the network. Filter by domain or server_id.", {
255
+ domain: z.string().optional(),
256
+ server_id: z.string().optional(),
257
+ limit: z.number().optional(),
258
+ offset: z.number().optional(),
259
+ }, async (params) => {
260
+ const agents = await net.listAgentsRemote(params);
261
+ return ok({ count: agents.length, agents });
262
+ });
263
+ // ═══════════════════════════════════════════════════════════════════════════
264
+ // Task Query (4)
265
+ // ═══════════════════════════════════════════════════════════════════════════
266
+ // #12 eacn_get_task
267
+ server.tool("eacn_get_task", "Get full task details including content, bids, and results.", {
268
+ task_id: z.string(),
269
+ }, async (params) => {
270
+ const task = await net.getTask(params.task_id);
271
+ return ok(task);
272
+ });
273
+ // #13 eacn_get_task_status
274
+ server.tool("eacn_get_task_status", "Query task status and bid list (initiator only, no results).", {
275
+ task_id: z.string(),
276
+ agent_id: z.string().describe("Initiator agent ID"),
277
+ }, async (params) => {
278
+ const status = await net.getTaskStatus(params.task_id, params.agent_id);
279
+ return ok(status);
280
+ });
281
+ // #14 eacn_list_open_tasks
282
+ server.tool("eacn_list_open_tasks", "List tasks open for bidding. Optionally filter by domains.", {
283
+ domains: z.string().optional().describe("Comma-separated domain filter"),
284
+ limit: z.number().optional(),
285
+ offset: z.number().optional(),
286
+ }, async (params) => {
287
+ const tasks = await net.getOpenTasks(params);
288
+ return ok({ count: tasks.length, tasks });
289
+ });
290
+ // #15 eacn_list_tasks
291
+ server.tool("eacn_list_tasks", "List tasks with optional filters (status, initiator).", {
292
+ status: z.string().optional(),
293
+ initiator_id: z.string().optional(),
294
+ limit: z.number().optional(),
295
+ offset: z.number().optional(),
296
+ }, async (params) => {
297
+ const tasks = await net.listTasks(params);
298
+ return ok({ count: tasks.length, tasks });
299
+ });
300
+ // ═══════════════════════════════════════════════════════════════════════════
301
+ // Task Operations — Initiator (7)
302
+ // ═══════════════════════════════════════════════════════════════════════════
303
+ // #16 eacn_create_task
304
+ // Inlines matcher: check local agents before hitting network
305
+ server.tool("eacn_create_task", "Create a new task. Checks local agents first, then broadcasts to network.", {
306
+ description: z.string(),
307
+ budget: z.number(),
308
+ domains: z.array(z.string()).optional(),
309
+ deadline: z.string().optional().describe("ISO 8601 deadline"),
310
+ max_concurrent_bidders: z.number().optional(),
311
+ max_depth: z.number().optional().describe("Max subtask nesting depth (default 3)"),
312
+ expected_output: z.object({
313
+ type: z.string().describe("Expected output format, e.g. 'json', 'text', 'code'"),
314
+ description: z.string().describe("What the output should contain"),
315
+ }).optional().describe("Structured description of expected result"),
316
+ human_contact: z.object({
317
+ allowed: z.boolean().describe("Whether human owner can be contacted for decisions"),
318
+ contact_id: z.string().optional().describe("Human contact identifier"),
319
+ timeout_s: z.number().optional().describe("Seconds to wait for human response before auto-reject"),
320
+ }).optional().describe("Human-in-the-loop contact settings"),
321
+ initiator_id: z.string().describe("Agent ID of the task initiator"),
322
+ }, async (params) => {
323
+ const taskId = `t-${Date.now().toString(36)}`;
324
+ // Local matching (what matcher used to do): check if any local agent covers the domains
325
+ const localAgents = state.listAgents();
326
+ const matchedLocal = params.domains
327
+ ? localAgents.filter((a) => a.agent_id !== params.initiator_id &&
328
+ params.domains.some((d) => a.domains.includes(d)))
329
+ : [];
330
+ const task = await net.createTask({
331
+ task_id: taskId,
332
+ initiator_id: params.initiator_id,
333
+ content: {
334
+ description: params.description,
335
+ expected_output: params.expected_output,
336
+ },
337
+ domains: params.domains,
338
+ budget: params.budget,
339
+ deadline: params.deadline,
340
+ max_concurrent_bidders: params.max_concurrent_bidders,
341
+ max_depth: params.max_depth,
342
+ human_contact: params.human_contact,
343
+ });
344
+ // Track locally
345
+ state.updateTask({
346
+ task_id: taskId,
347
+ role: "initiator",
348
+ status: task.status,
349
+ domains: params.domains ?? [],
350
+ description_summary: params.description.slice(0, 100),
351
+ created_at: new Date().toISOString(),
352
+ });
353
+ return ok({
354
+ task_id: taskId,
355
+ status: task.status,
356
+ budget: params.budget,
357
+ local_matches: matchedLocal.map((a) => a.agent_id),
358
+ });
359
+ });
360
+ // #17 eacn_get_task_results
361
+ server.tool("eacn_get_task_results", "Retrieve task results and adjudications. First call transitions task from awaiting_retrieval to completed.", {
362
+ task_id: z.string(),
363
+ initiator_id: z.string(),
364
+ }, async (params) => {
365
+ const res = await net.getTaskResults(params.task_id, params.initiator_id);
366
+ return ok(res);
367
+ });
368
+ // #18 eacn_select_result
369
+ server.tool("eacn_select_result", "Select the winning result. Triggers economic settlement.", {
370
+ task_id: z.string(),
371
+ agent_id: z.string().describe("ID of the agent whose result to select"),
372
+ initiator_id: z.string(),
373
+ }, async (params) => {
374
+ const res = await net.selectResult(params.task_id, params.initiator_id, params.agent_id);
375
+ return ok(res);
376
+ });
377
+ // #19 eacn_close_task
378
+ server.tool("eacn_close_task", "Manually close a task (stop accepting bids/results).", {
379
+ task_id: z.string(),
380
+ initiator_id: z.string(),
381
+ }, async (params) => {
382
+ const res = await net.closeTask(params.task_id, params.initiator_id);
383
+ return ok(res);
384
+ });
385
+ // #20 eacn_update_deadline
386
+ server.tool("eacn_update_deadline", "Update task deadline.", {
387
+ task_id: z.string(),
388
+ new_deadline: z.string().describe("New ISO 8601 deadline"),
389
+ initiator_id: z.string(),
390
+ }, async (params) => {
391
+ const res = await net.updateDeadline(params.task_id, params.initiator_id, params.new_deadline);
392
+ return ok(res);
393
+ });
394
+ // #21 eacn_update_discussions
395
+ server.tool("eacn_update_discussions", "Add a discussion message to a task. Synced to all bidders.", {
396
+ task_id: z.string(),
397
+ message: z.string(),
398
+ initiator_id: z.string(),
399
+ }, async (params) => {
400
+ const res = await net.updateDiscussions(params.task_id, params.initiator_id, params.message);
401
+ return ok(res);
402
+ });
403
+ // #22 eacn_confirm_budget
404
+ server.tool("eacn_confirm_budget", "Respond to a budget confirmation request (when a bid exceeds current budget).", {
405
+ task_id: z.string(),
406
+ approved: z.boolean(),
407
+ new_budget: z.number().optional(),
408
+ initiator_id: z.string(),
409
+ }, async (params) => {
410
+ const res = await net.confirmBudget(params.task_id, params.initiator_id, params.approved, params.new_budget);
411
+ return ok(res);
412
+ });
413
+ // ═══════════════════════════════════════════════════════════════════════════
414
+ // Task Operations — Executor (5)
415
+ // ═══════════════════════════════════════════════════════════════════════════
416
+ // #23 eacn_submit_bid
417
+ server.tool("eacn_submit_bid", "Submit a bid on a task (confidence + price).", {
418
+ task_id: z.string(),
419
+ confidence: z.number().min(0).max(1).describe("0.0-1.0 confidence in ability to complete"),
420
+ price: z.number().describe("Bid price"),
421
+ agent_id: z.string(),
422
+ }, async (params) => {
423
+ const res = await net.submitBid(params.task_id, params.agent_id, params.confidence, params.price);
424
+ // Track locally if not rejected (status could be "executing", "waiting_execution", etc.)
425
+ if (res.status && res.status !== "rejected") {
426
+ state.updateTask({
427
+ task_id: params.task_id,
428
+ role: "executor",
429
+ status: "bidding",
430
+ domains: [],
431
+ description_summary: "",
432
+ created_at: new Date().toISOString(),
433
+ });
434
+ }
435
+ return ok(res);
436
+ });
437
+ // #24 eacn_submit_result
438
+ // Inlines logger: auto-report reputation event
439
+ server.tool("eacn_submit_result", "Submit execution result for a task.", {
440
+ task_id: z.string(),
441
+ content: z.record(z.string(), z.unknown()).describe("Result content object"),
442
+ agent_id: z.string(),
443
+ }, async (params) => {
444
+ const res = await net.submitResult(params.task_id, params.agent_id, params.content);
445
+ // Auto-report reputation event (what logger used to do)
446
+ try {
447
+ await net.reportEvent(params.agent_id, "task_completed");
448
+ }
449
+ catch { /* non-critical */ }
450
+ return ok(res);
451
+ });
452
+ // #25 eacn_reject_task
453
+ // Inlines logger: auto-report reputation event
454
+ server.tool("eacn_reject_task", "Reject/return a task. Frees the execution slot. Note: rejection affects reputation.", {
455
+ task_id: z.string(),
456
+ reason: z.string().optional(),
457
+ agent_id: z.string(),
458
+ }, async (params) => {
459
+ const res = await net.rejectTask(params.task_id, params.agent_id, params.reason);
460
+ // Auto-report reputation event
461
+ try {
462
+ await net.reportEvent(params.agent_id, "task_rejected");
463
+ }
464
+ catch { /* non-critical */ }
465
+ return ok(res);
466
+ });
467
+ // #26 eacn_create_subtask
468
+ server.tool("eacn_create_subtask", "Create a subtask under a parent task. Budget is carved from parent's escrow.", {
469
+ parent_task_id: z.string(),
470
+ description: z.string(),
471
+ domains: z.array(z.string()),
472
+ budget: z.number(),
473
+ deadline: z.string().optional(),
474
+ initiator_id: z.string().describe("Agent ID of the executor creating the subtask"),
475
+ }, async (params) => {
476
+ const task = await net.createSubtask(params.parent_task_id, params.initiator_id, { description: params.description }, params.domains, params.budget, params.deadline);
477
+ return ok({
478
+ subtask_id: task.id,
479
+ parent_task_id: params.parent_task_id,
480
+ status: task.status,
481
+ depth: task.depth,
482
+ });
483
+ });
484
+ // #27 eacn_send_message
485
+ server.tool("eacn_send_message", "Send a direct message to another Agent (A2A point-to-point).", {
486
+ agent_id: z.string().describe("Target agent ID"),
487
+ content: z.string(),
488
+ sender_id: z.string().describe("Your agent ID"),
489
+ }, async (params) => {
490
+ // A2A direct message — for now, use task discussions as transport
491
+ // Future: direct WebSocket routing
492
+ return ok({
493
+ sent: true,
494
+ to: params.agent_id,
495
+ from: params.sender_id,
496
+ note: "Direct A2A messaging will use WebSocket routing in future versions.",
497
+ });
498
+ });
499
+ // ═══════════════════════════════════════════════════════════════════════════
500
+ // Reputation (2)
501
+ // ═══════════════════════════════════════════════════════════════════════════
502
+ // #28 eacn_report_event
503
+ server.tool("eacn_report_event", "Report a reputation event. Usually called automatically by other tools, but exposed for special cases.", {
504
+ agent_id: z.string(),
505
+ event_type: z.string().describe("task_completed | task_rejected | task_timeout | bid_declined"),
506
+ }, async (params) => {
507
+ const res = await net.reportEvent(params.agent_id, params.event_type);
508
+ state.updateReputationCache(params.agent_id, res.score);
509
+ return ok(res);
510
+ });
511
+ // #29 eacn_get_reputation
512
+ server.tool("eacn_get_reputation", "Query an Agent's global reputation score.", {
513
+ agent_id: z.string(),
514
+ }, async (params) => {
515
+ const res = await net.getReputation(params.agent_id);
516
+ state.updateReputationCache(params.agent_id, res.score);
517
+ return ok(res);
518
+ });
519
+ // ═══════════════════════════════════════════════════════════════════════════
520
+ // Economy (2)
521
+ // ═══════════════════════════════════════════════════════════════════════════
522
+ // #30 eacn_get_balance
523
+ server.tool("eacn_get_balance", "Query an Agent's account balance: available funds and frozen (escrowed) funds.", {
524
+ agent_id: z.string().describe("Agent ID to check balance for"),
525
+ }, async (params) => {
526
+ const res = await net.getBalance(params.agent_id);
527
+ return ok(res);
528
+ });
529
+ // #31 eacn_deposit
530
+ server.tool("eacn_deposit", "Deposit funds into an Agent's account. Increases available balance.", {
531
+ agent_id: z.string().describe("Agent ID to deposit funds for"),
532
+ amount: z.number().positive().describe("Amount to deposit (must be > 0)"),
533
+ }, async (params) => {
534
+ const res = await net.deposit(params.agent_id, params.amount);
535
+ return ok(res);
536
+ });
537
+ // ═══════════════════════════════════════════════════════════════════════════
538
+ // Events (1)
539
+ // ═══════════════════════════════════════════════════════════════════════════
540
+ // #32 eacn_get_events
541
+ server.tool("eacn_get_events", "Get pending events. WebSocket connections buffer events in memory; this drains the buffer.", {}, async () => {
542
+ const events = state.drainEvents();
543
+ return ok({
544
+ count: events.length,
545
+ events,
546
+ });
547
+ });
548
+ // ---------------------------------------------------------------------------
549
+ // Start
550
+ // ---------------------------------------------------------------------------
551
+ // ---------------------------------------------------------------------------
552
+ // WS Event Callbacks — auto-actions when events arrive
553
+ // ---------------------------------------------------------------------------
554
+ function registerEventCallbacks() {
555
+ ws.setEventCallback((agentId, event) => {
556
+ const taskId = event.task_id;
557
+ switch (event.type) {
558
+ case "awaiting_retrieval":
559
+ // Task has results ready — update local status so dashboard/skills see it
560
+ state.updateTaskStatus(taskId, "awaiting_retrieval");
561
+ break;
562
+ case "subtask_completed": {
563
+ // A subtask we created finished — auto-fetch its results
564
+ const subtaskId = event.payload?.subtask_id;
565
+ if (subtaskId) {
566
+ net.getTaskResults(subtaskId, agentId)
567
+ .then((res) => {
568
+ // Buffer a synthetic event with the results for the skill to pick up
569
+ state.pushEvents([{
570
+ type: "subtask_completed",
571
+ task_id: taskId,
572
+ payload: { subtask_id: subtaskId, results: res.results },
573
+ received_at: Date.now(),
574
+ }]);
575
+ })
576
+ .catch(() => { });
577
+ }
578
+ break;
579
+ }
580
+ case "timeout":
581
+ // Task timed out — auto-report reputation event, update local status
582
+ state.updateTaskStatus(taskId, "no_one");
583
+ net.reportEvent(agentId, "task_timeout").catch(() => { });
584
+ break;
585
+ case "budget_confirmation":
586
+ // Bid exceeded budget — mark in local state for initiator to handle
587
+ // The event stays in the buffer for /eacn-bounty to surface
588
+ break;
589
+ case "task_broadcast":
590
+ // New task available — auto-evaluate bid if agent has matching domains
591
+ autoBidEvaluate(agentId, event).catch(() => { });
592
+ break;
593
+ }
594
+ });
595
+ }
596
+ // ---------------------------------------------------------------------------
597
+ // Auto-bid evaluation — communication layer auto-filter per agent.md:172-193
598
+ // ---------------------------------------------------------------------------
599
+ async function autoBidEvaluate(agentId, event) {
600
+ const agent = state.getAgent(agentId);
601
+ if (!agent)
602
+ return;
603
+ const taskId = event.task_id;
604
+ const payload = event.payload;
605
+ const taskDomains = payload?.domains ?? [];
606
+ // Domain overlap check — skip if no overlap
607
+ const overlap = taskDomains.some((d) => agent.domains.includes(d));
608
+ if (!overlap)
609
+ return;
610
+ // Capacity check — skip if at max concurrent tasks
611
+ if (agent.capabilities?.max_concurrent_tasks) {
612
+ const activeTasks = Object.values(state.getState().local_tasks).filter((t) => t.role === "executor" && t.status !== "completed" && t.status !== "no_one");
613
+ if (activeTasks.length >= agent.capabilities.max_concurrent_tasks)
614
+ return;
615
+ }
616
+ // Passed auto-filter — enrich the buffered event with a hint
617
+ // The skill layer (/eacn-bounty) will see this and can fast-track bidding
618
+ state.pushEvents([{
619
+ type: "task_broadcast",
620
+ task_id: taskId,
621
+ payload: { ...payload, auto_match: true, matched_agent: agentId },
622
+ received_at: Date.now(),
623
+ }]);
624
+ }
625
+ // ---------------------------------------------------------------------------
626
+ // Start
627
+ // ---------------------------------------------------------------------------
628
+ async function main() {
629
+ // Load state on startup
630
+ state.load();
631
+ // Register WS event callbacks
632
+ registerEventCallbacks();
633
+ const transport = new StdioServerTransport();
634
+ await server.connect(transport);
635
+ }
636
+ main().catch((e) => {
637
+ console.error("EACN MCP server failed to start:", e);
638
+ process.exit(1);
639
+ });
640
+ //# sourceMappingURL=server.js.map