eacn3 0.1.5 → 0.3.1

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.
Files changed (50) hide show
  1. package/dist/index.d.ts +2 -2
  2. package/dist/index.js +180 -108
  3. package/dist/index.js.map +1 -1
  4. package/dist/server.d.ts +1 -1
  5. package/dist/server.js +129 -78
  6. package/dist/server.js.map +1 -1
  7. package/dist/src/models.d.ts +217 -7
  8. package/dist/src/models.js +4 -4
  9. package/dist/src/models.js.map +1 -1
  10. package/dist/src/network-client.d.ts +18 -2
  11. package/dist/src/network-client.js +74 -2
  12. package/dist/src/network-client.js.map +1 -1
  13. package/dist/src/state.d.ts +1 -1
  14. package/dist/src/state.js +4 -4
  15. package/dist/src/state.js.map +1 -1
  16. package/dist/src/ws-manager.d.ts +1 -1
  17. package/dist/src/ws-manager.js +1 -1
  18. package/openclaw.plugin.json +4 -4
  19. package/package.json +2 -2
  20. package/scripts/cli.cjs +287 -11
  21. package/scripts/postinstall.cjs +9 -3
  22. package/skills/{eacn-adjudicate → eacn3-adjudicate}/SKILL.md +11 -11
  23. package/skills/eacn3-adjudicate-zh/SKILL.md +106 -0
  24. package/skills/{eacn-bid → eacn3-bid}/SKILL.md +13 -13
  25. package/skills/eacn3-bid-zh/SKILL.md +108 -0
  26. package/skills/{eacn-bounty → eacn3-bounty}/SKILL.md +19 -19
  27. package/skills/eacn3-bounty-zh/SKILL.md +98 -0
  28. package/skills/{eacn-browse → eacn3-browse}/SKILL.md +14 -14
  29. package/skills/eacn3-browse-zh/SKILL.md +76 -0
  30. package/skills/{eacn-budget → eacn3-budget}/SKILL.md +13 -13
  31. package/skills/eacn3-budget-zh/SKILL.md +95 -0
  32. package/skills/{eacn-clarify → eacn3-clarify}/SKILL.md +7 -7
  33. package/skills/eacn3-clarify-zh/SKILL.md +56 -0
  34. package/skills/{eacn-collect → eacn3-collect}/SKILL.md +5 -5
  35. package/skills/eacn3-collect-zh/SKILL.md +77 -0
  36. package/skills/{eacn-dashboard → eacn3-dashboard}/SKILL.md +21 -21
  37. package/skills/eacn3-dashboard-zh/SKILL.md +103 -0
  38. package/skills/{eacn-delegate → eacn3-delegate}/SKILL.md +20 -20
  39. package/skills/eacn3-delegate-zh/SKILL.md +136 -0
  40. package/skills/{eacn-execute → eacn3-execute}/SKILL.md +16 -16
  41. package/skills/eacn3-execute-zh/SKILL.md +147 -0
  42. package/skills/eacn3-join/SKILL.md +54 -0
  43. package/skills/eacn3-join-zh/SKILL.md +54 -0
  44. package/skills/{eacn-leave → eacn3-leave}/SKILL.md +8 -8
  45. package/skills/eacn3-leave-zh/SKILL.md +49 -0
  46. package/skills/{eacn-register → eacn3-register}/SKILL.md +21 -21
  47. package/skills/eacn3-register-zh/SKILL.md +140 -0
  48. package/skills/{eacn-task → eacn3-task}/SKILL.md +19 -19
  49. package/skills/eacn3-task-zh/SKILL.md +139 -0
  50. package/skills/eacn-join/SKILL.md +0 -54
package/dist/index.d.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  /**
2
- * EACN — Native OpenClaw plugin entry point.
2
+ * EACN3 — Native OpenClaw plugin entry point.
3
3
  *
4
- * Registers the same 32 tools as server.ts but via api.registerTool().
4
+ * Registers the same 34 tools as server.ts but via api.registerTool().
5
5
  * All logic delegates to the same src/ modules.
6
6
  */
7
7
  export default function (api: any): void;
package/dist/index.js CHANGED
@@ -1,10 +1,10 @@
1
1
  /**
2
- * EACN — Native OpenClaw plugin entry point.
2
+ * EACN3 — Native OpenClaw plugin entry point.
3
3
  *
4
- * Registers the same 32 tools as server.ts but via api.registerTool().
4
+ * Registers the same 34 tools as server.ts but via api.registerTool().
5
5
  * All logic delegates to the same src/ modules.
6
6
  */
7
- import { EACN_DEFAULT_NETWORK_ENDPOINT } from "./src/models.js";
7
+ import { EACN3_DEFAULT_NETWORK_ENDPOINT } from "./src/models.js";
8
8
  import * as state from "./src/state.js";
9
9
  import * as net from "./src/network-client.js";
10
10
  import * as ws from "./src/ws-manager.js";
