overmind-mcp 3.6.0 → 3.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/services/AgentManager.d.ts.map +1 -1
- package/dist/services/AgentManager.js +8 -39
- package/dist/services/AgentManager.js.map +1 -1
- package/dist/services/HermesPoolClient.d.ts +105 -0
- package/dist/services/HermesPoolClient.d.ts.map +1 -0
- package/dist/services/HermesPoolClient.js +190 -0
- package/dist/services/HermesPoolClient.js.map +1 -0
- package/dist/services/HermesProfileManager.d.ts +14 -5
- package/dist/services/HermesProfileManager.d.ts.map +1 -1
- package/dist/services/HermesProfileManager.js +59 -38
- package/dist/services/HermesProfileManager.js.map +1 -1
- package/dist/tools/a2a_hub.d.ts +17 -11
- package/dist/tools/a2a_hub.d.ts.map +1 -1
- package/dist/tools/a2a_hub.js +330 -312
- package/dist/tools/a2a_hub.js.map +1 -1
- package/docs/overmind-bridge-guide.md +665 -0
- package/package.json +1 -1
- package/scripts/test-pool-client.ts +27 -0
- package/scripts/test-registry.ts +22 -0
- package/docs/agent-http-tutorial.md +0 -524
- package/docs/doc_guide_agent_hermes_permanent.md +0 -315
- package/docs/guide_agent_hermes_overmind.md +0 -403
- package/docs/overmind-bridge-commands-reference.md +0 -438
- package/docs/overmind-bridge-persistent-agents.md +0 -463
package/dist/tools/a2a_hub.js
CHANGED
|
@@ -3,137 +3,203 @@
|
|
|
3
3
|
* A2A Hub — Outil MCP pour la communication Agent-to-Agent
|
|
4
4
|
* ═══════════════════════════════════════════════════════════════════════════
|
|
5
5
|
*
|
|
6
|
-
* Architecture
|
|
6
|
+
* Architecture distribuée — chaque bridge est un serveur HTTP indépendant:
|
|
7
7
|
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
8
|
+
* a2a_hub parle DIRECTEMENT aux bridges HTTP:
|
|
9
|
+
* POST :31XX/rpc → JSON-RPC 2.0 agent.run (NEXUS bridges)
|
|
10
|
+
* POST :30XX/send → legacy message synchrone (discord_llm bridge)
|
|
11
|
+
* GET :XXXX/health → status live
|
|
10
12
|
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
13
|
+
* Découverte automatique:
|
|
14
|
+
* 1. Scan ports 3101-3120 (NEXUS bridges) + 3001-3020 (legacy workers)
|
|
15
|
+
* 2. Scan ~/.overmind/hermes/profiles/ (Linux) + ~/AppData/Local/hermes/profiles/ (Windows)
|
|
16
|
+
* 3. Cross-reference: profile ↔ bridge port
|
|
17
|
+
* 4. Lit Nexus/tmp/bridge_pids.json si présent
|
|
14
18
|
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
19
|
+
* PROD FIXES (2026-07-10):
|
|
20
|
+
* - fetch() async au lieu de execSync('curl') — ne bloque plus l'event loop
|
|
21
|
+
* - Vrai parallélisme pour fanout/broadcast (Promise.all)
|
|
22
|
+
* - Support dual: POST /rpc (JSON-RPC) + POST /send (legacy)
|
|
23
|
+
* - Port scan: 3101-3120 (NEXUS) + 3001-3020 (legacy)
|
|
24
|
+
* - Profils Hermes: ~/.overmind/hermes/ + ~/AppData/Local/hermes/
|
|
25
|
+
* - 127.0.0.1 au lieu de localhost (IPv4/IPv6 fix)
|
|
20
26
|
*/
|
|
21
27
|
import { z } from 'zod';
|
|
22
|
-
import { execSync } from 'child_process';
|
|
23
28
|
import fs from 'fs';
|
|
24
29
|
import path from 'path';
|
|
25
30
|
import os from 'os';
|
|
26
|
-
|
|
27
|
-
|
|
31
|
+
import { randomUUID } from 'crypto';
|
|
32
|
+
// ─── HTTP Helpers (async fetch — non-blocking) ─────────────────────────────
|
|
33
|
+
async function httpGet(url, timeoutMs = 5000) {
|
|
28
34
|
try {
|
|
29
|
-
const
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
35
|
+
const controller = new AbortController();
|
|
36
|
+
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
37
|
+
const response = await fetch(url, { method: 'GET', signal: controller.signal });
|
|
38
|
+
clearTimeout(timer);
|
|
39
|
+
if (!response.ok)
|
|
40
|
+
return null;
|
|
41
|
+
const text = await response.text();
|
|
42
|
+
return JSON.parse(text);
|
|
34
43
|
}
|
|
35
44
|
catch {
|
|
36
45
|
return null;
|
|
37
46
|
}
|
|
38
47
|
}
|
|
39
|
-
function httpPost(url, body, timeoutMs = 300000) {
|
|
40
|
-
const payload = JSON.stringify(body);
|
|
48
|
+
async function httpPost(url, body, timeoutMs = 300000) {
|
|
41
49
|
try {
|
|
42
|
-
const
|
|
43
|
-
const
|
|
44
|
-
|
|
50
|
+
const controller = new AbortController();
|
|
51
|
+
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
52
|
+
const response = await fetch(url, {
|
|
53
|
+
method: 'POST',
|
|
54
|
+
headers: { 'Content-Type': 'application/json' },
|
|
55
|
+
body: JSON.stringify(body),
|
|
56
|
+
signal: controller.signal,
|
|
57
|
+
});
|
|
58
|
+
clearTimeout(timer);
|
|
59
|
+
if (!response.ok)
|
|
60
|
+
return null;
|
|
61
|
+
const text = await response.text();
|
|
62
|
+
return JSON.parse(text);
|
|
45
63
|
}
|
|
46
64
|
catch {
|
|
47
65
|
return null;
|
|
48
66
|
}
|
|
49
67
|
}
|
|
50
|
-
// ───
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
const
|
|
54
|
-
|
|
55
|
-
|
|
68
|
+
// ─── Bridge Discovery ──────────────────────────────────────────────────────
|
|
69
|
+
/** Get candidate Hermes profile directories (Windows + Linux) */
|
|
70
|
+
function getProfileDirs() {
|
|
71
|
+
const dirs = [];
|
|
72
|
+
const home = os.homedir();
|
|
73
|
+
// Linux/Ubuntu: ~/.overmind/hermes/profiles/
|
|
74
|
+
const overmindHermes = path.join(home, '.overmind', 'hermes', 'profiles');
|
|
75
|
+
if (fs.existsSync(overmindHermes))
|
|
76
|
+
dirs.push(overmindHermes);
|
|
77
|
+
// Windows: ~/AppData/Local/hermes/profiles/
|
|
78
|
+
const winHermes = path.join(home, 'AppData', 'Local', 'hermes', 'profiles');
|
|
79
|
+
if (fs.existsSync(winHermes))
|
|
80
|
+
dirs.push(winHermes);
|
|
81
|
+
// Linux native: ~/.hermes/profiles/
|
|
82
|
+
const nativeHermes = path.join(home, '.hermes', 'profiles');
|
|
83
|
+
if (fs.existsSync(nativeHermes))
|
|
84
|
+
dirs.push(nativeHermes);
|
|
85
|
+
return dirs;
|
|
86
|
+
}
|
|
87
|
+
/** Get known bridge ports from pid files */
|
|
88
|
+
function getKnownPorts() {
|
|
89
|
+
const known = [];
|
|
90
|
+
const home = os.homedir();
|
|
91
|
+
// 1. NEXUS pid file: Nexus/tmp/bridge_pids.json
|
|
92
|
+
const nexusPidPath = path.join(home, 'Nexus', 'tmp', 'bridge_pids.json');
|
|
93
|
+
if (fs.existsSync(nexusPidPath)) {
|
|
94
|
+
try {
|
|
95
|
+
const data = JSON.parse(fs.readFileSync(nexusPidPath, 'utf8'));
|
|
96
|
+
if (Array.isArray(data)) {
|
|
97
|
+
for (const entry of data) {
|
|
98
|
+
if (entry.port && entry.name) {
|
|
99
|
+
known.push({ port: entry.port, agentName: entry.name });
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
else if (data.workers && Array.isArray(data.workers)) {
|
|
104
|
+
for (const w of data.workers) {
|
|
105
|
+
if (w.port)
|
|
106
|
+
known.push({ port: w.port, agentName: w.agentName || '' });
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
catch { /* ignore */ }
|
|
111
|
+
}
|
|
112
|
+
// 2. Legacy workers.json: ~/.overmind/bridge/workers.json
|
|
113
|
+
const workersJsonPath = path.join(home, '.overmind', 'bridge', 'workers.json');
|
|
56
114
|
if (fs.existsSync(workersJsonPath)) {
|
|
57
115
|
try {
|
|
58
116
|
const data = JSON.parse(fs.readFileSync(workersJsonPath, 'utf8'));
|
|
59
117
|
if (Array.isArray(data.workers)) {
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
}
|
|
118
|
+
for (const w of data.workers) {
|
|
119
|
+
if (w.port)
|
|
120
|
+
known.push({ port: w.port, agentName: w.agentName || '' });
|
|
121
|
+
}
|
|
64
122
|
}
|
|
65
123
|
}
|
|
66
|
-
catch {
|
|
67
|
-
// ignore
|
|
68
|
-
}
|
|
124
|
+
catch { /* ignore */ }
|
|
69
125
|
}
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
126
|
+
return known;
|
|
127
|
+
}
|
|
128
|
+
/** Probe a single port for bridge health */
|
|
129
|
+
async function probeBridge(port, agentName) {
|
|
130
|
+
const url = `http://127.0.0.1:${port}`;
|
|
131
|
+
const health = await httpGet(`${url}/health`, 3000);
|
|
132
|
+
if (health) {
|
|
133
|
+
const detectedAgent = agentName ||
|
|
134
|
+
health.agent ||
|
|
135
|
+
health.agentName ||
|
|
136
|
+
health.service ||
|
|
137
|
+
'unknown';
|
|
138
|
+
// NEXUS bridges expose rpcMethods[] in health — they support POST /rpc
|
|
139
|
+
const hasRpc = Array.isArray(health.rpcMethods) || 'jsonrpc' in health;
|
|
140
|
+
return { port, url, online: true, agentName: detectedAgent, health, hasRpc };
|
|
76
141
|
}
|
|
77
|
-
|
|
142
|
+
return { port, url, online: false, agentName: agentName || '', hasRpc: false };
|
|
143
|
+
}
|
|
144
|
+
/** Discover all bridges (scan ports + pid files) */
|
|
145
|
+
async function discoverBridges() {
|
|
146
|
+
const knownPorts = getKnownPorts();
|
|
147
|
+
// Build port list: known ports + scan ranges
|
|
148
|
+
const portSet = new Set();
|
|
149
|
+
const portAgentMap = new Map();
|
|
150
|
+
// Add known ports from pid files
|
|
78
151
|
for (const { port, agentName } of knownPorts) {
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
// Worker is online — extract agent name from health
|
|
83
|
-
const detectedAgent = agentName ||
|
|
84
|
-
health.agent ||
|
|
85
|
-
health.agentName ||
|
|
86
|
-
health.service ||
|
|
87
|
-
'unknown';
|
|
88
|
-
workers.push({
|
|
89
|
-
port,
|
|
90
|
-
url,
|
|
91
|
-
online: true,
|
|
92
|
-
agentName: detectedAgent,
|
|
93
|
-
health,
|
|
94
|
-
});
|
|
95
|
-
}
|
|
96
|
-
else {
|
|
97
|
-
// Check if port is listening at all
|
|
98
|
-
workers.push({
|
|
99
|
-
port,
|
|
100
|
-
url,
|
|
101
|
-
online: false,
|
|
102
|
-
agentName: agentName || '',
|
|
103
|
-
});
|
|
104
|
-
}
|
|
152
|
+
portSet.add(port);
|
|
153
|
+
if (agentName)
|
|
154
|
+
portAgentMap.set(port, agentName);
|
|
105
155
|
}
|
|
106
|
-
|
|
156
|
+
// NEXUS range: 3101-3120
|
|
157
|
+
for (let p = 3101; p <= 3120; p++)
|
|
158
|
+
portSet.add(p);
|
|
159
|
+
// Legacy range: 3001-3020
|
|
160
|
+
for (let p = 3001; p <= 3020; p++)
|
|
161
|
+
portSet.add(p);
|
|
162
|
+
// Probe all ports in parallel (true parallel — async fetch)
|
|
163
|
+
const ports = Array.from(portSet).sort((a, b) => a - b);
|
|
164
|
+
const results = await Promise.all(ports.map((port) => probeBridge(port, portAgentMap.get(port) || '')));
|
|
165
|
+
return results;
|
|
107
166
|
}
|
|
108
167
|
// ─── Agent Discovery ───────────────────────────────────────────────────────
|
|
109
|
-
function discoverAgents() {
|
|
110
|
-
const home = os.homedir();
|
|
111
|
-
const profilesDir = path.join(home, '.overmind', 'hermes', 'profiles');
|
|
168
|
+
async function discoverAgents() {
|
|
112
169
|
const selfAgent = process.env.OVERMIND_AGENT_NAME || null;
|
|
113
170
|
const agents = [];
|
|
114
|
-
const
|
|
115
|
-
// Build
|
|
116
|
-
const
|
|
117
|
-
for (const
|
|
118
|
-
if (
|
|
119
|
-
|
|
171
|
+
const bridges = await discoverBridges();
|
|
172
|
+
// Build bridge lookup: agentName → bridge
|
|
173
|
+
const bridgeByAgent = new Map();
|
|
174
|
+
for (const b of bridges) {
|
|
175
|
+
if (b.online && b.agentName && b.agentName !== 'unknown') {
|
|
176
|
+
bridgeByAgent.set(b.agentName, b);
|
|
120
177
|
}
|
|
121
178
|
}
|
|
122
|
-
// Scan
|
|
123
|
-
|
|
124
|
-
|
|
179
|
+
// Scan all profile directories
|
|
180
|
+
for (const profilesDir of getProfileDirs()) {
|
|
181
|
+
let entries;
|
|
182
|
+
try {
|
|
183
|
+
entries = fs.readdirSync(profilesDir, { withFileTypes: true });
|
|
184
|
+
}
|
|
185
|
+
catch {
|
|
186
|
+
continue;
|
|
187
|
+
}
|
|
125
188
|
for (const entry of entries) {
|
|
126
189
|
if (!entry.isDirectory())
|
|
127
190
|
continue;
|
|
128
191
|
const profilePath = path.join(profilesDir, entry.name);
|
|
192
|
+
// Avoid duplicates (same profile name in different dirs)
|
|
193
|
+
if (agents.some((a) => a.name === entry.name))
|
|
194
|
+
continue;
|
|
129
195
|
// Read config.yaml
|
|
130
196
|
const configPath = path.join(profilePath, 'config.yaml');
|
|
131
197
|
let model = 'unknown';
|
|
132
198
|
let provider = 'unknown';
|
|
133
199
|
if (fs.existsSync(configPath)) {
|
|
134
|
-
const
|
|
135
|
-
const modelMatch =
|
|
136
|
-
const providerMatch =
|
|
200
|
+
const configContent = fs.readFileSync(configPath, 'utf8');
|
|
201
|
+
const modelMatch = configContent.match(/^\s*model:\s*(?!provider)(.+)/m);
|
|
202
|
+
const providerMatch = configContent.match(/^\s*provider:\s*(.+)/m);
|
|
137
203
|
if (modelMatch)
|
|
138
204
|
model = modelMatch[1].trim().replace(/['"]/g, '');
|
|
139
205
|
if (providerMatch)
|
|
@@ -156,43 +222,38 @@ function discoverAgents() {
|
|
|
156
222
|
const walk = (dir) => {
|
|
157
223
|
let count = 0;
|
|
158
224
|
for (const f of fs.readdirSync(dir, { withFileTypes: true })) {
|
|
159
|
-
if (f.isDirectory())
|
|
225
|
+
if (f.isDirectory())
|
|
160
226
|
count += walk(path.join(dir, f.name));
|
|
161
|
-
|
|
162
|
-
else if (f.name === 'SKILL.md') {
|
|
227
|
+
else if (f.name === 'SKILL.md')
|
|
163
228
|
count++;
|
|
164
|
-
}
|
|
165
229
|
}
|
|
166
230
|
return count;
|
|
167
231
|
};
|
|
168
232
|
skillsCount = walk(skillsDir);
|
|
169
233
|
}
|
|
170
|
-
catch {
|
|
171
|
-
// ignore
|
|
172
|
-
}
|
|
234
|
+
catch { /* ignore */ }
|
|
173
235
|
}
|
|
174
|
-
// Check memory
|
|
175
|
-
const hasMemory = fs.existsSync(path.join(profilePath, 'memories', '
|
|
236
|
+
// Check memory (state.db or MEMORY.md)
|
|
237
|
+
const hasMemory = fs.existsSync(path.join(profilePath, 'memories', 'state.db')) ||
|
|
238
|
+
fs.existsSync(path.join(profilePath, 'memories', 'MEMORY.md'));
|
|
176
239
|
// Last activity
|
|
177
240
|
let lastActivity = null;
|
|
178
|
-
const stateDb = path.join(profilePath, 'state.db');
|
|
241
|
+
const stateDb = path.join(profilePath, 'memories', 'state.db');
|
|
179
242
|
if (fs.existsSync(stateDb)) {
|
|
180
243
|
try {
|
|
181
244
|
lastActivity = fs.statSync(stateDb).mtime.toISOString();
|
|
182
245
|
}
|
|
183
|
-
catch {
|
|
184
|
-
// ignore
|
|
185
|
-
}
|
|
246
|
+
catch { /* ignore */ }
|
|
186
247
|
}
|
|
187
|
-
// Cross-reference with
|
|
188
|
-
const
|
|
248
|
+
// Cross-reference with bridges
|
|
249
|
+
const bridge = bridgeByAgent.get(entry.name);
|
|
189
250
|
agents.push({
|
|
190
251
|
name: entry.name,
|
|
191
252
|
model,
|
|
192
253
|
provider,
|
|
193
|
-
status:
|
|
194
|
-
|
|
195
|
-
|
|
254
|
+
status: bridge ? 'online' : 'offline',
|
|
255
|
+
bridgePort: bridge?.port ?? null,
|
|
256
|
+
bridgeUrl: bridge?.url ?? null,
|
|
196
257
|
description,
|
|
197
258
|
skillsCount,
|
|
198
259
|
hasMemory,
|
|
@@ -202,35 +263,64 @@ function discoverAgents() {
|
|
|
202
263
|
}
|
|
203
264
|
return {
|
|
204
265
|
totalAgents: agents.length,
|
|
205
|
-
|
|
266
|
+
onlineBridges: bridges.filter((b) => b.online).length,
|
|
206
267
|
agents,
|
|
207
|
-
|
|
268
|
+
bridges,
|
|
208
269
|
selfAgent,
|
|
209
270
|
};
|
|
210
271
|
}
|
|
211
|
-
// ─── Send message to a
|
|
212
|
-
function
|
|
213
|
-
const
|
|
214
|
-
const
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
272
|
+
// ─── Send message to a bridge (supports /rpc + /send) ──────────────────────
|
|
273
|
+
async function sendToBridge(bridge, message, opts = {}) {
|
|
274
|
+
const selfAgent = process.env.OVERMIND_AGENT_NAME || 'A2A Hub';
|
|
275
|
+
const enrichedMessage = `[A2A — Message from ${selfAgent}]\n${message}`;
|
|
276
|
+
const timeout = opts.timeoutMs ?? 300000;
|
|
277
|
+
// Strategy 1: NEXUS bridge with POST /rpc (JSON-RPC 2.0)
|
|
278
|
+
if (bridge.hasRpc) {
|
|
279
|
+
const rpcBody = {
|
|
280
|
+
jsonrpc: '2.0',
|
|
281
|
+
id: randomUUID(),
|
|
282
|
+
method: 'agent.run',
|
|
283
|
+
params: {
|
|
284
|
+
agentName: bridge.agentName,
|
|
285
|
+
runner: 'hermes',
|
|
286
|
+
prompt: enrichedMessage,
|
|
287
|
+
...(opts.model ? { model: opts.model } : {}),
|
|
288
|
+
},
|
|
289
|
+
};
|
|
290
|
+
const response = await httpPost(`${bridge.url}/rpc`, rpcBody, timeout);
|
|
291
|
+
if (response === null) {
|
|
292
|
+
return { success: false, text: `Bridge :${bridge.port} injoignable`, raw: null };
|
|
293
|
+
}
|
|
294
|
+
if (response.error) {
|
|
295
|
+
return { success: false, text: `Erreur :${bridge.port}: ${response.error.message || JSON.stringify(response.error)}`, raw: response };
|
|
296
|
+
}
|
|
297
|
+
// Extract text from JSON-RPC result
|
|
298
|
+
const result = response.result;
|
|
299
|
+
if (result) {
|
|
300
|
+
const content = result.content;
|
|
301
|
+
const text = content
|
|
302
|
+
? content.map((c) => c.text || '').join('\n')
|
|
303
|
+
: result.output || result.text || JSON.stringify(result);
|
|
304
|
+
return { success: true, text, raw: response };
|
|
305
|
+
}
|
|
306
|
+
return { success: true, text: JSON.stringify(response), raw: response };
|
|
307
|
+
}
|
|
308
|
+
// Strategy 2: Legacy bridge with POST /send
|
|
309
|
+
const sendBody = {
|
|
310
|
+
message: enrichedMessage,
|
|
311
|
+
userId: `a2a_${selfAgent}`,
|
|
312
|
+
username: selfAgent,
|
|
218
313
|
channelId: 'a2a',
|
|
219
314
|
};
|
|
220
315
|
if (opts.model)
|
|
221
|
-
|
|
222
|
-
const response = httpPost(url
|
|
316
|
+
sendBody.model = opts.model;
|
|
317
|
+
const response = await httpPost(`${bridge.url}/send`, sendBody, timeout);
|
|
223
318
|
if (response === null) {
|
|
224
|
-
return { success: false, text: `
|
|
319
|
+
return { success: false, text: `Bridge :${bridge.port} injoignable`, raw: null };
|
|
225
320
|
}
|
|
226
321
|
if (response.error) {
|
|
227
|
-
return {
|
|
228
|
-
success: false,
|
|
229
|
-
text: `Erreur :${port}: ${response.error}`,
|
|
230
|
-
raw: response,
|
|
231
|
-
};
|
|
322
|
+
return { success: false, text: `Erreur :${bridge.port}: ${response.error}`, raw: response };
|
|
232
323
|
}
|
|
233
|
-
// Workers may return { result, content, response, output }
|
|
234
324
|
const text = response.result ||
|
|
235
325
|
response.response ||
|
|
236
326
|
response.output ||
|
|
@@ -244,11 +334,11 @@ function sendToWorker(port, message, opts = {}) {
|
|
|
244
334
|
export const a2aHubSchema = z.object({
|
|
245
335
|
action: z
|
|
246
336
|
.enum(['discover', 'status', 'send', 'delegate', 'pipeline', 'fanout', 'query', 'broadcast'])
|
|
247
|
-
.describe("Action: discover=liste tous les agents+
|
|
337
|
+
.describe("Action: discover=liste tous les agents+bridges, status=état d'un bridge, send=message synchrone, delegate=async, pipeline=chaîne A→B→C, fanout=1→N+merge, query=multi-agents, broadcast=global"),
|
|
248
338
|
target: z
|
|
249
339
|
.string()
|
|
250
340
|
.optional()
|
|
251
|
-
.describe("Nom de l'agent cible (ex: '
|
|
341
|
+
.describe("Nom de l'agent cible (ex: 'nexus_master') OU port du bridge (ex: '3101')"),
|
|
252
342
|
message: z.string().optional().describe('Le message/prompt à envoyer aux agents'),
|
|
253
343
|
targets: z
|
|
254
344
|
.array(z.string())
|
|
@@ -258,7 +348,7 @@ export const a2aHubSchema = z.object({
|
|
|
258
348
|
.array(z.object({
|
|
259
349
|
agentName: z
|
|
260
350
|
.string()
|
|
261
|
-
.describe("Nom de l'agent ou port (ex: '
|
|
351
|
+
.describe("Nom de l'agent ou port (ex: 'nexus_master' ou '3101')"),
|
|
262
352
|
promptPrefix: z.string().optional(),
|
|
263
353
|
}))
|
|
264
354
|
.optional()
|
|
@@ -292,26 +382,27 @@ export const a2aHubSchema = z.object({
|
|
|
292
382
|
.describe('Timeout par agent en ms (default: 300000 = 5min)'),
|
|
293
383
|
model: z.string().optional().describe('Modèle LLM override'),
|
|
294
384
|
});
|
|
295
|
-
// ─── Resolve agent name to
|
|
385
|
+
// ─── Resolve agent name to bridge ──────────────────────────────────────────
|
|
296
386
|
function resolveTarget(target, discovery) {
|
|
297
387
|
// If target is a port number
|
|
298
388
|
const portMatch = target.match(/^(\d{4,5})$/);
|
|
299
389
|
if (portMatch) {
|
|
300
390
|
const port = parseInt(portMatch[1], 10);
|
|
301
|
-
const
|
|
302
|
-
return {
|
|
391
|
+
const bridge = discovery.bridges.find((b) => b.port === port);
|
|
392
|
+
return { bridge: bridge ?? null, agentName: bridge?.agentName || target };
|
|
303
393
|
}
|
|
304
|
-
// If target is an agent name, find its
|
|
394
|
+
// If target is an agent name, find its bridge
|
|
305
395
|
const agent = discovery.agents.find((a) => a.name === target);
|
|
306
|
-
if (agent?.
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
const worker = discovery.workers.find((w) => w.online && w.agentName === target);
|
|
311
|
-
if (worker) {
|
|
312
|
-
return { port: worker.port, agentName: target };
|
|
396
|
+
if (agent?.bridgePort) {
|
|
397
|
+
const bridge = discovery.bridges.find((b) => b.port === agent.bridgePort);
|
|
398
|
+
if (bridge)
|
|
399
|
+
return { bridge, agentName: target };
|
|
313
400
|
}
|
|
314
|
-
|
|
401
|
+
// Try online bridges by agentName
|
|
402
|
+
const bridge = discovery.bridges.find((b) => b.online && b.agentName === target);
|
|
403
|
+
if (bridge)
|
|
404
|
+
return { bridge, agentName: target };
|
|
405
|
+
return { bridge: null, agentName: target };
|
|
315
406
|
}
|
|
316
407
|
// ─── Execute ───────────────────────────────────────────────────────────────
|
|
317
408
|
export async function a2aHub(args) {
|
|
@@ -323,47 +414,47 @@ export async function a2aHub(args) {
|
|
|
323
414
|
// DISCOVER
|
|
324
415
|
// ═══════════════════════════════════════════════════════════════════════
|
|
325
416
|
case 'discover': {
|
|
326
|
-
const d = discoverAgents();
|
|
417
|
+
const d = await discoverAgents();
|
|
327
418
|
const lines = [
|
|
328
419
|
`🌐 **A2A Hub — Découverte du système multi-agents**`,
|
|
329
420
|
``,
|
|
330
421
|
`**Self:** ${d.selfAgent || '(inconnu)'}`,
|
|
331
|
-
`**
|
|
422
|
+
`**Bridges online:** ${d.onlineBridges}`,
|
|
332
423
|
`**Total agents:** ${d.totalAgents}`,
|
|
333
424
|
``,
|
|
334
|
-
`###
|
|
425
|
+
`### Bridges HTTP`,
|
|
335
426
|
``,
|
|
336
427
|
];
|
|
337
|
-
const
|
|
338
|
-
if (
|
|
339
|
-
lines.push('Aucun
|
|
428
|
+
const onlineBridges = d.bridges.filter((b) => b.online);
|
|
429
|
+
if (onlineBridges.length === 0) {
|
|
430
|
+
lines.push('Aucun bridge HTTP en ligne.');
|
|
340
431
|
}
|
|
341
432
|
else {
|
|
342
|
-
lines.push('| Port | Agent |
|
|
343
|
-
lines.push('
|
|
344
|
-
for (const
|
|
345
|
-
|
|
433
|
+
lines.push('| Port | Agent | Type | URL |');
|
|
434
|
+
lines.push('|------|-------|------|-----|');
|
|
435
|
+
for (const b of onlineBridges) {
|
|
436
|
+
const type = b.hasRpc ? 'RPC' : 'Legacy';
|
|
437
|
+
lines.push(`| :${b.port} | ${b.agentName} | ${type} | ${b.url} |`);
|
|
346
438
|
}
|
|
347
439
|
}
|
|
348
440
|
lines.push('');
|
|
349
|
-
lines.push('### Agents
|
|
441
|
+
lines.push('### Agents');
|
|
350
442
|
lines.push('');
|
|
351
443
|
if (d.agents.length === 0) {
|
|
352
|
-
lines.push('Aucun agent
|
|
444
|
+
lines.push('Aucun agent trouvé.');
|
|
353
445
|
}
|
|
354
446
|
else {
|
|
355
|
-
lines.push('| Agent | Status |
|
|
447
|
+
lines.push('| Agent | Status | Bridge | Model | Provider | Skills | Description |');
|
|
356
448
|
lines.push('|-------|--------|--------|-------|----------|--------|-------------|');
|
|
357
449
|
for (const a of d.agents) {
|
|
358
450
|
const statusIcon = a.status === 'online' ? '🟢' : '🔴';
|
|
359
|
-
const
|
|
451
|
+
const bridgeInfo = a.bridgePort ? `:${a.bridgePort}` : '—';
|
|
360
452
|
const nameDisplay = a.name === selfAgent ? `**${a.name} (self)**` : a.name;
|
|
361
|
-
lines.push(`| ${nameDisplay} | ${statusIcon} ${a.status} | ${
|
|
453
|
+
lines.push(`| ${nameDisplay} | ${statusIcon} ${a.status} | ${bridgeInfo} | ${a.model} | ${a.provider} | ${a.skillsCount} | ${a.description.slice(0, 40)} |`);
|
|
362
454
|
}
|
|
363
455
|
}
|
|
364
456
|
lines.push('');
|
|
365
457
|
lines.push('**Actions disponibles:** send, delegate, pipeline, fanout, query, broadcast');
|
|
366
|
-
lines.push('**Exemple:** a2a_hub(action: "send", target: "sniperbot_analyst", message: "Analyse BTC")');
|
|
367
458
|
return { content: [{ type: 'text', text: lines.join('\n') }] };
|
|
368
459
|
}
|
|
369
460
|
// ═══════════════════════════════════════════════════════════════════════
|
|
@@ -376,25 +467,16 @@ export async function a2aHub(args) {
|
|
|
376
467
|
isError: true,
|
|
377
468
|
};
|
|
378
469
|
}
|
|
379
|
-
const d = discoverAgents();
|
|
380
|
-
const {
|
|
381
|
-
if (!
|
|
470
|
+
const d = await discoverAgents();
|
|
471
|
+
const { bridge, agentName } = resolveTarget(args.target, d);
|
|
472
|
+
if (!bridge || !bridge.online) {
|
|
382
473
|
return {
|
|
383
|
-
content: [
|
|
384
|
-
{ type: 'text', text: `❌ Worker pour "${args.target}" introuvable` },
|
|
385
|
-
],
|
|
474
|
+
content: [{ type: 'text', text: `❌ Bridge pour "${args.target}" introuvable` }],
|
|
386
475
|
isError: true,
|
|
387
476
|
};
|
|
388
477
|
}
|
|
389
|
-
const
|
|
390
|
-
|
|
391
|
-
return {
|
|
392
|
-
content: [{ type: 'text', text: `❌ Worker :${port} injoignable` }],
|
|
393
|
-
isError: true,
|
|
394
|
-
};
|
|
395
|
-
}
|
|
396
|
-
const lines = [`📊 **Status: ${agentName} (:${port})**`, ``];
|
|
397
|
-
for (const [key, value] of Object.entries(health)) {
|
|
478
|
+
const lines = [`📊 **Status: ${agentName} (:${bridge.port})**`, ``];
|
|
479
|
+
for (const [key, value] of Object.entries(bridge.health ?? {})) {
|
|
398
480
|
lines.push(`**${key}:** ${typeof value === 'object' ? JSON.stringify(value).slice(0, 100) : value}`);
|
|
399
481
|
}
|
|
400
482
|
return { content: [{ type: 'text', text: lines.join('\n') }] };
|
|
@@ -409,21 +491,15 @@ export async function a2aHub(args) {
|
|
|
409
491
|
isError: true,
|
|
410
492
|
};
|
|
411
493
|
}
|
|
412
|
-
const d = discoverAgents();
|
|
413
|
-
const {
|
|
414
|
-
if (!
|
|
494
|
+
const d = await discoverAgents();
|
|
495
|
+
const { bridge, agentName } = resolveTarget(args.target, d);
|
|
496
|
+
if (!bridge || !bridge.online) {
|
|
415
497
|
return {
|
|
416
|
-
content: [
|
|
417
|
-
{
|
|
418
|
-
type: 'text',
|
|
419
|
-
text: `❌ Worker pour "${args.target}" introuvable. Utilisez action=discover pour lister les agents.`,
|
|
420
|
-
},
|
|
421
|
-
],
|
|
498
|
+
content: [{ type: 'text', text: `❌ Bridge pour "${args.target}" introuvable. Utilisez action=discover.` }],
|
|
422
499
|
isError: true,
|
|
423
500
|
};
|
|
424
501
|
}
|
|
425
|
-
const
|
|
426
|
-
const result = sendToWorker(port, enrichedMessage, {
|
|
502
|
+
const result = await sendToBridge(bridge, args.message, {
|
|
427
503
|
model: args.model,
|
|
428
504
|
timeoutMs: timeout,
|
|
429
505
|
});
|
|
@@ -434,12 +510,7 @@ export async function a2aHub(args) {
|
|
|
434
510
|
};
|
|
435
511
|
}
|
|
436
512
|
return {
|
|
437
|
-
content: [
|
|
438
|
-
{
|
|
439
|
-
type: 'text',
|
|
440
|
-
text: `📤 **Message envoyé à ${agentName} (:${port})**\n\n${result.text}`,
|
|
441
|
-
},
|
|
442
|
-
],
|
|
513
|
+
content: [{ type: 'text', text: `📤 **Message envoyé à ${agentName} (:${bridge.port})**\n\n${result.text}` }],
|
|
443
514
|
};
|
|
444
515
|
}
|
|
445
516
|
// ═══════════════════════════════════════════════════════════════════════
|
|
@@ -452,41 +523,22 @@ export async function a2aHub(args) {
|
|
|
452
523
|
isError: true,
|
|
453
524
|
};
|
|
454
525
|
}
|
|
455
|
-
const d = discoverAgents();
|
|
456
|
-
const {
|
|
457
|
-
if (!
|
|
526
|
+
const d = await discoverAgents();
|
|
527
|
+
const { bridge, agentName } = resolveTarget(args.target, d);
|
|
528
|
+
if (!bridge || !bridge.online) {
|
|
458
529
|
return {
|
|
459
|
-
content: [
|
|
460
|
-
{ type: 'text', text: `❌ Worker pour "${args.target}" introuvable` },
|
|
461
|
-
],
|
|
530
|
+
content: [{ type: 'text', text: `❌ Bridge pour "${args.target}" introuvable` }],
|
|
462
531
|
isError: true,
|
|
463
532
|
};
|
|
464
533
|
}
|
|
465
|
-
// Fire and forget — don't
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
const body = {
|
|
469
|
-
message: enrichedMessage,
|
|
470
|
-
userId: `a2a_${selfAgent}`,
|
|
471
|
-
username: selfAgent,
|
|
472
|
-
channelId: 'a2a_delegate',
|
|
473
|
-
...(args.model ? { model: args.model } : {}),
|
|
474
|
-
};
|
|
475
|
-
// Spawn curl in background (fire-and-forget)
|
|
476
|
-
try {
|
|
477
|
-
const payload = JSON.stringify(body).replace(/'/g, "'\\''");
|
|
478
|
-
execSync(`nohup curl -s -m ${Math.floor(timeout / 1000)} -X POST "${url}" -H "Content-Type: application/json" -d '${payload}' > /dev/null 2>&1 &`, { timeout: 2000 });
|
|
479
|
-
}
|
|
480
|
-
catch {
|
|
481
|
-
// ignore — fire and forget
|
|
482
|
-
}
|
|
534
|
+
// Fire and forget — don't await
|
|
535
|
+
void sendToBridge(bridge, args.message, { model: args.model, timeoutMs: timeout })
|
|
536
|
+
.catch(() => { });
|
|
483
537
|
return {
|
|
484
|
-
content: [
|
|
485
|
-
{
|
|
538
|
+
content: [{
|
|
486
539
|
type: 'text',
|
|
487
|
-
text: `🤝 **Tâche déléguée à ${agentName} (:${port})**\n\nLe
|
|
488
|
-
},
|
|
489
|
-
],
|
|
540
|
+
text: `🤝 **Tâche déléguée à ${agentName} (:${bridge.port})**\n\nLe bridge traite la requête en arrière-plan.`,
|
|
541
|
+
}],
|
|
490
542
|
};
|
|
491
543
|
}
|
|
492
544
|
// ═══════════════════════════════════════════════════════════════════════
|
|
@@ -495,30 +547,25 @@ export async function a2aHub(args) {
|
|
|
495
547
|
case 'pipeline': {
|
|
496
548
|
if (!args.message || !args.steps || args.steps.length === 0) {
|
|
497
549
|
return {
|
|
498
|
-
content: [
|
|
499
|
-
{
|
|
500
|
-
type: 'text',
|
|
501
|
-
text: '❌ `message` (prompt initial) et `steps` requis pour pipeline',
|
|
502
|
-
},
|
|
503
|
-
],
|
|
550
|
+
content: [{ type: 'text', text: '❌ `message` (prompt initial) et `steps` requis' }],
|
|
504
551
|
isError: true,
|
|
505
552
|
};
|
|
506
553
|
}
|
|
507
|
-
const d = discoverAgents();
|
|
554
|
+
const d = await discoverAgents();
|
|
508
555
|
const outputs = [];
|
|
509
556
|
let currentPrompt = args.message;
|
|
510
557
|
for (let i = 0; i < args.steps.length; i++) {
|
|
511
558
|
const step = args.steps[i];
|
|
512
|
-
const {
|
|
513
|
-
if (!
|
|
514
|
-
outputs.push({ agent: step.agentName, output: '
|
|
559
|
+
const { bridge, agentName } = resolveTarget(step.agentName, d);
|
|
560
|
+
if (!bridge || !bridge.online) {
|
|
561
|
+
outputs.push({ agent: step.agentName, output: 'Bridge introuvable', success: false });
|
|
515
562
|
break;
|
|
516
563
|
}
|
|
517
564
|
const stepPrompt = (step.promptPrefix ? step.promptPrefix + '\n\n' : '') +
|
|
518
565
|
`[Pipeline Step ${i + 1}/${args.steps.length}]\n${currentPrompt}`;
|
|
519
|
-
const result =
|
|
566
|
+
const result = await sendToBridge(bridge, stepPrompt, { timeoutMs: timeout });
|
|
520
567
|
outputs.push({
|
|
521
|
-
agent: `${agentName} (:${port})`,
|
|
568
|
+
agent: `${agentName} (:${bridge.port})`,
|
|
522
569
|
output: result.text,
|
|
523
570
|
success: result.success,
|
|
524
571
|
});
|
|
@@ -528,10 +575,7 @@ export async function a2aHub(args) {
|
|
|
528
575
|
? outputs.map((o) => `[${o.agent}]: ${o.output}`).join('\n\n---\n\n')
|
|
529
576
|
: result.text;
|
|
530
577
|
}
|
|
531
|
-
const lines = [
|
|
532
|
-
`🔗 **Pipeline terminé** (${outputs.length}/${args.steps.length} steps)`,
|
|
533
|
-
``,
|
|
534
|
-
];
|
|
578
|
+
const lines = [`🔗 **Pipeline** (${outputs.length}/${args.steps.length} steps)`, ``];
|
|
535
579
|
for (const o of outputs) {
|
|
536
580
|
lines.push(`**${o.success ? '✅' : '❌'} ${o.agent}:**`);
|
|
537
581
|
lines.push(o.output.slice(0, 800) + (o.output.length > 800 ? '...' : ''));
|
|
@@ -540,7 +584,7 @@ export async function a2aHub(args) {
|
|
|
540
584
|
return { content: [{ type: 'text', text: lines.join('\n') }] };
|
|
541
585
|
}
|
|
542
586
|
// ═══════════════════════════════════════════════════════════════════════
|
|
543
|
-
// FANOUT
|
|
587
|
+
// FANOUT (true parallel — Promise.all)
|
|
544
588
|
// ═══════════════════════════════════════════════════════════════════════
|
|
545
589
|
case 'fanout': {
|
|
546
590
|
if (!args.message || !args.targets || args.targets.length === 0) {
|
|
@@ -549,21 +593,18 @@ export async function a2aHub(args) {
|
|
|
549
593
|
isError: true,
|
|
550
594
|
};
|
|
551
595
|
}
|
|
552
|
-
const d = discoverAgents();
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
.
|
|
557
|
-
|
|
558
|
-
if (!port) {
|
|
559
|
-
return { agent: target, success: false, text: 'Worker introuvable' };
|
|
596
|
+
const d = await discoverAgents();
|
|
597
|
+
// Run ALL targets in parallel (true parallel — async fetch)
|
|
598
|
+
const results = await Promise.all(args.targets.map(async (target) => {
|
|
599
|
+
const { bridge, agentName } = resolveTarget(target, d);
|
|
600
|
+
if (!bridge || !bridge.online) {
|
|
601
|
+
return { agent: target, success: false, text: 'Bridge introuvable' };
|
|
560
602
|
}
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
success: r.success,
|
|
566
|
-
text: r.text,
|
|
603
|
+
const r = await sendToBridge(bridge, args.message, {
|
|
604
|
+
model: args.model,
|
|
605
|
+
timeoutMs: timeout,
|
|
606
|
+
});
|
|
607
|
+
return { agent: `${agentName} (:${bridge.port})`, success: r.success, text: r.text };
|
|
567
608
|
}));
|
|
568
609
|
// Merge
|
|
569
610
|
let merged;
|
|
@@ -601,7 +642,7 @@ export async function a2aHub(args) {
|
|
|
601
642
|
return { content: [{ type: 'text', text: lines.join('\n') }] };
|
|
602
643
|
}
|
|
603
644
|
// ═══════════════════════════════════════════════════════════════════════
|
|
604
|
-
// QUERY
|
|
645
|
+
// QUERY (parallel, short timeout)
|
|
605
646
|
// ═══════════════════════════════════════════════════════════════════════
|
|
606
647
|
case 'query': {
|
|
607
648
|
if (!args.message || !args.targets || args.targets.length === 0) {
|
|
@@ -610,27 +651,21 @@ export async function a2aHub(args) {
|
|
|
610
651
|
isError: true,
|
|
611
652
|
};
|
|
612
653
|
}
|
|
613
|
-
const d = discoverAgents();
|
|
614
|
-
const enrichedMessage = `[A2A — Query from ${selfAgent}]\n${args.message}`;
|
|
654
|
+
const d = await discoverAgents();
|
|
615
655
|
const queryTimeout = Math.min(timeout, 60000);
|
|
616
|
-
const results = args.targets
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
return { agent: target, success: false, text: 'Worker introuvable' };
|
|
656
|
+
const results = await Promise.all(args.targets.map(async (target) => {
|
|
657
|
+
const { bridge, agentName } = resolveTarget(target, d);
|
|
658
|
+
if (!bridge || !bridge.online) {
|
|
659
|
+
return { agent: target, success: false, text: 'Bridge introuvable' };
|
|
621
660
|
}
|
|
622
|
-
|
|
661
|
+
const r = await sendToBridge(bridge, args.message, {
|
|
623
662
|
model: args.model,
|
|
624
663
|
timeoutMs: queryTimeout,
|
|
625
664
|
});
|
|
626
|
-
|
|
627
|
-
.map((r, i) => ({
|
|
628
|
-
agent: args.targets[i],
|
|
629
|
-
success: r.success,
|
|
630
|
-
text: r.text,
|
|
665
|
+
return { agent: `${agentName} (:${bridge.port})`, success: r.success, text: r.text };
|
|
631
666
|
}));
|
|
632
667
|
const lines = [
|
|
633
|
-
`❓ **Query
|
|
668
|
+
`❓ **Query** (${results.filter((r) => r.success).length}/${results.length} réponses)`,
|
|
634
669
|
``,
|
|
635
670
|
];
|
|
636
671
|
for (const r of results) {
|
|
@@ -650,60 +685,50 @@ export async function a2aHub(args) {
|
|
|
650
685
|
isError: true,
|
|
651
686
|
};
|
|
652
687
|
}
|
|
653
|
-
const d = discoverAgents();
|
|
654
|
-
|
|
655
|
-
let targetWorkers;
|
|
688
|
+
const d = await discoverAgents();
|
|
689
|
+
let targetBridges;
|
|
656
690
|
if (args.targets && args.targets.length > 0) {
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
.
|
|
660
|
-
const { port } = resolveTarget(t, d);
|
|
661
|
-
return d.workers.find((w) => w.port === port);
|
|
662
|
-
})
|
|
663
|
-
.filter((w) => w !== undefined);
|
|
691
|
+
targetBridges = args.targets
|
|
692
|
+
.map((t) => resolveTarget(t, d).bridge)
|
|
693
|
+
.filter((b) => b !== null && b.online);
|
|
664
694
|
}
|
|
665
695
|
else {
|
|
666
|
-
|
|
667
|
-
targetWorkers = d.workers.filter((w) => w.online);
|
|
696
|
+
targetBridges = d.bridges.filter((b) => b.online);
|
|
668
697
|
}
|
|
669
|
-
if (
|
|
698
|
+
if (targetBridges.length === 0) {
|
|
670
699
|
return {
|
|
671
|
-
content: [{ type: 'text', text: '❌ Aucun
|
|
700
|
+
content: [{ type: 'text', text: '❌ Aucun bridge online pour broadcast' }],
|
|
672
701
|
isError: true,
|
|
673
702
|
};
|
|
674
703
|
}
|
|
675
|
-
const enrichedMessage = `[A2A — Broadcast from ${selfAgent}]\n${args.message}`;
|
|
676
704
|
if (args.race) {
|
|
677
|
-
// First to respond wins
|
|
678
|
-
|
|
679
|
-
const
|
|
680
|
-
model: args.model,
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
705
|
+
// First to respond wins — use Promise.any
|
|
706
|
+
try {
|
|
707
|
+
const firstResult = await Promise.any(targetBridges.map(async (b) => {
|
|
708
|
+
const r = await sendToBridge(b, args.message, { model: args.model, timeoutMs: timeout });
|
|
709
|
+
if (!r.success)
|
|
710
|
+
throw new Error(r.text);
|
|
711
|
+
return { bridge: b, text: r.text };
|
|
712
|
+
}));
|
|
713
|
+
return {
|
|
714
|
+
content: [{
|
|
715
|
+
type: 'text',
|
|
716
|
+
text: `📡 **Broadcast race — ${firstResult.bridge.agentName} (:${firstResult.bridge.port}) a gagné!**\n\n${firstResult.text}`,
|
|
717
|
+
}],
|
|
718
|
+
};
|
|
719
|
+
}
|
|
720
|
+
catch {
|
|
721
|
+
return {
|
|
722
|
+
content: [{ type: 'text', text: '❌ Tous les bridges ont échoué' }],
|
|
723
|
+
isError: true,
|
|
724
|
+
};
|
|
693
725
|
}
|
|
694
|
-
return {
|
|
695
|
-
content: [{ type: 'text', text: '❌ Tous les workers ont échoué' }],
|
|
696
|
-
isError: true,
|
|
697
|
-
};
|
|
698
726
|
}
|
|
699
727
|
// Send to all in parallel
|
|
700
|
-
const results =
|
|
701
|
-
const r =
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
});
|
|
705
|
-
return { agent: `${w.agentName} (:${w.port})`, success: r.success, text: r.text };
|
|
706
|
-
});
|
|
728
|
+
const results = await Promise.all(targetBridges.map(async (b) => {
|
|
729
|
+
const r = await sendToBridge(b, args.message, { model: args.model, timeoutMs: timeout });
|
|
730
|
+
return { agent: `${b.agentName} (:${b.port})`, success: r.success, text: r.text };
|
|
731
|
+
}));
|
|
707
732
|
const successCount = results.filter((r) => r.success).length;
|
|
708
733
|
const lines = [`📡 **Broadcast** — ${successCount}/${results.length} succès`, ``];
|
|
709
734
|
for (const r of results) {
|
|
@@ -715,24 +740,17 @@ export async function a2aHub(args) {
|
|
|
715
740
|
}
|
|
716
741
|
default:
|
|
717
742
|
return {
|
|
718
|
-
content: [
|
|
719
|
-
{
|
|
720
|
-
type: 'text',
|
|
721
|
-
text: `❌ Action inconnue: ${args.action}`,
|
|
722
|
-
},
|
|
723
|
-
],
|
|
743
|
+
content: [{ type: 'text', text: `❌ Action inconnue: ${args.action}` }],
|
|
724
744
|
isError: true,
|
|
725
745
|
};
|
|
726
746
|
}
|
|
727
747
|
}
|
|
728
748
|
catch (error) {
|
|
729
749
|
return {
|
|
730
|
-
content: [
|
|
731
|
-
{
|
|
750
|
+
content: [{
|
|
732
751
|
type: 'text',
|
|
733
752
|
text: `❌ Erreur A2A Hub: ${error instanceof Error ? error.message : String(error)}`,
|
|
734
|
-
},
|
|
735
|
-
],
|
|
753
|
+
}],
|
|
736
754
|
isError: true,
|
|
737
755
|
};
|
|
738
756
|
}
|