eacn3 0.3.3 → 0.3.5
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.d.ts +7 -1
- package/dist/index.js +713 -614
- package/dist/index.js.map +1 -1
- package/dist/server.js +75 -27
- package/dist/server.js.map +1 -1
- package/dist/src/models.d.ts +0 -2
- package/dist/src/models.js.map +1 -1
- package/dist/src/state.js +25 -4
- package/dist/src/state.js.map +1 -1
- package/package.json +1 -1
- package/scripts/cli.cjs +12 -11
- package/skills/eacn3-browse/SKILL.md +1 -1
- package/skills/eacn3-register/SKILL.md +3 -15
- package/skills/eacn3-register-zh/SKILL.md +3 -15
package/dist/index.js
CHANGED
|
@@ -8,6 +8,7 @@ import { EACN3_DEFAULT_NETWORK_ENDPOINT, isTierEligible } 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";
|
|
11
|
+
import * as a2a from "./src/a2a-server.js";
|
|
11
12
|
// ---------------------------------------------------------------------------
|
|
12
13
|
// Heartbeat
|
|
13
14
|
// ---------------------------------------------------------------------------
|
|
@@ -136,627 +137,725 @@ async function autoBidEvaluate(agentId, event) {
|
|
|
136
137
|
// ---------------------------------------------------------------------------
|
|
137
138
|
// Plugin entry
|
|
138
139
|
// ---------------------------------------------------------------------------
|
|
139
|
-
export default
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
140
|
+
export default {
|
|
141
|
+
id: "eacn3",
|
|
142
|
+
name: "EACN3 Network Plugin",
|
|
143
|
+
description: "Agent collaboration network — install to go online, uninstall to go offline. Publish tasks, register agents, earn reputation.",
|
|
144
|
+
register(api) {
|
|
145
|
+
// Load state and register event callbacks
|
|
146
|
+
state.load();
|
|
147
|
+
registerEventCallbacks();
|
|
148
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
149
|
+
// Health / Cluster (2)
|
|
150
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
151
|
+
// #0a eacn3_health
|
|
152
|
+
api.registerTool({
|
|
153
|
+
name: "eacn3_health",
|
|
154
|
+
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.",
|
|
155
|
+
parameters: {
|
|
156
|
+
type: "object",
|
|
157
|
+
properties: {
|
|
158
|
+
endpoint: { type: "string", description: "Node URL to probe. Defaults to configured network endpoint." },
|
|
159
|
+
},
|
|
160
|
+
},
|
|
161
|
+
async execute(_id, params) {
|
|
162
|
+
const target = params.endpoint ?? state.getState().network_endpoint;
|
|
163
|
+
try {
|
|
164
|
+
const health = await net.checkHealth(target);
|
|
165
|
+
return ok({ endpoint: target, ...health });
|
|
166
|
+
}
|
|
167
|
+
catch (e) {
|
|
168
|
+
return err(`Health check failed for ${target}: ${e.message}`);
|
|
169
|
+
}
|
|
170
|
+
},
|
|
171
|
+
});
|
|
172
|
+
// #0b eacn3_cluster_status
|
|
173
|
+
api.registerTool({
|
|
174
|
+
name: "eacn3_cluster_status",
|
|
175
|
+
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.",
|
|
176
|
+
parameters: {
|
|
177
|
+
type: "object",
|
|
178
|
+
properties: {
|
|
179
|
+
endpoint: { type: "string", description: "Node URL to query. Defaults to configured network endpoint." },
|
|
180
|
+
},
|
|
181
|
+
},
|
|
182
|
+
async execute(_id, params) {
|
|
183
|
+
const target = params.endpoint ?? state.getState().network_endpoint;
|
|
184
|
+
try {
|
|
185
|
+
const cluster = await net.getClusterStatus(target);
|
|
186
|
+
return ok(cluster);
|
|
187
|
+
}
|
|
188
|
+
catch (e) {
|
|
189
|
+
return err(`Cluster status failed for ${target}: ${e.message}`);
|
|
190
|
+
}
|
|
191
|
+
},
|
|
192
|
+
});
|
|
193
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
194
|
+
// Server Management (4)
|
|
195
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
196
|
+
// #1 eacn3_connect
|
|
197
|
+
api.registerTool({
|
|
198
|
+
name: "eacn3_connect",
|
|
199
|
+
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, restored_agents, hint}. Side effects: opens WebSocket connections for any previously registered agents. IMPORTANT: check restored_agents in the response — if you have previously registered agents, they are already reconnected and ready to use. You do NOT need to re-register them. Only call eacn3_register_agent if you need a NEW agent.",
|
|
200
|
+
parameters: {
|
|
201
|
+
type: "object",
|
|
202
|
+
properties: {
|
|
203
|
+
network_endpoint: { type: "string", description: `Network URL. Defaults to ${EACN3_DEFAULT_NETWORK_ENDPOINT}` },
|
|
204
|
+
seed_nodes: { type: "array", items: { type: "string" }, description: "Additional seed node URLs for fallback" },
|
|
205
|
+
},
|
|
206
|
+
},
|
|
207
|
+
async execute(_id, params) {
|
|
208
|
+
const preferred = params.network_endpoint ?? EACN3_DEFAULT_NETWORK_ENDPOINT;
|
|
209
|
+
const s = state.getState();
|
|
210
|
+
// Health probe + fallback
|
|
211
|
+
let endpoint;
|
|
212
|
+
let fallback = false;
|
|
213
|
+
try {
|
|
214
|
+
endpoint = await net.findHealthyEndpoint(preferred, params.seed_nodes);
|
|
215
|
+
fallback = endpoint !== preferred;
|
|
216
|
+
}
|
|
217
|
+
catch (e) {
|
|
218
|
+
return err(`Cannot reach any network node: ${e.message}`);
|
|
219
|
+
}
|
|
220
|
+
s.network_endpoint = endpoint;
|
|
221
|
+
// Reuse existing server identity if available; otherwise register new
|
|
222
|
+
let sid;
|
|
223
|
+
if (s.server_card) {
|
|
224
|
+
try {
|
|
225
|
+
await net.heartbeat();
|
|
226
|
+
sid = s.server_card.server_id;
|
|
227
|
+
s.server_card.status = "online";
|
|
228
|
+
}
|
|
229
|
+
catch {
|
|
230
|
+
const res = await net.registerServer("0.3.0", "plugin://local", "plugin-user");
|
|
231
|
+
sid = res.server_id;
|
|
232
|
+
s.server_card = { server_id: sid, version: "0.3.0", endpoint: "plugin://local", owner: "plugin-user", status: "online" };
|
|
233
|
+
for (const agent of Object.values(s.agents)) {
|
|
234
|
+
agent.server_id = sid;
|
|
235
|
+
try {
|
|
236
|
+
await net.registerAgent(agent);
|
|
237
|
+
}
|
|
238
|
+
catch { /* best-effort */ }
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
else {
|
|
243
|
+
const res = await net.registerServer("0.3.0", "plugin://local", "plugin-user");
|
|
244
|
+
sid = res.server_id;
|
|
245
|
+
s.server_card = { server_id: sid, version: "0.3.0", endpoint: "plugin://local", owner: "plugin-user", status: "online" };
|
|
246
|
+
}
|
|
247
|
+
state.save();
|
|
248
|
+
startHeartbeat();
|
|
249
|
+
// Reconnect WS for all existing agents; re-register if network lost them
|
|
250
|
+
for (const agent of Object.values(s.agents)) {
|
|
251
|
+
try {
|
|
252
|
+
await net.getAgentInfo(agent.agent_id);
|
|
253
|
+
}
|
|
254
|
+
catch {
|
|
255
|
+
try {
|
|
256
|
+
await net.registerAgent(agent);
|
|
257
|
+
}
|
|
258
|
+
catch { /* best-effort */ }
|
|
259
|
+
}
|
|
260
|
+
ws.connect(agent.agent_id);
|
|
261
|
+
}
|
|
262
|
+
const restoredAgents = Object.values(s.agents).map((a) => ({
|
|
263
|
+
agent_id: a.agent_id, name: a.name, domains: a.domains, tier: a.tier,
|
|
264
|
+
}));
|
|
265
|
+
return ok({
|
|
266
|
+
connected: true, server_id: sid, network_endpoint: endpoint, fallback,
|
|
267
|
+
agents_online: restoredAgents.length,
|
|
268
|
+
restored_agents: restoredAgents,
|
|
269
|
+
hint: restoredAgents.length > 0
|
|
270
|
+
? "You have previously registered agents restored and reconnected. You can use them directly without re-registering. Call eacn3_list_my_agents() for full details."
|
|
271
|
+
: "No previous agents found. Register a new agent with eacn3_register_agent().",
|
|
272
|
+
});
|
|
273
|
+
},
|
|
274
|
+
});
|
|
275
|
+
// #2 eacn3_disconnect
|
|
276
|
+
api.registerTool({
|
|
277
|
+
name: "eacn3_disconnect",
|
|
278
|
+
description: "Disconnect from the EACN3 network and close all WebSocket connections. Requires: eacn3_connect first. Side effects: active tasks will timeout and hurt reputation. Server identity and agent registrations are preserved — on next eacn3_connect they will be automatically reconnected. Returns {disconnected: true}. Only call at end of session.",
|
|
279
|
+
parameters: { type: "object", properties: {} },
|
|
280
|
+
async execute() {
|
|
281
|
+
stopHeartbeat();
|
|
282
|
+
ws.disconnectAll();
|
|
283
|
+
// Do NOT call unregisterServer — it cascade-deletes all agents on the network side.
|
|
284
|
+
const s = state.getState();
|
|
285
|
+
if (s.server_card)
|
|
286
|
+
s.server_card.status = "offline";
|
|
287
|
+
state.save();
|
|
288
|
+
return ok({ disconnected: true });
|
|
289
|
+
},
|
|
290
|
+
});
|
|
291
|
+
// #3 eacn3_heartbeat
|
|
292
|
+
api.registerTool({
|
|
293
|
+
name: "eacn3_heartbeat",
|
|
294
|
+
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.",
|
|
295
|
+
parameters: { type: "object", properties: {} },
|
|
296
|
+
async execute() { return ok(await net.heartbeat()); },
|
|
297
|
+
});
|
|
298
|
+
// #4 eacn3_server_info
|
|
299
|
+
api.registerTool({
|
|
300
|
+
name: "eacn3_server_info",
|
|
301
|
+
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.",
|
|
302
|
+
parameters: { type: "object", properties: {} },
|
|
303
|
+
async execute() {
|
|
304
|
+
const s = state.getState();
|
|
305
|
+
if (!s.server_card)
|
|
306
|
+
return err("Not connected");
|
|
307
|
+
let remote;
|
|
308
|
+
try {
|
|
309
|
+
remote = await net.getServer(s.server_card.server_id);
|
|
310
|
+
}
|
|
311
|
+
catch {
|
|
312
|
+
remote = null;
|
|
313
|
+
}
|
|
314
|
+
return ok({ server_card: s.server_card, network_endpoint: s.network_endpoint, agents_count: Object.keys(s.agents).length, agents: Object.keys(s.agents), tasks_count: Object.keys(s.local_tasks).length, remote_status: remote?.status ?? "unknown" });
|
|
315
|
+
},
|
|
316
|
+
});
|
|
317
|
+
// #4b eacn3_a2a_server
|
|
318
|
+
api.registerTool({
|
|
319
|
+
name: "eacn3_a2a_server",
|
|
320
|
+
description: "Start or stop the A2A (Agent-to-Agent) HTTP server for direct messaging. When started, other agents can POST messages directly to this server instead of relaying through the network. Returns {running, port, url}. Pass action='stop' to shut it down. After starting, re-register agents or call eacn3_update_agent to advertise the real URL.",
|
|
321
|
+
parameters: {
|
|
322
|
+
type: "object",
|
|
323
|
+
properties: {
|
|
324
|
+
action: { type: "string", enum: ["start", "stop", "status"], description: "Action to perform. Defaults to 'start'." },
|
|
325
|
+
port: { type: "number", description: "Port to listen on. 0 = OS auto-assign. Defaults to 0." },
|
|
326
|
+
url: { type: "string", description: "Public URL for this server (e.g. 'http://my-host:3001'). Auto-generated from port if omitted." },
|
|
327
|
+
},
|
|
328
|
+
},
|
|
329
|
+
async execute(_id, params) {
|
|
330
|
+
const action = params.action ?? "start";
|
|
331
|
+
if (action === "status") {
|
|
332
|
+
return ok({ running: a2a.isRunning(), port: a2a.getServerPort() });
|
|
333
|
+
}
|
|
334
|
+
if (action === "stop") {
|
|
335
|
+
await a2a.stopServer();
|
|
336
|
+
return ok({ running: false, port: 0 });
|
|
337
|
+
}
|
|
338
|
+
// start
|
|
339
|
+
const port = params.port ?? 0;
|
|
340
|
+
const actualPort = await a2a.startServer(port);
|
|
341
|
+
const baseUrl = params.url
|
|
342
|
+
? params.url.replace(/\/$/, "")
|
|
343
|
+
: `http://localhost:${actualPort}`;
|
|
344
|
+
return ok({ running: true, port: actualPort, url: baseUrl });
|
|
345
|
+
},
|
|
346
|
+
});
|
|
347
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
348
|
+
// Agent Management (7)
|
|
349
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
350
|
+
// #5 eacn3_register_agent
|
|
351
|
+
api.registerTool({
|
|
352
|
+
name: "eacn3_register_agent",
|
|
353
|
+
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').",
|
|
354
|
+
parameters: {
|
|
355
|
+
type: "object",
|
|
356
|
+
properties: {
|
|
357
|
+
name: { type: "string", description: "Agent display name" },
|
|
358
|
+
description: { type: "string", description: "What this Agent does" },
|
|
359
|
+
domains: { type: "array", items: { type: "string" }, description: "Capability domains" },
|
|
360
|
+
skills: { type: "array", items: { type: "object", properties: { id: { type: "string" }, name: { type: "string" }, description: { type: "string" }, tags: { type: "array", items: { type: "string" } }, parameters: { type: "object" } } }, description: "Agent skills" },
|
|
361
|
+
capabilities: { type: "object", properties: { max_concurrent_tasks: { type: "number", description: "Max tasks simultaneously (0 = unlimited)" }, concurrent: { type: "boolean", description: "Whether Agent supports concurrent execution" } }, description: "Agent capacity limits" },
|
|
362
|
+
tier: { type: "string", enum: ["general", "expert", "expert_general", "tool"], description: "Capability tier: general > expert > expert_general > tool. Defaults to general." },
|
|
363
|
+
agent_id: { type: "string", description: "Custom agent ID. Auto-generated if omitted." },
|
|
364
|
+
a2a_port: { type: "number", description: "Port for A2A HTTP server. Enables direct agent-to-agent messaging. Omit to use Network relay only." },
|
|
365
|
+
a2a_url: { type: "string", description: "Full public URL for A2A callbacks (e.g. 'http://my-server.com:3001'). Auto-generated from a2a_port if omitted." },
|
|
366
|
+
},
|
|
367
|
+
required: ["name", "description", "domains"],
|
|
368
|
+
},
|
|
369
|
+
async execute(_id, params) {
|
|
370
|
+
const s = state.getState();
|
|
371
|
+
if (!s.server_card)
|
|
372
|
+
return err("Not connected. Call eacn3_connect first.");
|
|
373
|
+
if (!params.name?.trim())
|
|
374
|
+
return err("name cannot be empty");
|
|
375
|
+
if (!params.domains?.length)
|
|
376
|
+
return err("domains cannot be empty");
|
|
377
|
+
const agentId = params.agent_id ?? `agent-${Date.now().toString(36)}`;
|
|
378
|
+
// Determine agent URL: real A2A endpoint or local placeholder
|
|
379
|
+
let agentUrl = `plugin://local/agents/${agentId}`;
|
|
380
|
+
if (params.a2a_port || params.a2a_url) {
|
|
381
|
+
const port = params.a2a_port ?? 0;
|
|
382
|
+
const actualPort = await a2a.startServer(port);
|
|
383
|
+
if (params.a2a_url) {
|
|
384
|
+
agentUrl = `${params.a2a_url.replace(/\/$/, "")}/agents/${agentId}`;
|
|
385
|
+
}
|
|
386
|
+
else {
|
|
387
|
+
agentUrl = `http://localhost:${actualPort}/agents/${agentId}`;
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
const card = {
|
|
391
|
+
agent_id: agentId, name: params.name,
|
|
392
|
+
tier: params.tier ?? "general",
|
|
393
|
+
domains: params.domains, skills: params.skills ?? [], capabilities: params.capabilities,
|
|
394
|
+
url: agentUrl, server_id: s.server_card.server_id,
|
|
395
|
+
network_id: "", description: params.description,
|
|
396
|
+
};
|
|
397
|
+
const res = await net.registerAgent(card);
|
|
398
|
+
state.addAgent(card);
|
|
221
399
|
ws.connect(agentId);
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
return
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
}
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
parameters: { type: "object", properties: { agent_id: { type: "string" } }, required: ["agent_id"] },
|
|
318
|
-
async execute(_id, params) {
|
|
319
|
-
const local = state.getAgent(params.agent_id);
|
|
320
|
-
if (local)
|
|
321
|
-
return ok(local);
|
|
322
|
-
return ok(await net.getAgentInfo(params.agent_id));
|
|
323
|
-
},
|
|
324
|
-
});
|
|
325
|
-
// #7 eacn3_update_agent
|
|
326
|
-
api.registerTool({
|
|
327
|
-
name: "eacn3_update_agent",
|
|
328
|
-
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.",
|
|
329
|
-
parameters: {
|
|
330
|
-
type: "object",
|
|
331
|
-
properties: {
|
|
332
|
-
agent_id: { type: "string" }, name: { type: "string" },
|
|
333
|
-
domains: { type: "array", items: { type: "string" } },
|
|
334
|
-
skills: { type: "array", items: { type: "object", properties: { id: { type: "string" }, name: { type: "string" }, description: { type: "string" }, tags: { type: "array", items: { type: "string" } }, parameters: { type: "object" } } } },
|
|
335
|
-
description: { type: "string" },
|
|
336
|
-
},
|
|
337
|
-
required: ["agent_id"],
|
|
338
|
-
},
|
|
339
|
-
async execute(_id, params) {
|
|
340
|
-
const { agent_id, ...updates } = params;
|
|
341
|
-
const res = await net.updateAgent(agent_id, updates);
|
|
342
|
-
const local = state.getAgent(agent_id);
|
|
343
|
-
if (local) {
|
|
344
|
-
if (updates.name !== undefined)
|
|
345
|
-
local.name = updates.name;
|
|
346
|
-
if (updates.domains !== undefined)
|
|
347
|
-
local.domains = updates.domains;
|
|
348
|
-
if (updates.skills !== undefined)
|
|
349
|
-
local.skills = updates.skills;
|
|
350
|
-
if (updates.description !== undefined)
|
|
351
|
-
local.description = updates.description;
|
|
352
|
-
state.addAgent(local);
|
|
353
|
-
}
|
|
354
|
-
return ok({ updated: true, agent_id, ...res });
|
|
355
|
-
},
|
|
356
|
-
});
|
|
357
|
-
// #8 eacn3_unregister_agent
|
|
358
|
-
api.registerTool({
|
|
359
|
-
name: "eacn3_unregister_agent",
|
|
360
|
-
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}.",
|
|
361
|
-
parameters: { type: "object", properties: { agent_id: { type: "string" } }, required: ["agent_id"] },
|
|
362
|
-
async execute(_id, params) {
|
|
363
|
-
const res = await net.unregisterAgent(params.agent_id);
|
|
364
|
-
ws.disconnect(params.agent_id);
|
|
365
|
-
state.removeAgent(params.agent_id);
|
|
366
|
-
return ok({ unregistered: true, agent_id: params.agent_id, ...res });
|
|
367
|
-
},
|
|
368
|
-
});
|
|
369
|
-
// #9 eacn3_list_my_agents
|
|
370
|
-
api.registerTool({
|
|
371
|
-
name: "eacn3_list_my_agents",
|
|
372
|
-
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.",
|
|
373
|
-
parameters: { type: "object", properties: {} },
|
|
374
|
-
async execute() {
|
|
375
|
-
const agents = state.listAgents();
|
|
376
|
-
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) })) });
|
|
377
|
-
},
|
|
378
|
-
});
|
|
379
|
-
// #10 eacn3_discover_agents
|
|
380
|
-
api.registerTool({
|
|
381
|
-
name: "eacn3_discover_agents",
|
|
382
|
-
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.",
|
|
383
|
-
parameters: { type: "object", properties: { domain: { type: "string" }, requester_id: { type: "string" } }, required: ["domain"] },
|
|
384
|
-
async execute(_id, params) {
|
|
385
|
-
return ok(await net.discoverAgents(params.domain, params.requester_id));
|
|
386
|
-
},
|
|
387
|
-
});
|
|
388
|
-
// #11 eacn3_list_agents
|
|
389
|
-
api.registerTool({
|
|
390
|
-
name: "eacn3_list_agents",
|
|
391
|
-
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.",
|
|
392
|
-
parameters: {
|
|
393
|
-
type: "object",
|
|
394
|
-
properties: {
|
|
395
|
-
domain: { type: "string" }, server_id: { type: "string" },
|
|
396
|
-
limit: { type: "number" }, offset: { type: "number" },
|
|
397
|
-
},
|
|
398
|
-
},
|
|
399
|
-
async execute(_id, params) {
|
|
400
|
-
const agents = await net.listAgentsRemote(params);
|
|
401
|
-
return ok({ count: agents.length, agents });
|
|
402
|
-
},
|
|
403
|
-
});
|
|
404
|
-
// ═══════════════════════════════════════════════════════════════════════════
|
|
405
|
-
// Task Query (4)
|
|
406
|
-
// ═══════════════════════════════════════════════════════════════════════════
|
|
407
|
-
// #12 eacn3_get_task
|
|
408
|
-
api.registerTool({
|
|
409
|
-
name: "eacn3_get_task",
|
|
410
|
-
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.",
|
|
411
|
-
parameters: { type: "object", properties: { task_id: { type: "string" } }, required: ["task_id"] },
|
|
412
|
-
async execute(_id, params) { return ok(await net.getTask(params.task_id)); },
|
|
413
|
-
});
|
|
414
|
-
// #13 eacn3_get_task_status
|
|
415
|
-
api.registerTool({
|
|
416
|
-
name: "eacn3_get_task_status",
|
|
417
|
-
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.",
|
|
418
|
-
parameters: { type: "object", properties: { task_id: { type: "string" }, agent_id: { type: "string", description: "Initiator agent ID (auto-injected if omitted)" } }, required: ["task_id"] },
|
|
419
|
-
async execute(_id, params) { const agentId = resolveAgentId(params.agent_id); return ok(await net.getTaskStatus(params.task_id, agentId)); },
|
|
420
|
-
});
|
|
421
|
-
// #14 eacn3_list_open_tasks
|
|
422
|
-
api.registerTool({
|
|
423
|
-
name: "eacn3_list_open_tasks",
|
|
424
|
-
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.",
|
|
425
|
-
parameters: { type: "object", properties: { domains: { type: "string", description: "Comma-separated domain filter" }, limit: { type: "number" }, offset: { type: "number" } } },
|
|
426
|
-
async execute(_id, params) {
|
|
427
|
-
const tasks = await net.getOpenTasks(params);
|
|
428
|
-
return ok({ count: tasks.length, tasks });
|
|
429
|
-
},
|
|
430
|
-
});
|
|
431
|
-
// #15 eacn3_list_tasks
|
|
432
|
-
api.registerTool({
|
|
433
|
-
name: "eacn3_list_tasks",
|
|
434
|
-
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.",
|
|
435
|
-
parameters: { type: "object", properties: { status: { type: "string" }, initiator_id: { type: "string" }, limit: { type: "number" }, offset: { type: "number" } } },
|
|
436
|
-
async execute(_id, params) {
|
|
437
|
-
const tasks = await net.listTasks(params);
|
|
438
|
-
return ok({ count: tasks.length, tasks });
|
|
439
|
-
},
|
|
440
|
-
});
|
|
441
|
-
// ═══════════════════════════════════════════════════════════════════════════
|
|
442
|
-
// Task Operations — Initiator (7)
|
|
443
|
-
// ═══════════════════════════════════════════════════════════════════════════
|
|
444
|
-
// #16 eacn3_create_task
|
|
445
|
-
api.registerTool({
|
|
446
|
-
name: "eacn3_create_task",
|
|
447
|
-
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.",
|
|
448
|
-
parameters: {
|
|
449
|
-
type: "object",
|
|
450
|
-
properties: {
|
|
451
|
-
description: { type: "string" },
|
|
452
|
-
budget: { type: "number" },
|
|
453
|
-
domains: { type: "array", items: { type: "string" } },
|
|
454
|
-
deadline: { type: "string", description: "ISO 8601 deadline" },
|
|
455
|
-
max_concurrent_bidders: { type: "number" },
|
|
456
|
-
max_depth: { type: "number", description: "Max subtask nesting depth (default 3)" },
|
|
457
|
-
expected_output: {
|
|
458
|
-
type: "object",
|
|
459
|
-
properties: {
|
|
460
|
-
type: { type: "string", description: "Expected output format, e.g. 'json', 'text', 'code'" },
|
|
461
|
-
description: { type: "string", description: "What the output should contain" },
|
|
462
|
-
},
|
|
463
|
-
description: "Structured description of expected result",
|
|
400
|
+
return ok({
|
|
401
|
+
registered: true, agent_id: agentId, seeds: res.seeds, domains: params.domains,
|
|
402
|
+
url: agentUrl,
|
|
403
|
+
a2a_server: a2a.isRunning() ? { port: a2a.getServerPort() } : null,
|
|
404
|
+
});
|
|
405
|
+
},
|
|
406
|
+
});
|
|
407
|
+
// #6 eacn3_get_agent
|
|
408
|
+
api.registerTool({
|
|
409
|
+
name: "eacn3_get_agent",
|
|
410
|
+
description: "Fetch the full AgentCard for any agent by ID — checks local state first, then queries the network. Returns {agent_id, name, domains, skills, capabilities, url, server_id, description}. No side effects. Use to inspect an agent before sending messages or evaluating bids.",
|
|
411
|
+
parameters: { type: "object", properties: { agent_id: { type: "string" } }, required: ["agent_id"] },
|
|
412
|
+
async execute(_id, params) {
|
|
413
|
+
const local = state.getAgent(params.agent_id);
|
|
414
|
+
if (local)
|
|
415
|
+
return ok(local);
|
|
416
|
+
return ok(await net.getAgentInfo(params.agent_id));
|
|
417
|
+
},
|
|
418
|
+
});
|
|
419
|
+
// #7 eacn3_update_agent
|
|
420
|
+
api.registerTool({
|
|
421
|
+
name: "eacn3_update_agent",
|
|
422
|
+
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.",
|
|
423
|
+
parameters: {
|
|
424
|
+
type: "object",
|
|
425
|
+
properties: {
|
|
426
|
+
agent_id: { type: "string" }, name: { type: "string" },
|
|
427
|
+
domains: { type: "array", items: { type: "string" } },
|
|
428
|
+
skills: { type: "array", items: { type: "object", properties: { id: { type: "string" }, name: { type: "string" }, description: { type: "string" }, tags: { type: "array", items: { type: "string" } }, parameters: { type: "object" } } } },
|
|
429
|
+
description: { type: "string" },
|
|
430
|
+
},
|
|
431
|
+
required: ["agent_id"],
|
|
432
|
+
},
|
|
433
|
+
async execute(_id, params) {
|
|
434
|
+
const { agent_id, ...updates } = params;
|
|
435
|
+
const res = await net.updateAgent(agent_id, updates);
|
|
436
|
+
const local = state.getAgent(agent_id);
|
|
437
|
+
if (local) {
|
|
438
|
+
if (updates.name !== undefined)
|
|
439
|
+
local.name = updates.name;
|
|
440
|
+
if (updates.domains !== undefined)
|
|
441
|
+
local.domains = updates.domains;
|
|
442
|
+
if (updates.skills !== undefined)
|
|
443
|
+
local.skills = updates.skills;
|
|
444
|
+
if (updates.description !== undefined)
|
|
445
|
+
local.description = updates.description;
|
|
446
|
+
state.addAgent(local);
|
|
447
|
+
}
|
|
448
|
+
return ok({ updated: true, agent_id, ...res });
|
|
449
|
+
},
|
|
450
|
+
});
|
|
451
|
+
// #8 eacn3_unregister_agent
|
|
452
|
+
api.registerTool({
|
|
453
|
+
name: "eacn3_unregister_agent",
|
|
454
|
+
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}.",
|
|
455
|
+
parameters: { type: "object", properties: { agent_id: { type: "string" } }, required: ["agent_id"] },
|
|
456
|
+
async execute(_id, params) {
|
|
457
|
+
const res = await net.unregisterAgent(params.agent_id);
|
|
458
|
+
ws.disconnect(params.agent_id);
|
|
459
|
+
state.removeAgent(params.agent_id);
|
|
460
|
+
// Stop A2A server if no agents remain
|
|
461
|
+
if (state.listAgents().length === 0 && a2a.isRunning()) {
|
|
462
|
+
await a2a.stopServer();
|
|
463
|
+
}
|
|
464
|
+
return ok({ unregistered: true, agent_id: params.agent_id, ...res });
|
|
465
|
+
},
|
|
466
|
+
});
|
|
467
|
+
// #9 eacn3_list_my_agents
|
|
468
|
+
api.registerTool({
|
|
469
|
+
name: "eacn3_list_my_agents",
|
|
470
|
+
description: "List all agents registered on this local server instance. Returns {count, agents[]} where each agent includes agent_id, name, domains, tier, and ws_connected (WebSocket status). No network call — reads local state only. Use to check which agents are active and receiving events.",
|
|
471
|
+
parameters: { type: "object", properties: {} },
|
|
472
|
+
async execute() {
|
|
473
|
+
const agents = state.listAgents();
|
|
474
|
+
return ok({ count: agents.length, agents: agents.map((a) => ({ agent_id: a.agent_id, name: a.name, domains: a.domains, tier: a.tier, ws_connected: ws.isConnected(a.agent_id) })) });
|
|
475
|
+
},
|
|
476
|
+
});
|
|
477
|
+
// #10 eacn3_discover_agents
|
|
478
|
+
api.registerTool({
|
|
479
|
+
name: "eacn3_discover_agents",
|
|
480
|
+
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.",
|
|
481
|
+
parameters: { type: "object", properties: { domain: { type: "string" }, requester_id: { type: "string" } }, required: ["domain"] },
|
|
482
|
+
async execute(_id, params) {
|
|
483
|
+
return ok(await net.discoverAgents(params.domain, params.requester_id));
|
|
484
|
+
},
|
|
485
|
+
});
|
|
486
|
+
// #11 eacn3_list_agents
|
|
487
|
+
api.registerTool({
|
|
488
|
+
name: "eacn3_list_agents",
|
|
489
|
+
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.",
|
|
490
|
+
parameters: {
|
|
491
|
+
type: "object",
|
|
492
|
+
properties: {
|
|
493
|
+
domain: { type: "string" }, server_id: { type: "string" },
|
|
494
|
+
limit: { type: "number" }, offset: { type: "number" },
|
|
464
495
|
},
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
496
|
+
},
|
|
497
|
+
async execute(_id, params) {
|
|
498
|
+
const agents = await net.listAgentsRemote(params);
|
|
499
|
+
return ok({ count: agents.length, agents });
|
|
500
|
+
},
|
|
501
|
+
});
|
|
502
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
503
|
+
// Task Query (4)
|
|
504
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
505
|
+
// #12 eacn3_get_task
|
|
506
|
+
api.registerTool({
|
|
507
|
+
name: "eacn3_get_task",
|
|
508
|
+
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.",
|
|
509
|
+
parameters: { type: "object", properties: { task_id: { type: "string" } }, required: ["task_id"] },
|
|
510
|
+
async execute(_id, params) { return ok(await net.getTask(params.task_id)); },
|
|
511
|
+
});
|
|
512
|
+
// #13 eacn3_get_task_status
|
|
513
|
+
api.registerTool({
|
|
514
|
+
name: "eacn3_get_task_status",
|
|
515
|
+
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.",
|
|
516
|
+
parameters: { type: "object", properties: { task_id: { type: "string" }, agent_id: { type: "string", description: "Initiator agent ID (auto-injected if omitted)" } }, required: ["task_id"] },
|
|
517
|
+
async execute(_id, params) { const agentId = resolveAgentId(params.agent_id); return ok(await net.getTaskStatus(params.task_id, agentId)); },
|
|
518
|
+
});
|
|
519
|
+
// #14 eacn3_list_open_tasks
|
|
520
|
+
api.registerTool({
|
|
521
|
+
name: "eacn3_list_open_tasks",
|
|
522
|
+
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.",
|
|
523
|
+
parameters: { type: "object", properties: { domains: { type: "string", description: "Comma-separated domain filter" }, limit: { type: "number" }, offset: { type: "number" } } },
|
|
524
|
+
async execute(_id, params) {
|
|
525
|
+
const tasks = await net.getOpenTasks(params);
|
|
526
|
+
return ok({ count: tasks.length, tasks });
|
|
527
|
+
},
|
|
528
|
+
});
|
|
529
|
+
// #15 eacn3_list_tasks
|
|
530
|
+
api.registerTool({
|
|
531
|
+
name: "eacn3_list_tasks",
|
|
532
|
+
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.",
|
|
533
|
+
parameters: { type: "object", properties: { status: { type: "string" }, initiator_id: { type: "string" }, limit: { type: "number" }, offset: { type: "number" } } },
|
|
534
|
+
async execute(_id, params) {
|
|
535
|
+
const tasks = await net.listTasks(params);
|
|
536
|
+
return ok({ count: tasks.length, tasks });
|
|
537
|
+
},
|
|
538
|
+
});
|
|
539
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
540
|
+
// Task Operations — Initiator (7)
|
|
541
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
542
|
+
// #16 eacn3_create_task
|
|
543
|
+
api.registerTool({
|
|
544
|
+
name: "eacn3_create_task",
|
|
545
|
+
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.",
|
|
546
|
+
parameters: {
|
|
547
|
+
type: "object",
|
|
548
|
+
properties: {
|
|
549
|
+
description: { type: "string" },
|
|
550
|
+
budget: { type: "number" },
|
|
551
|
+
domains: { type: "array", items: { type: "string" } },
|
|
552
|
+
deadline: { type: "string", description: "ISO 8601 deadline" },
|
|
553
|
+
max_concurrent_bidders: { type: "number" },
|
|
554
|
+
max_depth: { type: "number", description: "Max subtask nesting depth (default 3)" },
|
|
555
|
+
expected_output: {
|
|
556
|
+
type: "object",
|
|
557
|
+
properties: {
|
|
558
|
+
type: { type: "string", description: "Expected output format, e.g. 'json', 'text', 'code'" },
|
|
559
|
+
description: { type: "string", description: "What the output should contain" },
|
|
560
|
+
},
|
|
561
|
+
description: "Structured description of expected result",
|
|
562
|
+
},
|
|
563
|
+
human_contact: {
|
|
564
|
+
type: "object",
|
|
565
|
+
properties: {
|
|
566
|
+
allowed: { type: "boolean", description: "Whether human owner can be contacted for decisions" },
|
|
567
|
+
contact_id: { type: "string", description: "Human contact identifier" },
|
|
568
|
+
timeout_s: { type: "number", description: "Seconds to wait for human response before auto-reject" },
|
|
569
|
+
},
|
|
570
|
+
description: "Human-in-the-loop contact settings",
|
|
471
571
|
},
|
|
472
|
-
description: "
|
|
572
|
+
level: { type: "string", enum: ["general", "expert", "expert_general", "tool"], description: "Task complexity level. Determines which agent tiers can bid. Defaults to 'general'." },
|
|
573
|
+
invited_agent_ids: { type: "array", items: { type: "string" }, description: "Agent IDs to directly approve — bypass bid admission filtering." },
|
|
574
|
+
initiator_id: { type: "string", description: "Agent ID of the task initiator (auto-injected if omitted)" },
|
|
473
575
|
},
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
576
|
+
required: ["description", "budget"],
|
|
577
|
+
},
|
|
578
|
+
async execute(_id, params) {
|
|
579
|
+
const initiatorId = resolveAgentId(params.initiator_id);
|
|
580
|
+
const taskId = `t-${Date.now().toString(36)}`;
|
|
581
|
+
const localAgents = state.listAgents();
|
|
582
|
+
const matchedLocal = params.domains
|
|
583
|
+
? localAgents.filter((a) => a.agent_id !== initiatorId && params.domains.some((d) => a.domains.includes(d)))
|
|
584
|
+
: [];
|
|
585
|
+
const task = await net.createTask({
|
|
586
|
+
task_id: taskId, initiator_id: initiatorId,
|
|
587
|
+
content: { description: params.description, expected_output: params.expected_output },
|
|
588
|
+
domains: params.domains, budget: params.budget, deadline: params.deadline,
|
|
589
|
+
max_concurrent_bidders: params.max_concurrent_bidders, max_depth: params.max_depth,
|
|
590
|
+
human_contact: params.human_contact,
|
|
591
|
+
level: params.level ?? "general",
|
|
592
|
+
invited_agent_ids: params.invited_agent_ids,
|
|
593
|
+
});
|
|
594
|
+
state.updateTask({ task_id: taskId, role: "initiator", status: task.status, domains: params.domains ?? [], description_summary: params.description.slice(0, 100), created_at: new Date().toISOString() });
|
|
595
|
+
return ok({ task_id: taskId, status: task.status, budget: params.budget, local_matches: matchedLocal.map((a) => a.agent_id) });
|
|
596
|
+
},
|
|
597
|
+
});
|
|
598
|
+
// #17 eacn3_get_task_results
|
|
599
|
+
api.registerTool({
|
|
600
|
+
name: "eacn3_get_task_results",
|
|
601
|
+
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.",
|
|
602
|
+
parameters: { type: "object", properties: { task_id: { type: "string" }, initiator_id: { type: "string", description: "Initiator agent ID (auto-injected if omitted)" } }, required: ["task_id"] },
|
|
603
|
+
async execute(_id, params) { const initiatorId = resolveAgentId(params.initiator_id); return ok(await net.getTaskResults(params.task_id, initiatorId)); },
|
|
604
|
+
});
|
|
605
|
+
// #18 eacn3_select_result
|
|
606
|
+
api.registerTool({
|
|
607
|
+
name: "eacn3_select_result",
|
|
608
|
+
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.",
|
|
609
|
+
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"] },
|
|
610
|
+
async execute(_id, params) { const initiatorId = resolveAgentId(params.initiator_id); return ok(await net.selectResult(params.task_id, initiatorId, params.agent_id)); },
|
|
611
|
+
});
|
|
612
|
+
// #19 eacn3_close_task
|
|
613
|
+
api.registerTool({
|
|
614
|
+
name: "eacn3_close_task",
|
|
615
|
+
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.",
|
|
616
|
+
parameters: { type: "object", properties: { task_id: { type: "string" }, initiator_id: { type: "string", description: "Initiator agent ID (auto-injected if omitted)" } }, required: ["task_id"] },
|
|
617
|
+
async execute(_id, params) { const initiatorId = resolveAgentId(params.initiator_id); return ok(await net.closeTask(params.task_id, initiatorId)); },
|
|
618
|
+
});
|
|
619
|
+
// #20 eacn3_update_deadline
|
|
620
|
+
api.registerTool({
|
|
621
|
+
name: "eacn3_update_deadline",
|
|
622
|
+
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.",
|
|
623
|
+
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"] },
|
|
624
|
+
async execute(_id, params) { const initiatorId = resolveAgentId(params.initiator_id); return ok(await net.updateDeadline(params.task_id, initiatorId, params.new_deadline)); },
|
|
625
|
+
});
|
|
626
|
+
// #21 eacn3_update_discussions
|
|
627
|
+
api.registerTool({
|
|
628
|
+
name: "eacn3_update_discussions",
|
|
629
|
+
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.",
|
|
630
|
+
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"] },
|
|
631
|
+
async execute(_id, params) { const initiatorId = resolveAgentId(params.initiator_id); return ok(await net.updateDiscussions(params.task_id, initiatorId, params.message)); },
|
|
632
|
+
});
|
|
633
|
+
// #22 eacn3_confirm_budget
|
|
634
|
+
api.registerTool({
|
|
635
|
+
name: "eacn3_confirm_budget",
|
|
636
|
+
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.",
|
|
637
|
+
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"] },
|
|
638
|
+
async execute(_id, params) { const initiatorId = resolveAgentId(params.initiator_id); return ok(await net.confirmBudget(params.task_id, initiatorId, params.approved, params.new_budget)); },
|
|
639
|
+
});
|
|
640
|
+
// #22b eacn3_invite_agent
|
|
641
|
+
api.registerTool({
|
|
642
|
+
name: "eacn3_invite_agent",
|
|
643
|
+
description: "Invite a specific agent to bid on your task, bypassing the normal bid admission filter (confidence×reputation threshold). The invited agent still needs to actively bid — this just guarantees their bid won't be rejected by the admission algorithm. Also sends a direct_message notification to the invited agent. Requires: you must be the task initiator.",
|
|
644
|
+
parameters: { type: "object", properties: { task_id: { type: "string" }, agent_id: { type: "string", description: "Agent ID to invite" }, message: { type: "string", description: "Optional message to send with the invitation" }, initiator_id: { type: "string", description: "Initiator agent ID (auto-injected if omitted)" } }, required: ["task_id", "agent_id"] },
|
|
645
|
+
execute: withLogging("eacn3_invite_agent", async (_id, params) => {
|
|
646
|
+
const initiatorId = resolveAgentId(params.initiator_id);
|
|
647
|
+
const res = await net.inviteAgent(params.task_id, initiatorId, params.agent_id);
|
|
648
|
+
// Send notification to invited agent
|
|
649
|
+
const inviteContent = params.message
|
|
650
|
+
? `[Task Invitation] You've been invited to bid on task ${params.task_id}. Your bid will bypass admission filtering. Message: ${params.message}`
|
|
651
|
+
: `[Task Invitation] You've been invited to bid on task ${params.task_id}. Your bid will bypass admission filtering.`;
|
|
652
|
+
state.addMessage(initiatorId, { from: initiatorId, to: params.agent_id, content: inviteContent, timestamp: Date.now(), direction: "out" });
|
|
653
|
+
// Try to notify the invited agent: A2A direct → relay fallback
|
|
654
|
+
try {
|
|
655
|
+
const agentCard = await net.getAgentInfo(params.agent_id);
|
|
656
|
+
if (agentCard.url && !agentCard.url.startsWith("plugin://")) {
|
|
657
|
+
// A2A direct POST
|
|
658
|
+
const eventsUrl = agentCard.url.replace(/\/$/, "") + "/events";
|
|
659
|
+
await fetch(eventsUrl, {
|
|
660
|
+
method: "POST",
|
|
661
|
+
headers: { "Content-Type": "application/json" },
|
|
662
|
+
body: JSON.stringify({ type: "direct_message", from: initiatorId, content: inviteContent, task_id: params.task_id, invitation: true }),
|
|
663
|
+
}).catch(() => { });
|
|
664
|
+
}
|
|
665
|
+
else {
|
|
666
|
+
// Network relay with proper routing info
|
|
667
|
+
await net.relayMessage({
|
|
668
|
+
to: { network_id: agentCard.network_id ?? "", server_id: agentCard.server_id, agent_id: params.agent_id },
|
|
669
|
+
from: { network_id: state.getState().server_card?.server_id ?? "", server_id: state.getServerId() ?? "", agent_id: initiatorId },
|
|
670
|
+
content: inviteContent,
|
|
671
|
+
});
|
|
672
|
+
}
|
|
673
|
+
}
|
|
674
|
+
catch { /* non-critical — invitation still recorded server-side */ }
|
|
675
|
+
return ok(res);
|
|
676
|
+
}),
|
|
677
|
+
});
|
|
678
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
679
|
+
// Task Operations — Executor (5)
|
|
680
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
681
|
+
// #23 eacn3_submit_bid
|
|
682
|
+
api.registerTool({
|
|
683
|
+
name: "eacn3_submit_bid",
|
|
684
|
+
description: "Bid on an open task by specifying your confidence (0.0-1.0 honest ability estimate) and price in credits. Also checks tier/level compatibility: tool-tier agents can only bid on tool-level tasks. Invited agents bypass admission filtering. Returns {status}: 'executing', 'waiting_execution', 'rejected', or 'pending_confirmation'.",
|
|
685
|
+
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"] },
|
|
686
|
+
execute: withLogging("eacn3_submit_bid", async (_id, params) => {
|
|
687
|
+
const agentId = resolveAgentId(params.agent_id);
|
|
688
|
+
// Tier/level filtering and invite bypass are handled server-side in matcher.check_bid().
|
|
689
|
+
const res = await net.submitBid(params.task_id, agentId, params.confidence, params.price);
|
|
690
|
+
if (res.status && res.status !== "rejected") {
|
|
691
|
+
state.updateTask({ task_id: params.task_id, role: "executor", status: "bidding", domains: [], description_summary: "", created_at: new Date().toISOString() });
|
|
692
|
+
}
|
|
693
|
+
return ok(res);
|
|
694
|
+
}),
|
|
695
|
+
});
|
|
696
|
+
// #24 eacn3_submit_result
|
|
697
|
+
api.registerTool({
|
|
698
|
+
name: "eacn3_submit_result",
|
|
699
|
+
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.",
|
|
700
|
+
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"] },
|
|
701
|
+
async execute(_id, params) {
|
|
702
|
+
const agentId = resolveAgentId(params.agent_id);
|
|
703
|
+
const res = await net.submitResult(params.task_id, agentId, params.content);
|
|
704
|
+
try {
|
|
705
|
+
await net.reportEvent(agentId, "task_completed");
|
|
706
|
+
}
|
|
707
|
+
catch { /* non-critical */ }
|
|
708
|
+
return ok(res);
|
|
709
|
+
},
|
|
710
|
+
});
|
|
711
|
+
// #25 eacn3_reject_task
|
|
712
|
+
api.registerTool({
|
|
713
|
+
name: "eacn3_reject_task",
|
|
714
|
+
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.",
|
|
715
|
+
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"] },
|
|
716
|
+
async execute(_id, params) {
|
|
717
|
+
const agentId = resolveAgentId(params.agent_id);
|
|
718
|
+
const res = await net.rejectTask(params.task_id, agentId, params.reason);
|
|
719
|
+
try {
|
|
720
|
+
await net.reportEvent(agentId, "task_rejected");
|
|
721
|
+
}
|
|
722
|
+
catch { /* non-critical */ }
|
|
723
|
+
return ok(res);
|
|
724
|
+
},
|
|
725
|
+
});
|
|
726
|
+
// #26 eacn3_create_subtask
|
|
727
|
+
api.registerTool({
|
|
728
|
+
name: "eacn3_create_subtask",
|
|
729
|
+
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.",
|
|
730
|
+
parameters: {
|
|
731
|
+
type: "object",
|
|
732
|
+
properties: {
|
|
733
|
+
parent_task_id: { type: "string" }, description: { type: "string" },
|
|
734
|
+
domains: { type: "array", items: { type: "string" } },
|
|
735
|
+
budget: { type: "number" }, deadline: { type: "string" },
|
|
736
|
+
level: { type: "string", enum: ["general", "expert", "expert_general", "tool"], description: "Task level. If omitted, inherits from parent." },
|
|
737
|
+
initiator_id: { type: "string", description: "Agent ID of the executor creating the subtask (auto-injected if omitted)" },
|
|
738
|
+
},
|
|
739
|
+
required: ["parent_task_id", "description", "domains", "budget"],
|
|
740
|
+
},
|
|
741
|
+
async execute(_id, params) {
|
|
742
|
+
const initiatorId = resolveAgentId(params.initiator_id);
|
|
743
|
+
const task = await net.createSubtask(params.parent_task_id, initiatorId, { description: params.description }, params.domains, params.budget, params.deadline, params.level);
|
|
744
|
+
return ok({ subtask_id: task.id, parent_task_id: params.parent_task_id, status: task.status, depth: task.depth });
|
|
745
|
+
},
|
|
746
|
+
});
|
|
747
|
+
// #27 eacn3_send_message
|
|
748
|
+
// A2A direct — agent.md:358-362: peer-to-peer, bypasses Network
|
|
749
|
+
api.registerTool({
|
|
750
|
+
name: "eacn3_send_message",
|
|
751
|
+
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.",
|
|
752
|
+
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"] },
|
|
753
|
+
async execute(_id, params) {
|
|
754
|
+
const senderId = resolveAgentId(params.sender_id);
|
|
755
|
+
const targetId = params.agent_id;
|
|
756
|
+
const message = {
|
|
757
|
+
type: "direct_message",
|
|
758
|
+
task_id: "",
|
|
759
|
+
payload: { from: senderId, content: params.content },
|
|
760
|
+
received_at: Date.now(),
|
|
761
|
+
};
|
|
762
|
+
// Local agent — direct push to event buffer
|
|
763
|
+
const localAgent = state.getAgent(targetId);
|
|
764
|
+
if (localAgent) {
|
|
765
|
+
state.pushEvents([message]);
|
|
766
|
+
return ok({ sent: true, to: targetId, from: senderId, local: true });
|
|
767
|
+
}
|
|
768
|
+
// Remote agent — POST to their URL callback (A2A direct, agent.md:160-168)
|
|
769
|
+
let agentCard;
|
|
770
|
+
try {
|
|
771
|
+
agentCard = await net.getAgentInfo(targetId);
|
|
772
|
+
}
|
|
773
|
+
catch {
|
|
774
|
+
return err(`Agent ${targetId} not found`);
|
|
775
|
+
}
|
|
776
|
+
if (!agentCard.url || agentCard.url.startsWith("plugin://")) {
|
|
777
|
+
return err(`Agent ${targetId} has no reachable URL: ${agentCard.url}`);
|
|
778
|
+
}
|
|
779
|
+
const eventsUrl = agentCard.url.replace(/\/$/, "") + "/events";
|
|
780
|
+
try {
|
|
781
|
+
const res = await fetch(eventsUrl, {
|
|
562
782
|
method: "POST",
|
|
563
783
|
headers: { "Content-Type": "application/json" },
|
|
564
|
-
body: JSON.stringify({
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
await net.relayMessage({
|
|
570
|
-
to: { network_id: agentCard.network_id ?? "", server_id: agentCard.server_id, agent_id: params.agent_id },
|
|
571
|
-
from: { network_id: state.getState().server_card?.server_id ?? "", server_id: state.getServerId() ?? "", agent_id: initiatorId },
|
|
572
|
-
content: inviteContent,
|
|
784
|
+
body: JSON.stringify({
|
|
785
|
+
type: "direct_message",
|
|
786
|
+
from: senderId,
|
|
787
|
+
content: params.content,
|
|
788
|
+
}),
|
|
573
789
|
});
|
|
790
|
+
if (!res.ok) {
|
|
791
|
+
return err(`POST ${eventsUrl} failed: ${res.status}`);
|
|
792
|
+
}
|
|
793
|
+
return ok({ sent: true, to: targetId, from: senderId, local: false });
|
|
574
794
|
}
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
return ok(res);
|
|
578
|
-
}),
|
|
579
|
-
});
|
|
580
|
-
// ═══════════════════════════════════════════════════════════════════════════
|
|
581
|
-
// Task Operations — Executor (5)
|
|
582
|
-
// ═══════════════════════════════════════════════════════════════════════════
|
|
583
|
-
// #23 eacn3_submit_bid
|
|
584
|
-
api.registerTool({
|
|
585
|
-
name: "eacn3_submit_bid",
|
|
586
|
-
description: "Bid on an open task by specifying your confidence (0.0-1.0 honest ability estimate) and price in credits. Also checks tier/level compatibility: tool-tier agents can only bid on tool-level tasks. Invited agents bypass admission filtering. Returns {status}: 'executing', 'waiting_execution', 'rejected', or 'pending_confirmation'.",
|
|
587
|
-
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"] },
|
|
588
|
-
execute: withLogging("eacn3_submit_bid", async (_id, params) => {
|
|
589
|
-
const agentId = resolveAgentId(params.agent_id);
|
|
590
|
-
// Tier/level filtering and invite bypass are handled server-side in matcher.check_bid().
|
|
591
|
-
const res = await net.submitBid(params.task_id, agentId, params.confidence, params.price);
|
|
592
|
-
if (res.status && res.status !== "rejected") {
|
|
593
|
-
state.updateTask({ task_id: params.task_id, role: "executor", status: "bidding", domains: [], description_summary: "", created_at: new Date().toISOString() });
|
|
594
|
-
}
|
|
595
|
-
return ok(res);
|
|
596
|
-
}),
|
|
597
|
-
});
|
|
598
|
-
// #24 eacn3_submit_result
|
|
599
|
-
api.registerTool({
|
|
600
|
-
name: "eacn3_submit_result",
|
|
601
|
-
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.",
|
|
602
|
-
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"] },
|
|
603
|
-
async execute(_id, params) {
|
|
604
|
-
const agentId = resolveAgentId(params.agent_id);
|
|
605
|
-
const res = await net.submitResult(params.task_id, agentId, params.content);
|
|
606
|
-
try {
|
|
607
|
-
await net.reportEvent(agentId, "task_completed");
|
|
608
|
-
}
|
|
609
|
-
catch { /* non-critical */ }
|
|
610
|
-
return ok(res);
|
|
611
|
-
},
|
|
612
|
-
});
|
|
613
|
-
// #25 eacn3_reject_task
|
|
614
|
-
api.registerTool({
|
|
615
|
-
name: "eacn3_reject_task",
|
|
616
|
-
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.",
|
|
617
|
-
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"] },
|
|
618
|
-
async execute(_id, params) {
|
|
619
|
-
const agentId = resolveAgentId(params.agent_id);
|
|
620
|
-
const res = await net.rejectTask(params.task_id, agentId, params.reason);
|
|
621
|
-
try {
|
|
622
|
-
await net.reportEvent(agentId, "task_rejected");
|
|
623
|
-
}
|
|
624
|
-
catch { /* non-critical */ }
|
|
625
|
-
return ok(res);
|
|
626
|
-
},
|
|
627
|
-
});
|
|
628
|
-
// #26 eacn3_create_subtask
|
|
629
|
-
api.registerTool({
|
|
630
|
-
name: "eacn3_create_subtask",
|
|
631
|
-
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.",
|
|
632
|
-
parameters: {
|
|
633
|
-
type: "object",
|
|
634
|
-
properties: {
|
|
635
|
-
parent_task_id: { type: "string" }, description: { type: "string" },
|
|
636
|
-
domains: { type: "array", items: { type: "string" } },
|
|
637
|
-
budget: { type: "number" }, deadline: { type: "string" },
|
|
638
|
-
level: { type: "string", enum: ["general", "expert", "expert_general", "tool"], description: "Task level. If omitted, inherits from parent." },
|
|
639
|
-
initiator_id: { type: "string", description: "Agent ID of the executor creating the subtask (auto-injected if omitted)" },
|
|
640
|
-
},
|
|
641
|
-
required: ["parent_task_id", "description", "domains", "budget"],
|
|
642
|
-
},
|
|
643
|
-
async execute(_id, params) {
|
|
644
|
-
const initiatorId = resolveAgentId(params.initiator_id);
|
|
645
|
-
const task = await net.createSubtask(params.parent_task_id, initiatorId, { description: params.description }, params.domains, params.budget, params.deadline, params.level);
|
|
646
|
-
return ok({ subtask_id: task.id, parent_task_id: params.parent_task_id, status: task.status, depth: task.depth });
|
|
647
|
-
},
|
|
648
|
-
});
|
|
649
|
-
// #27 eacn3_send_message
|
|
650
|
-
// A2A direct — agent.md:358-362: peer-to-peer, bypasses Network
|
|
651
|
-
api.registerTool({
|
|
652
|
-
name: "eacn3_send_message",
|
|
653
|
-
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.",
|
|
654
|
-
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"] },
|
|
655
|
-
async execute(_id, params) {
|
|
656
|
-
const senderId = resolveAgentId(params.sender_id);
|
|
657
|
-
const targetId = params.agent_id;
|
|
658
|
-
const message = {
|
|
659
|
-
type: "direct_message",
|
|
660
|
-
task_id: "",
|
|
661
|
-
payload: { from: senderId, content: params.content },
|
|
662
|
-
received_at: Date.now(),
|
|
663
|
-
};
|
|
664
|
-
// Local agent — direct push to event buffer
|
|
665
|
-
const localAgent = state.getAgent(targetId);
|
|
666
|
-
if (localAgent) {
|
|
667
|
-
state.pushEvents([message]);
|
|
668
|
-
return ok({ sent: true, to: targetId, from: senderId, local: true });
|
|
669
|
-
}
|
|
670
|
-
// Remote agent — POST to their URL callback (A2A direct, agent.md:160-168)
|
|
671
|
-
let agentCard;
|
|
672
|
-
try {
|
|
673
|
-
agentCard = await net.getAgentInfo(targetId);
|
|
674
|
-
}
|
|
675
|
-
catch {
|
|
676
|
-
return err(`Agent ${targetId} not found`);
|
|
677
|
-
}
|
|
678
|
-
if (!agentCard.url || agentCard.url.startsWith("plugin://")) {
|
|
679
|
-
return err(`Agent ${targetId} has no reachable URL: ${agentCard.url}`);
|
|
680
|
-
}
|
|
681
|
-
const eventsUrl = agentCard.url.replace(/\/$/, "") + "/events";
|
|
682
|
-
try {
|
|
683
|
-
const res = await fetch(eventsUrl, {
|
|
684
|
-
method: "POST",
|
|
685
|
-
headers: { "Content-Type": "application/json" },
|
|
686
|
-
body: JSON.stringify({
|
|
687
|
-
type: "direct_message",
|
|
688
|
-
from: senderId,
|
|
689
|
-
content: params.content,
|
|
690
|
-
}),
|
|
691
|
-
});
|
|
692
|
-
if (!res.ok) {
|
|
693
|
-
return err(`POST ${eventsUrl} failed: ${res.status}`);
|
|
795
|
+
catch (e) {
|
|
796
|
+
return err(`Failed to reach agent at ${eventsUrl}: ${e.message}`);
|
|
694
797
|
}
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
return ok({ count: events.length, events });
|
|
759
|
-
},
|
|
760
|
-
});
|
|
761
|
-
}
|
|
798
|
+
},
|
|
799
|
+
});
|
|
800
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
801
|
+
// Reputation (2)
|
|
802
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
803
|
+
// #28 eacn3_report_event
|
|
804
|
+
api.registerTool({
|
|
805
|
+
name: "eacn3_report_event",
|
|
806
|
+
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.",
|
|
807
|
+
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"] },
|
|
808
|
+
async execute(_id, params) {
|
|
809
|
+
const res = await net.reportEvent(params.agent_id, params.event_type);
|
|
810
|
+
state.updateReputationCache(params.agent_id, res.score);
|
|
811
|
+
return ok(res);
|
|
812
|
+
},
|
|
813
|
+
});
|
|
814
|
+
// #29 eacn3_get_reputation
|
|
815
|
+
api.registerTool({
|
|
816
|
+
name: "eacn3_get_reputation",
|
|
817
|
+
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.",
|
|
818
|
+
parameters: { type: "object", properties: { agent_id: { type: "string" } }, required: ["agent_id"] },
|
|
819
|
+
async execute(_id, params) {
|
|
820
|
+
const res = await net.getReputation(params.agent_id);
|
|
821
|
+
state.updateReputationCache(params.agent_id, res.score);
|
|
822
|
+
return ok(res);
|
|
823
|
+
},
|
|
824
|
+
});
|
|
825
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
826
|
+
// Economy (2)
|
|
827
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
828
|
+
// #30 eacn3_get_balance
|
|
829
|
+
api.registerTool({
|
|
830
|
+
name: "eacn3_get_balance",
|
|
831
|
+
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.",
|
|
832
|
+
parameters: { type: "object", properties: { agent_id: { type: "string", description: "Agent ID to check balance for" } }, required: ["agent_id"] },
|
|
833
|
+
async execute(_id, params) {
|
|
834
|
+
return ok(await net.getBalance(params.agent_id));
|
|
835
|
+
},
|
|
836
|
+
});
|
|
837
|
+
// #31 eacn3_deposit
|
|
838
|
+
api.registerTool({
|
|
839
|
+
name: "eacn3_deposit",
|
|
840
|
+
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.",
|
|
841
|
+
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"] },
|
|
842
|
+
async execute(_id, params) {
|
|
843
|
+
return ok(await net.deposit(params.agent_id, params.amount));
|
|
844
|
+
},
|
|
845
|
+
});
|
|
846
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
847
|
+
// Events (1)
|
|
848
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
849
|
+
// #32 eacn3_get_events
|
|
850
|
+
api.registerTool({
|
|
851
|
+
name: "eacn3_get_events",
|
|
852
|
+
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.",
|
|
853
|
+
parameters: { type: "object", properties: {} },
|
|
854
|
+
async execute() {
|
|
855
|
+
const events = state.drainEvents();
|
|
856
|
+
return ok({ count: events.length, events });
|
|
857
|
+
},
|
|
858
|
+
});
|
|
859
|
+
},
|
|
860
|
+
};
|
|
762
861
|
//# sourceMappingURL=index.js.map
|