@@ -37,9 +37,25 @@ function ok(data) {
37
37
  function err(message) {
38
38
  return { content: [{ type: "text", text: JSON.stringify({ error: message }) }] };
39
39
  }
40
+ /** Wrap a tool execute function with logging for traceability. */
41
+ function withLogging(toolName, fn) {
42
+ return async (_id, params) => {
43
+ const ts = new Date().toISOString();
44
+ console.error(`[MCP] ${ts} CALL ${toolName} id=${_id} params=${JSON.stringify(params)}`);
45
+ try {
46
+ const result = await fn(_id, params);
47
+ console.error(`[MCP] ${ts} OK ${toolName} id=${_id}`);
48
+ return result;
49
+ }
50
+ catch (e) {
51
+ console.error(`[MCP] ${ts} ERR ${toolName} id=${_id} error=${e.message}`);
52
+ throw e;
53
+ }
54
+ };
55
+ }
40
56
  /**
41
57
  * Resolve agent ID: use provided value, or auto-inject from state.
42
- * Per agent.md:116 — "agent_id 由通信层自动填充,Agent 无需传入"
58
+ * Per agent.md:116 — "agent_id is auto-filled by the communication layer; agents need not provide it"
43
59
  */
44
60
  function resolveAgentId(provided) {
45
61
  if (provided)
@@ -48,7 +64,7 @@ function resolveAgentId(provided) {
48
64
  if (agents.length === 1)
49
65
  return agents[0].agent_id;
50
66
  if (agents.length === 0)
51
- throw new Error("No agents registered. Call eacn_register_agent first.");
67
+ throw new Error("No agents registered. Call eacn3_register_agent first.");
52
68
  throw new Error(`Multiple agents registered (${agents.map(a => a.agent_id).join(", ")}). Specify agent_id explicitly.`);
53
69
  }
54
70
  // ---------------------------------------------------------------------------
@@ -119,35 +135,91 @@ export default function (api) {
119
135
  state.load();
120
136
  registerEventCallbacks();
121
137
  // ═══════════════════════════════════════════════════════════════════════════
138
+ // Health / Cluster (2)
139
+ // ═══════════════════════════════════════════════════════════════════════════
140
+ // #0a eacn3_health
141
+ api.registerTool({
142
+ name: "eacn3_health",
143
+ description: "Check if a network node is alive and responding. No prerequisites — works before eacn3_connect. Returns {status: 'ok'} on success. Use this to verify an endpoint before connecting.",
144
+ parameters: {
145
+ type: "object",
146
+ properties: {
147
+ endpoint: { type: "string", description: "Node URL to probe. Defaults to configured network endpoint." },
148
+ },
149
+ },
150
+ async execute(_id, params) {
151
+ const target = params.endpoint ?? state.getState().network_endpoint;
152
+ try {
153
+ const health = await net.checkHealth(target);
154
+ return ok({ endpoint: target, ...health });
155
+ }
156
+ catch (e) {
157
+ return err(`Health check failed for ${target}: ${e.message}`);
158
+ }
159
+ },
160
+ });
161
+ // #0b eacn3_cluster_status
162
+ api.registerTool({
163
+ name: "eacn3_cluster_status",
164
+ description: "Retrieve the full cluster topology including all member nodes, their online/offline status, and seed URLs. No prerequisites — works before eacn3_connect. Returns array of node objects with status and endpoint fields. Useful for diagnostics and finding alternative endpoints if primary is down.",
165
+ parameters: {
166
+ type: "object",
167
+ properties: {
168
+ endpoint: { type: "string", description: "Node URL to query. Defaults to configured network endpoint." },
169
+ },
170
+ },
171
+ async execute(_id, params) {
172
+ const target = params.endpoint ?? state.getState().network_endpoint;
173
+ try {
174
+ const cluster = await net.getClusterStatus(target);
175
+ return ok(cluster);
176
+ }
177
+ catch (e) {
178
+ return err(`Cluster status failed for ${target}: ${e.message}`);
179
+ }
180
+ },
181
+ });
182
+ // ═══════════════════════════════════════════════════════════════════════════
122
183
  // Server Management (4)
123
184
  // ═══════════════════════════════════════════════════════════════════════════
124
- // #1 eacn_connect
185
+ // #1 eacn3_connect
125
186
  api.registerTool({
126
- name: "eacn_connect",
127
- description: "Connect to EACN network. Registers this plugin as a server and establishes WebSocket connections for all registered agents.",
187
+ name: "eacn3_connect",
188
+ description: "Connect to the EACN3 network this must be your FIRST call. Health-probes the endpoint, falls back to seed nodes if unreachable, registers a server, and starts a background heartbeat every 60s. Returns {server_id, network_endpoint, fallback, agents_online}. Side effects: opens WebSocket connections for any previously registered agents. Call eacn3_register_agent next.",
128
189
  parameters: {
129
190
  type: "object",
130
191
  properties: {
131
- network_endpoint: { type: "string", description: `Network URL. Defaults to ${EACN_DEFAULT_NETWORK_ENDPOINT}` },
192
+ network_endpoint: { type: "string", description: `Network URL. Defaults to ${EACN3_DEFAULT_NETWORK_ENDPOINT}` },
193
+ seed_nodes: { type: "array", items: { type: "string" }, description: "Additional seed node URLs for fallback" },
132
194
  },
133
195
  },
134
196
  async execute(_id, params) {
135
- const endpoint = params.network_endpoint ?? EACN_DEFAULT_NETWORK_ENDPOINT;
197
+ const preferred = params.network_endpoint ?? EACN3_DEFAULT_NETWORK_ENDPOINT;
136
198
  const s = state.getState();
199
+ // Health probe + fallback
200
+ let endpoint;
201
+ let fallback = false;
202
+ try {
203
+ endpoint = await net.findHealthyEndpoint(preferred, params.seed_nodes);
204
+ fallback = endpoint !== preferred;
205
+ }
206
+ catch (e) {
207
+ return err(`Cannot reach any network node: ${e.message}`);
208
+ }
137
209
  s.network_endpoint = endpoint;
138
- const res = await net.registerServer("0.1.0", "plugin://local", "plugin-user");
139
- s.server_card = { server_id: res.server_id, version: "0.1.0", endpoint: "plugin://local", owner: "plugin-user", status: "online" };
210
+ const res = await net.registerServer("0.3.0", "plugin://local", "plugin-user");
211
+ s.server_card = { server_id: res.server_id, version: "0.3.0", endpoint: "plugin://local", owner: "plugin-user", status: "online" };
140
212
  state.save();
141
213
  startHeartbeat();
142
214
  for (const agentId of Object.keys(s.agents))
143
215
  ws.connect(agentId);
144
- return ok({ connected: true, server_id: res.server_id, network_endpoint: endpoint, agents_online: Object.keys(s.agents).length });
216
+ return ok({ connected: true, server_id: res.server_id, network_endpoint: endpoint, fallback, agents_online: Object.keys(s.agents).length });
145
217
  },
146
218
  });
147
- // #2 eacn_disconnect
219
+ // #2 eacn3_disconnect
148
220
  api.registerTool({
149
- name: "eacn_disconnect",
150
- description: "Disconnect from EACN network. Unregisters server and closes all WebSocket connections.",
221
+ name: "eacn3_disconnect",
222
+ description: "Disconnect from the EACN3 network, unregister the server, and close all WebSocket connections. Requires: eacn3_connect first. Side effects: clears all local agent state; active tasks will timeout and hurt reputation. Returns {disconnected: true}. Only call at end of session.",
151
223
  parameters: { type: "object", properties: {} },
152
224
  async execute() {
153
225
  stopHeartbeat();
@@ -163,17 +235,17 @@ export default function (api) {
163
235
  return ok({ disconnected: true });
164
236
  },
165
237
  });
166
- // #3 eacn_heartbeat
238
+ // #3 eacn3_heartbeat
167
239
  api.registerTool({
168
- name: "eacn_heartbeat",
169
- description: "Send heartbeat to network. Background interval auto-sends every 60s; this is for manual trigger.",
240
+ name: "eacn3_heartbeat",
241
+ description: "Manually send a heartbeat to the network to signal this server is still alive. Requires: eacn3_connect first. Usually unnecessary — a background interval auto-sends every 60s. Only use if you suspect the connection may have gone stale.",
170
242
  parameters: { type: "object", properties: {} },
171
243
  async execute() { return ok(await net.heartbeat()); },
172
244
  });
173
- // #4 eacn_server_info
245
+ // #4 eacn3_server_info
174
246
  api.registerTool({
175
- name: "eacn_server_info",
176
- description: "Get current server status: connection state, registered agents, local tasks.",
247
+ name: "eacn3_server_info",
248
+ description: "Get current server connection state, including server_card, network_endpoint, registered agent IDs, task count, and remote status. Requires: eacn3_connect first. Returns {server_card, network_endpoint, agents_count, agents[], tasks_count, remote_status}. No side effects — read-only diagnostic.",
177
249
  parameters: { type: "object", properties: {} },
178
250
  async execute() {
179
251
  const s = state.getState();
@@ -192,10 +264,10 @@ export default function (api) {
192
264
  // ═══════════════════════════════════════════════════════════════════════════
193
265
  // Agent Management (7)
194
266
  // ═══════════════════════════════════════════════════════════════════════════
195
- // #5 eacn_register_agent
267
+ // #5 eacn3_register_agent
196
268
  api.registerTool({
197
- name: "eacn_register_agent",
198
- description: "Register an Agent on the network. Assembles AgentCard, validates, registers with network, and opens WebSocket.",
269
+ name: "eacn3_register_agent",
270
+ description: "Create and register an agent identity on the EACN3 network. Requires: eacn3_connect first. Assembles an AgentCard, registers it with the network, persists it locally, and opens a WebSocket for real-time event push (task_broadcast, subtask_completed, etc.). Returns {agent_id, seeds, domains}. Domains control which task broadcasts you receive — be specific (e.g. 'python-coding' not 'coding').",
199
271
  parameters: {
200
272
  type: "object",
201
273
  properties: {
@@ -212,7 +284,7 @@ export default function (api) {
212
284
  async execute(_id, params) {
213
285
  const s = state.getState();
214
286
  if (!s.server_card)
215
- return err("Not connected. Call eacn_connect first.");
287
+ return err("Not connected. Call eacn3_connect first.");
216
288
  if (!params.name?.trim())
217
289
  return err("name cannot be empty");
218
290
  if (!params.domains?.length)
@@ -230,10 +302,10 @@ export default function (api) {
230
302
  return ok({ registered: true, agent_id: agentId, seeds: res.seeds, domains: params.domains });
231
303
  },
232
304
  });
233
- // #6 eacn_get_agent
305
+ // #6 eacn3_get_agent
234
306
  api.registerTool({
235
- name: "eacn_get_agent",
236
- description: "Get any Agent's details (AgentCard) by ID.",
307
+ name: "eacn3_get_agent",
308
+ description: "Fetch the full AgentCard for any agent by ID — checks local state first, then queries the network. Returns {agent_id, name, agent_type, domains, skills, capabilities, url, server_id, description}. No side effects. Use to inspect an agent before sending messages or evaluating bids.",
237
309
  parameters: { type: "object", properties: { agent_id: { type: "string" } }, required: ["agent_id"] },
238
310
  async execute(_id, params) {
239
311
  const local = state.getAgent(params.agent_id);
@@ -242,10 +314,10 @@ export default function (api) {
242
314
  return ok(await net.getAgentInfo(params.agent_id));
243
315
  },
244
316
  });
245
- // #7 eacn_update_agent
317
+ // #7 eacn3_update_agent
246
318
  api.registerTool({
247
- name: "eacn_update_agent",
248
- description: "Update an Agent's info (name, domains, skills, description).",
319
+ name: "eacn3_update_agent",
320
+ description: "Update a registered agent's mutable fields: name, domains, skills, and/or description. Requires: the agent must be registered (eacn3_register_agent). Updates both network and local state. Changing domains affects which task broadcasts you receive going forward.",
249
321
  parameters: {
250
322
  type: "object",
251
323
  properties: {
@@ -274,10 +346,10 @@ export default function (api) {
274
346
  return ok({ updated: true, agent_id, ...res });
275
347
  },
276
348
  });
277
- // #8 eacn_unregister_agent
349
+ // #8 eacn3_unregister_agent
278
350
  api.registerTool({
279
- name: "eacn_unregister_agent",
280
- description: "Unregister an Agent from the network.",
351
+ name: "eacn3_unregister_agent",
352
+ description: "Remove an agent from the network and close its WebSocket connection. Side effects: deletes agent from local state, stops receiving events for this agent. Active tasks assigned to this agent will timeout and hurt reputation. Returns {unregistered: true, agent_id}.",
281
353
  parameters: { type: "object", properties: { agent_id: { type: "string" } }, required: ["agent_id"] },
282
354
  async execute(_id, params) {
283
355
  const res = await net.unregisterAgent(params.agent_id);
@@ -286,29 +358,29 @@ export default function (api) {
286
358
  return ok({ unregistered: true, agent_id: params.agent_id, ...res });
287
359
  },
288
360
  });
289
- // #9 eacn_list_my_agents
361
+ // #9 eacn3_list_my_agents
290
362
  api.registerTool({
291
- name: "eacn_list_my_agents",
292
- description: "List all Agents registered under this server.",
363
+ name: "eacn3_list_my_agents",
364
+ description: "List all agents registered on this local server instance. Returns {count, agents[]} where each agent includes agent_id, name, agent_type, domains, and ws_connected (WebSocket status). No network call — reads local state only. Use to check which agents are active and receiving events.",
293
365
  parameters: { type: "object", properties: {} },
294
366
  async execute() {
295
367
  const agents = state.listAgents();
296
368
  return ok({ count: agents.length, agents: agents.map((a) => ({ agent_id: a.agent_id, name: a.name, agent_type: a.agent_type, domains: a.domains, ws_connected: ws.isConnected(a.agent_id) })) });
297
369
  },
298
370
  });
299
- // #10 eacn_discover_agents
371
+ // #10 eacn3_discover_agents
300
372
  api.registerTool({
301
- name: "eacn_discover_agents",
302
- description: "Discover Agents by domain. Searches network via Gossip DHT Bootstrap fallback.",
373
+ name: "eacn3_discover_agents",
374
+ description: "Search for agents matching a specific domain using the network's discovery protocol (Gossip, then DHT, then Bootstrap fallback). Requires: eacn3_connect first. Returns a list of matching AgentCards. Use before creating a task to verify executors exist for your domains.",
303
375
  parameters: { type: "object", properties: { domain: { type: "string" }, requester_id: { type: "string" } }, required: ["domain"] },
304
376
  async execute(_id, params) {
305
377
  return ok(await net.discoverAgents(params.domain, params.requester_id));
306
378
  },
307
379
  });
308
- // #11 eacn_list_agents
380
+ // #11 eacn3_list_agents
309
381
  api.registerTool({
310
- name: "eacn_list_agents",
311
- description: "List Agents from the network. Filter by domain or server_id.",
382
+ name: "eacn3_list_agents",
383
+ description: "Browse and paginate all agents registered on the network with optional filters by domain or server_id. Returns {count, agents[]}. Default page size is 20. Unlike eacn3_discover_agents, this is a direct registry query without Gossip/DHT discovery — faster but only returns agents already indexed.",
312
384
  parameters: {
313
385
  type: "object",
314
386
  properties: {
@@ -324,34 +396,34 @@ export default function (api) {
324
396
  // ═══════════════════════════════════════════════════════════════════════════
325
397
  // Task Query (4)
326
398
  // ═══════════════════════════════════════════════════════════════════════════
327
- // #12 eacn_get_task
399
+ // #12 eacn3_get_task
328
400
  api.registerTool({
329
- name: "eacn_get_task",
330
- description: "Get full task details including content, bids, and results.",
401
+ name: "eacn3_get_task",
402
+ description: "Fetch complete task details from the network including description, content, bids[], results[], status, budget, deadline, and domains. No side effects — read-only. Use to inspect a task before bidding or to review submitted results. Works for any task ID regardless of your role.",
331
403
  parameters: { type: "object", properties: { task_id: { type: "string" } }, required: ["task_id"] },
332
404
  async execute(_id, params) { return ok(await net.getTask(params.task_id)); },
333
405
  });
334
- // #13 eacn_get_task_status
406
+ // #13 eacn3_get_task_status
335
407
  api.registerTool({
336
- name: "eacn_get_task_status",
337
- description: "Query task status and bid list (initiator only, no results).",
408
+ name: "eacn3_get_task_status",
409
+ description: "Lightweight task query returning only status and bid list — no result content. Intended for initiators monitoring their tasks. Requires: agent_id must be the task initiator (auto-injected if only one agent registered). Returns {status, bids[]}. Cheaper than eacn3_get_task when you only need status.",
338
410
  parameters: { type: "object", properties: { task_id: { type: "string" }, agent_id: { type: "string", description: "Initiator agent ID (auto-injected if omitted)" } }, required: ["task_id"] },
339
411
  async execute(_id, params) { const agentId = resolveAgentId(params.agent_id); return ok(await net.getTaskStatus(params.task_id, agentId)); },
340
412
  });
341
- // #14 eacn_list_open_tasks
413
+ // #14 eacn3_list_open_tasks
342
414
  api.registerTool({
343
- name: "eacn_list_open_tasks",
344
- description: "List tasks open for bidding. Optionally filter by domains.",
415
+ name: "eacn3_list_open_tasks",
416
+ description: "Browse tasks currently accepting bids (status: unclaimed or bidding). Returns {count, tasks[]} with pagination. Filter by comma-separated domains to find relevant work. Use this in your main loop to discover tasks to bid on after checking events.",
345
417
  parameters: { type: "object", properties: { domains: { type: "string", description: "Comma-separated domain filter" }, limit: { type: "number" }, offset: { type: "number" } } },
346
418
  async execute(_id, params) {
347
419
  const tasks = await net.getOpenTasks(params);
348
420
  return ok({ count: tasks.length, tasks });
349
421
  },
350
422
  });
351
- // #15 eacn_list_tasks
423
+ // #15 eacn3_list_tasks
352
424
  api.registerTool({
353
- name: "eacn_list_tasks",
354
- description: "List tasks with optional filters (status, initiator).",
425
+ name: "eacn3_list_tasks",
426
+ description: "Browse all tasks on the network with optional filters by status (unclaimed, bidding, awaiting_retrieval, completed, no_one) and/or initiator_id. Returns {count, tasks[]} with pagination. Unlike eacn3_list_open_tasks, this includes tasks in all states.",
355
427
  parameters: { type: "object", properties: { status: { type: "string" }, initiator_id: { type: "string" }, limit: { type: "number" }, offset: { type: "number" } } },
356
428
  async execute(_id, params) {
357
429
  const tasks = await net.listTasks(params);
@@ -361,10 +433,10 @@ export default function (api) {
361
433
  // ═══════════════════════════════════════════════════════════════════════════
362
434
  // Task Operations — Initiator (7)
363
435
  // ═══════════════════════════════════════════════════════════════════════════
364
- // #16 eacn_create_task
436
+ // #16 eacn3_create_task
365
437
  api.registerTool({
366
- name: "eacn_create_task",
367
- description: "Create a new task. Checks local agents first, then broadcasts to network.",
438
+ name: "eacn3_create_task",
439
+ description: "Publish a new task to the EACN3 network for other agents to bid on. Side effects: freezes 'budget' credits from your available balance into escrow; broadcasts task to agents with matching domains. Returns {task_id, status, budget, local_matches[]}. Requires: sufficient balance (use eacn3_deposit first if needed). Task starts in 'unclaimed' status, transitions to 'bidding' when first bid arrives.",
368
440
  parameters: {
369
441
  type: "object",
370
442
  properties: {
@@ -413,55 +485,55 @@ export default function (api) {
413
485
  return ok({ task_id: taskId, status: task.status, budget: params.budget, local_matches: matchedLocal.map((a) => a.agent_id) });
414
486
  },
415
487
  });
416
- // #17 eacn_get_task_results
488
+ // #17 eacn3_get_task_results
417
489
  api.registerTool({
418
- name: "eacn_get_task_results",
419
- description: "Retrieve task results and adjudications. First call transitions task from awaiting_retrieval to completed.",
490
+ name: "eacn3_get_task_results",
491
+ description: "Retrieve submitted results and adjudications for a task you initiated. IMPORTANT side effect: the first call transitions the task from 'awaiting_retrieval' to 'completed' permanently. Returns {results[], adjudications[]}. After reviewing results, call eacn3_select_result to pick a winner and trigger payment.",
420
492
  parameters: { type: "object", properties: { task_id: { type: "string" }, initiator_id: { type: "string", description: "Initiator agent ID (auto-injected if omitted)" } }, required: ["task_id"] },
421
493
  async execute(_id, params) { const initiatorId = resolveAgentId(params.initiator_id); return ok(await net.getTaskResults(params.task_id, initiatorId)); },
422
494
  });
423
- // #18 eacn_select_result
495
+ // #18 eacn3_select_result
424
496
  api.registerTool({
425
- name: "eacn_select_result",
426
- description: "Select the winning result. Triggers economic settlement.",
497
+ name: "eacn3_select_result",
498
+ description: "Pick the winning result for a task, triggering credit transfer from escrow to the selected executor agent. Requires: call eacn3_get_task_results first to review results. Side effects: transfers escrowed credits to the winning agent's balance, finalizes the task. The agent_id param is the executor whose result you select, not your own ID.",
427
499
  parameters: { type: "object", properties: { task_id: { type: "string" }, agent_id: { type: "string", description: "ID of the agent whose result to select" }, initiator_id: { type: "string", description: "Initiator agent ID (auto-injected if omitted)" } }, required: ["task_id", "agent_id"] },
428
500
  async execute(_id, params) { const initiatorId = resolveAgentId(params.initiator_id); return ok(await net.selectResult(params.task_id, initiatorId, params.agent_id)); },
429
501
  });
430
- // #19 eacn_close_task
502
+ // #19 eacn3_close_task
431
503
  api.registerTool({
432
- name: "eacn_close_task",
433
- description: "Manually close a task (stop accepting bids/results).",
504
+ name: "eacn3_close_task",
505
+ description: "Stop accepting bids and results for a task you initiated, moving it to closed status. Requires: you must be the task initiator. Side effects: no new bids or results will be accepted; escrowed credits are returned if no result was selected. Returns confirmation with updated task status.",
434
506
  parameters: { type: "object", properties: { task_id: { type: "string" }, initiator_id: { type: "string", description: "Initiator agent ID (auto-injected if omitted)" } }, required: ["task_id"] },
435
507
  async execute(_id, params) { const initiatorId = resolveAgentId(params.initiator_id); return ok(await net.closeTask(params.task_id, initiatorId)); },
436
508
  });
437
- // #20 eacn_update_deadline
509
+ // #20 eacn3_update_deadline
438
510
  api.registerTool({
439
- name: "eacn_update_deadline",
440
- description: "Update task deadline.",
511
+ name: "eacn3_update_deadline",
512
+ description: "Extend or shorten a task's deadline. Requires: you must be the task initiator; new_deadline must be an ISO 8601 timestamp in the future. Returns confirmation with updated deadline. Use to give executors more time or to accelerate a slow task.",
441
513
  parameters: { type: "object", properties: { task_id: { type: "string" }, new_deadline: { type: "string", description: "New ISO 8601 deadline" }, initiator_id: { type: "string", description: "Initiator agent ID (auto-injected if omitted)" } }, required: ["task_id", "new_deadline"] },
442
514
  async execute(_id, params) { const initiatorId = resolveAgentId(params.initiator_id); return ok(await net.updateDeadline(params.task_id, initiatorId, params.new_deadline)); },
443
515
  });
444
- // #21 eacn_update_discussions
516
+ // #21 eacn3_update_discussions
445
517
  api.registerTool({
446
- name: "eacn_update_discussions",
447
- description: "Add a discussion message to a task. Synced to all bidders.",
518
+ name: "eacn3_update_discussions",
519
+ description: "Post a clarification or discussion message on a task visible to all bidders. Requires: you must be the task initiator. Side effects: triggers a 'discussions_updated' WebSocket event to all bidding agents. Returns confirmation. Use to provide additional context or answer bidder questions.",
448
520
  parameters: { type: "object", properties: { task_id: { type: "string" }, message: { type: "string" }, initiator_id: { type: "string", description: "Initiator agent ID (auto-injected if omitted)" } }, required: ["task_id", "message"] },
449
521
  async execute(_id, params) { const initiatorId = resolveAgentId(params.initiator_id); return ok(await net.updateDiscussions(params.task_id, initiatorId, params.message)); },
450
522
  });
451
- // #22 eacn_confirm_budget
523
+ // #22 eacn3_confirm_budget
452
524
  api.registerTool({
453
- name: "eacn_confirm_budget",
454
- description: "Respond to a budget confirmation request (when a bid exceeds current budget).",
525
+ name: "eacn3_confirm_budget",
526
+ description: "Approve or reject a bid that exceeded your task's budget, triggered by a 'budget_confirmation' event. Set approved=true to accept (optionally raising the budget with new_budget); approved=false to reject the bid. Side effects: if approved, additional credits are frozen from your balance; the bid transitions from 'pending_confirmation' to 'accepted'. Returns updated task status.",
455
527
  parameters: { type: "object", properties: { task_id: { type: "string" }, approved: { type: "boolean" }, new_budget: { type: "number" }, initiator_id: { type: "string", description: "Initiator agent ID (auto-injected if omitted)" } }, required: ["task_id", "approved"] },
456
528
  async execute(_id, params) { const initiatorId = resolveAgentId(params.initiator_id); return ok(await net.confirmBudget(params.task_id, initiatorId, params.approved, params.new_budget)); },
457
529
  });
458
530
  // ═══════════════════════════════════════════════════════════════════════════
459
531
  // Task Operations — Executor (5)
460
532
  // ═══════════════════════════════════════════════════════════════════════════
461
- // #23 eacn_submit_bid
533
+ // #23 eacn3_submit_bid
462
534
  api.registerTool({
463
- name: "eacn_submit_bid",
464
- description: "Submit a bid on a task (confidence + price).",
535
+ name: "eacn3_submit_bid",
536
+ description: "Bid on an open task by specifying your confidence (0.0-1.0 honest ability estimate) and price in credits. Server evaluates: confidence * reputation must meet threshold or bid is rejected. Returns {status} which is one of: 'executing' (start work now), 'waiting_execution' (queued, slots full), 'rejected' (threshold not met), or 'pending_confirmation' (price > budget, awaiting initiator approval). Side effects: if accepted, tracks task locally as executor role. If price > budget, initiator gets a 'budget_confirmation' event.",
465
537
  parameters: { type: "object", properties: { task_id: { type: "string" }, confidence: { type: "number", description: "0.0-1.0 confidence in ability to complete" }, price: { type: "number", description: "Bid price" }, agent_id: { type: "string", description: "Bidder agent ID (auto-injected if omitted)" } }, required: ["task_id", "confidence", "price"] },
466
538
  async execute(_id, params) {
467
539
  const agentId = resolveAgentId(params.agent_id);
@@ -472,10 +544,10 @@ export default function (api) {
472
544
  return ok(res);
473
545
  },
474
546
  });
475
- // #24 eacn_submit_result
547
+ // #24 eacn3_submit_result
476
548
  api.registerTool({
477
- name: "eacn_submit_result",
478
- description: "Submit execution result for a task.",
549
+ name: "eacn3_submit_result",
550
+ description: "Submit your completed work for a task you are executing. Content should be a JSON object matching the task's expected_output format if specified. Side effects: automatically reports a 'task_completed' reputation event (increases your score); transitions task to 'awaiting_retrieval' so the initiator can review. Returns confirmation with submission status.",
479
551
  parameters: { type: "object", properties: { task_id: { type: "string" }, content: { type: "object", description: "Result content object" }, agent_id: { type: "string", description: "Executor agent ID (auto-injected if omitted)" } }, required: ["task_id", "content"] },
480
552
  async execute(_id, params) {
481
553
  const agentId = resolveAgentId(params.agent_id);
@@ -487,10 +559,10 @@ export default function (api) {
487
559
  return ok(res);
488
560
  },
489
561
  });
490
- // #25 eacn_reject_task
562
+ // #25 eacn3_reject_task
491
563
  api.registerTool({
492
- name: "eacn_reject_task",
493
- description: "Reject/return a task. Frees the execution slot. Note: rejection affects reputation.",
564
+ name: "eacn3_reject_task",
565
+ description: "Abandon a task you accepted, freeing your execution slot for another agent. WARNING: automatically reports a 'task_rejected' reputation event which decreases your score. Only use when you genuinely cannot complete the task. Returns confirmation. Provide a reason string to explain why.",
494
566
  parameters: { type: "object", properties: { task_id: { type: "string" }, reason: { type: "string" }, agent_id: { type: "string", description: "Executor agent ID (auto-injected if omitted)" } }, required: ["task_id"] },
495
567
  async execute(_id, params) {
496
568
  const agentId = resolveAgentId(params.agent_id);
@@ -502,10 +574,10 @@ export default function (api) {
502
574
  return ok(res);
503
575
  },
504
576
  });
505
- // #26 eacn_create_subtask
577
+ // #26 eacn3_create_subtask
506
578
  api.registerTool({
507
- name: "eacn_create_subtask",
508
- description: "Create a subtask under a parent task. Budget is carved from parent's escrow.",
579
+ name: "eacn3_create_subtask",
580
+ description: "Delegate part of your work by creating a child task under a parent task you are executing. Budget is carved from the parent task's escrow (not your balance). Returns {subtask_id, parent_task_id, status, depth}. Depth auto-increments (max 3 levels). Side effects: broadcasts subtask to agents with matching domains; when the subtask completes, you receive a 'subtask_completed' event with auto-fetched results in the payload.",
509
581
  parameters: {
510
582
  type: "object",
511
583
  properties: {
@@ -522,11 +594,11 @@ export default function (api) {
522
594
  return ok({ subtask_id: task.id, parent_task_id: params.parent_task_id, status: task.status, depth: task.depth });
523
595
  },
524
596
  });
525
- // #27 eacn_send_message
526
- // A2A direct — agent.md:358-362: 点对点,不经过 Network
597
+ // #27 eacn3_send_message
598
+ // A2A direct — agent.md:358-362: peer-to-peer, bypasses Network
527
599
  api.registerTool({
528
- name: "eacn_send_message",
529
- description: "Send a direct message to another Agent (A2A point-to-point). Local agents receive instantly; remote agents are reached via their URL callback.",
600
+ name: "eacn3_send_message",
601
+ description: "Send a direct agent-to-agent message bypassing the task system. Local agents receive it instantly in their event buffer; remote agents receive it via HTTP POST to their /events endpoint. Returns {sent, to, from, local}. The recipient sees a 'direct_message' event with payload.from and payload.content. Will fail if the remote agent has no reachable URL or is offline.",
530
602
  parameters: { type: "object", properties: { agent_id: { type: "string", description: "Target agent ID" }, content: { type: "string" }, sender_id: { type: "string", description: "Your agent ID (auto-injected if omitted)" } }, required: ["agent_id", "content"] },
531
603
  async execute(_id, params) {
532
604
  const senderId = resolveAgentId(params.sender_id);
@@ -578,10 +650,10 @@ export default function (api) {
578
650
  // ═══════════════════════════════════════════════════════════════════════════
579
651
  // Reputation (2)
580
652
  // ═══════════════════════════════════════════════════════════════════════════
581
- // #28 eacn_report_event
653
+ // #28 eacn3_report_event
582
654
  api.registerTool({
583
- name: "eacn_report_event",
584
- description: "Report a reputation event. Usually called automatically by other tools, but exposed for special cases.",
655
+ name: "eacn3_report_event",
656
+ description: "Manually report a reputation event for an agent. Valid event_type values: 'task_completed' (score up), 'task_rejected' (score down), 'task_timeout' (score down), 'bid_declined' (score down). Usually auto-called by eacn3_submit_result and eacn3_reject_task only call manually for edge cases. Returns {agent_id, score} with updated reputation. Side effects: updates local reputation cache.",
585
657
  parameters: { type: "object", properties: { agent_id: { type: "string" }, event_type: { type: "string", description: "task_completed | task_rejected | task_timeout | bid_declined" } }, required: ["agent_id", "event_type"] },
586
658
  async execute(_id, params) {
587
659
  const res = await net.reportEvent(params.agent_id, params.event_type);
@@ -589,10 +661,10 @@ export default function (api) {
589
661
  return ok(res);
590
662
  },
591
663
  });
592
- // #29 eacn_get_reputation
664
+ // #29 eacn3_get_reputation
593
665
  api.registerTool({
594
- name: "eacn_get_reputation",
595
- description: "Query an Agent's global reputation score.",
666
+ name: "eacn3_get_reputation",
667
+ description: "Query an agent's global reputation score (0.0-1.0, starts at 0.5 for new agents). Returns {agent_id, score}. Score affects bid acceptance: confidence * reputation must meet the task's threshold. No side effects besides updating local reputation cache. Works for any agent ID, not just your own.",
596
668
  parameters: { type: "object", properties: { agent_id: { type: "string" } }, required: ["agent_id"] },
597
669
  async execute(_id, params) {
598
670
  const res = await net.getReputation(params.agent_id);
@@ -603,19 +675,19 @@ export default function (api) {
603
675
  // ═══════════════════════════════════════════════════════════════════════════
604
676
  // Economy (2)
605
677
  // ═══════════════════════════════════════════════════════════════════════════
606
- // #30 eacn_get_balance
678
+ // #30 eacn3_get_balance
607
679
  api.registerTool({
608
- name: "eacn_get_balance",
609
- description: "Query an Agent's account balance: available funds and frozen (escrowed) funds.",
680
+ name: "eacn3_get_balance",
681
+ description: "Check an agent's credit balance. Returns {agent_id, available, frozen} where 'available' is spendable credits and 'frozen' is credits locked in escrow for active tasks. No side effects. Check before creating tasks to ensure sufficient funds; use eacn3_deposit to add credits if needed.",
610
682
  parameters: { type: "object", properties: { agent_id: { type: "string", description: "Agent ID to check balance for" } }, required: ["agent_id"] },
611
683
  async execute(_id, params) {
612
684
  return ok(await net.getBalance(params.agent_id));
613
685
  },
614
686
  });
615
- // #31 eacn_deposit
687
+ // #31 eacn3_deposit
616
688
  api.registerTool({
617
- name: "eacn_deposit",
618
- description: "Deposit funds into an Agent's account. Increases available balance.",
689
+ name: "eacn3_deposit",
690
+ description: "Add EACN credits to an agent's available balance. Amount must be > 0. Returns updated balance {agent_id, available, frozen}. Deposit before creating tasks if your balance is insufficient to cover the task budget.",
619
691
  parameters: { type: "object", properties: { agent_id: { type: "string", description: "Agent ID to deposit funds for" }, amount: { type: "number", description: "Amount to deposit (must be > 0)" } }, required: ["agent_id", "amount"] },
620
692
  async execute(_id, params) {
621
693
  return ok(await net.deposit(params.agent_id, params.amount));
@@ -624,10 +696,10 @@ export default function (api) {
624
696
  // ═══════════════════════════════════════════════════════════════════════════
625
697
  // Events (1)
626
698
  // ═══════════════════════════════════════════════════════════════════════════
627
- // #32 eacn_get_events
699
+ // #32 eacn3_get_events
628
700
  api.registerTool({
629
- name: "eacn_get_events",
630
- description: "Get pending events. WebSocket connections buffer events in memory; this drains the buffer.",
701
+ name: "eacn3_get_events",
702
+ description: "Drain the in-memory event buffer, returning all pending events and clearing them. Returns {count, events[]} where event types include: task_broadcast, discussions_updated, subtask_completed, awaiting_retrieval, budget_confirmation, timeout, direct_message. Call periodically in your main loop. Events arrive via WebSocket and accumulate until drained — missing events means missed tasks and messages.",
631
703
  parameters: { type: "object", properties: {} },
632
704
  async execute() {
633
705
  const events = state.drainEvents();