overmind-mcp 3.3.9 → 3.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,663 @@
1
+ /**
2
+ * ═══════════════════════════════════════════════════════════════════════════
3
+ * A2A Hub — Outil MCP unifié pour la communication Agent-to-Agent
4
+ * ═══════════════════════════════════════════════════════════════════════════
5
+ *
6
+ * Cet outil donne aux agents une vue complète du système multi-agents:
7
+ *
8
+ * - LISTER tous les agents persistants (Hermes profiles + live registry)
9
+ * - VOIR ce que chaque agent fait en temps réel (status, busy/idle, dernière activité)
10
+ * - DELEGUER une tâche à un autre agent (async avec callback)
11
+ * - PIPELINE chainé (A→B→C avec passing de contexte)
12
+ * - FANOUT parallèle (1→N + merge des résultats)
13
+ * - QUERY multi-agents (poser une question à plusieurs agents simultanément)
14
+ * - BROADCAST (message global à tous les agents online)
15
+ *
16
+ * L'agent qui appelle cet outil découvre AUTOMATIQUEMENT:
17
+ * - Quels agents existent sur le système (profiles Hermes)
18
+ * - Leur status (online/busy/idle/offline)
19
+ * - Leur runner (hermes, claude, kilo, etc.)
20
+ * - Leur modèle LLM
21
+ * - Leur dernière activité
22
+ * - Le compteur A2A (combien de messages envoyés/reçus)
23
+ *
24
+ * Aucune configuration manuelle — l'outil scanne ~/.overmind/hermes/profiles/
25
+ * et croise avec le registry live du bridge.
26
+ */
27
+ import { z } from 'zod';
28
+ import { execSync } from 'child_process';
29
+ import fs from 'fs';
30
+ import path from 'path';
31
+ import os from 'os';
32
+ // ─── Discovery: scan profiles + live status ────────────────────────────────
33
+ function discoverAgents() {
34
+ const home = os.homedir();
35
+ const profilesDir = process.platform === 'win32'
36
+ ? path.join(home, '.overmind', 'hermes', 'profiles')
37
+ : path.join(home, '.overmind', 'hermes', 'profiles');
38
+ const bridgeUrl = process.env.OVERMIND_BRIDGE_URL || 'http://localhost:3100/rpc';
39
+ const selfAgent = process.env.OVERMIND_AGENT_NAME || null;
40
+ const agents = [];
41
+ // 1. Scan Hermes profiles
42
+ if (fs.existsSync(profilesDir)) {
43
+ const entries = fs.readdirSync(profilesDir, { withFileTypes: true });
44
+ for (const entry of entries) {
45
+ if (!entry.isDirectory())
46
+ continue;
47
+ const profilePath = path.join(profilesDir, entry.name);
48
+ // Read config.yaml
49
+ const configPath = path.join(profilePath, 'config.yaml');
50
+ let model = 'unknown';
51
+ let provider = 'unknown';
52
+ if (fs.existsSync(configPath)) {
53
+ const config = fs.readFileSync(configPath, 'utf8');
54
+ const modelMatch = config.match(/model:\s*(.+)/);
55
+ const providerMatch = config.match(/provider:\s*(.+)/);
56
+ if (modelMatch)
57
+ model = modelMatch[1].trim();
58
+ if (providerMatch)
59
+ provider = providerMatch[1].trim();
60
+ }
61
+ // Read profile.yaml for description
62
+ const profileYamlPath = path.join(profilePath, 'profile.yaml');
63
+ let description = '';
64
+ if (fs.existsSync(profileYamlPath)) {
65
+ const py = fs.readFileSync(profileYamlPath, 'utf8');
66
+ const descMatch = py.match(/description:\s*"?([^"\n]+)"?/);
67
+ if (descMatch)
68
+ description = descMatch[1].trim();
69
+ }
70
+ // Count skills
71
+ let skillsCount = 0;
72
+ const skillsDir = path.join(profilePath, 'skills');
73
+ if (fs.existsSync(skillsDir)) {
74
+ try {
75
+ const walk = (dir) => {
76
+ let count = 0;
77
+ for (const f of fs.readdirSync(dir, { withFileTypes: true })) {
78
+ if (f.isDirectory()) {
79
+ count += walk(path.join(dir, f.name));
80
+ }
81
+ else if (f.name === 'SKILL.md') {
82
+ count++;
83
+ }
84
+ }
85
+ return count;
86
+ };
87
+ skillsCount = walk(skillsDir);
88
+ }
89
+ catch {
90
+ // ignore
91
+ }
92
+ }
93
+ // Check memory
94
+ const hasMemory = fs.existsSync(path.join(profilePath, 'memories', 'MEMORY.md'));
95
+ // Last activity (state.db mtime)
96
+ let lastActivity = null;
97
+ const stateDb = path.join(profilePath, 'state.db');
98
+ if (fs.existsSync(stateDb)) {
99
+ try {
100
+ const stat = fs.statSync(stateDb);
101
+ lastActivity = stat.mtime.toISOString();
102
+ }
103
+ catch {
104
+ // ignore
105
+ }
106
+ }
107
+ agents.push({
108
+ name: entry.name,
109
+ runner: 'hermes',
110
+ model,
111
+ status: 'unknown', // Will be enriched from bridge if available
112
+ provider,
113
+ lastActivity,
114
+ description,
115
+ skillsCount,
116
+ hasMemory,
117
+ a2aCapabilities: ['delegate', 'pipeline', 'fanout', 'query', 'broadcast', 'a2a'],
118
+ });
119
+ }
120
+ }
121
+ // 2. Try to get live status from bridge
122
+ let bridgeOnline = false;
123
+ try {
124
+ const healthUrl = bridgeUrl.replace('/rpc', '/health');
125
+ const result = execSync(`curl -s -m 3 "${healthUrl}" 2>/dev/null || echo '{}'`, {
126
+ encoding: 'utf8',
127
+ timeout: 5000,
128
+ });
129
+ const health = JSON.parse(result);
130
+ bridgeOnline = health.status === 'online' || health.status === 'degraded';
131
+ // Enrich agents with live status from registry
132
+ if (health.agents && Array.isArray(health.agents)) {
133
+ for (const liveAgent of health.agents) {
134
+ const found = agents.find((a) => a.name === liveAgent.name);
135
+ if (found) {
136
+ found.status = liveAgent.status;
137
+ }
138
+ }
139
+ }
140
+ }
141
+ catch {
142
+ // Bridge not reachable — agents keep 'unknown' status
143
+ }
144
+ // Default 'offline' for agents we couldn't reach
145
+ for (const a of agents) {
146
+ if (a.status === 'unknown')
147
+ a.status = 'offline';
148
+ }
149
+ return {
150
+ totalAgents: agents.length,
151
+ onlineAgents: agents.filter((a) => a.status === 'online').length,
152
+ busyAgents: agents.filter((a) => a.status === 'busy').length,
153
+ agents,
154
+ selfAgent,
155
+ bridgeUrl,
156
+ bridgeOnline,
157
+ };
158
+ }
159
+ // ─── RPC Helper ────────────────────────────────────────────────────────────
160
+ function rpcCall(method, params) {
161
+ const bridgeUrl = process.env.OVERMIND_BRIDGE_URL || 'http://localhost:3100/rpc';
162
+ const payload = JSON.stringify({
163
+ jsonrpc: '2.0',
164
+ id: Date.now(),
165
+ method,
166
+ params,
167
+ });
168
+ try {
169
+ const result = execSync(`curl -s -m 300 -X POST "${bridgeUrl}" -H "Content-Type: application/json" -d '${payload.replace(/'/g, "'\\''")}' 2>/dev/null`, { encoding: 'utf8', timeout: 310000, maxBuffer: 50 * 1024 * 1024 });
170
+ const response = JSON.parse(result);
171
+ if (response.error) {
172
+ return {
173
+ error: response.error.message,
174
+ code: response.error.code,
175
+ };
176
+ }
177
+ return response.result;
178
+ }
179
+ catch (err) {
180
+ return { error: err.message };
181
+ }
182
+ }
183
+ // ─── Schema ────────────────────────────────────────────────────────────────
184
+ export const a2aHubSchema = z.object({
185
+ action: z
186
+ .enum(['discover', 'status', 'send', 'delegate', 'pipeline', 'fanout', 'query', 'broadcast'])
187
+ .describe("Action à effectuer: discover=liste tous les agents, status=état d'un agent, send=message synchrone, delegate=async+callback, pipeline=chaîne A→B→C, fanout=1→N parallèle+merge, query=question multi-agents, broadcast=message global"),
188
+ // ─── Target (for send/delegate/status) ──────────────────────────────────
189
+ target: z.string().optional().describe("Nom de l'agent cible (ex: 'sniperbot_analyst')"),
190
+ // ─── Message ─────────────────────────────────────────────────────────────
191
+ message: z.string().optional().describe('Le message/prompt à envoyer aux agents'),
192
+ // ─── Targets (for fanout/query/broadcast/pipeline) ──────────────────────
193
+ targets: z
194
+ .array(z.string())
195
+ .optional()
196
+ .describe('Liste des agents cibles (pour fanout/query). Si absent pour broadcast → tous les agents online'),
197
+ // ─── Pipeline steps ──────────────────────────────────────────────────────
198
+ steps: z
199
+ .array(z.object({
200
+ agentName: z.string(),
201
+ promptPrefix: z.string().optional(),
202
+ }))
203
+ .optional()
204
+ .describe('Étapes de la pipeline (pour action=pipeline)'),
205
+ // ─── Options ─────────────────────────────────────────────────────────────
206
+ runner: z.string().optional().default('hermes').describe('Runner à utiliser (default: hermes)'),
207
+ model: z.string().optional().describe('Modèle LLM à utiliser (override)'),
208
+ mergeStrategy: z
209
+ .enum(['concat', 'best', 'vote', 'first_success'])
210
+ .optional()
211
+ .default('concat')
212
+ .describe('Stratégie de merge pour fanout (default: concat)'),
213
+ race: z
214
+ .boolean()
215
+ .optional()
216
+ .default(false)
217
+ .describe("Pour broadcast: si true, retourne dès qu'un agent répond"),
218
+ async: z
219
+ .boolean()
220
+ .optional()
221
+ .default(true)
222
+ .describe('Pour delegate: si true (default), retourne immédiatement avec un taskId'),
223
+ callbackUrl: z
224
+ .string()
225
+ .optional()
226
+ .describe("Pour delegate: URL appelée quand l'agent termine (POST result)"),
227
+ accumulateContext: z
228
+ .boolean()
229
+ .optional()
230
+ .default(false)
231
+ .describe('Pour pipeline: si true, chaque step reçoit tous les outputs précédents'),
232
+ timeoutMs: z
233
+ .number()
234
+ .int()
235
+ .min(1000)
236
+ .max(3600000)
237
+ .optional()
238
+ .describe('Timeout en ms (défaut selon action)'),
239
+ });
240
+ // ─── Execute ───────────────────────────────────────────────────────────────
241
+ export async function a2aHub(args) {
242
+ const selfAgent = process.env.OVERMIND_AGENT_NAME || 'unknown';
243
+ const { action } = args;
244
+ try {
245
+ switch (action) {
246
+ // ═══════════════════════════════════════════════════════════════════════
247
+ // DISCOVER — Liste TOUS les agents + leur status temps réel
248
+ // ═══════════════════════════════════════════════════════════════════════
249
+ case 'discover': {
250
+ const discovery = discoverAgents();
251
+ const lines = [
252
+ `🌐 **A2A Hub — Découverte du système multi-agents**`,
253
+ ``,
254
+ `**Self:** ${discovery.selfAgent || '(inconnu)'}`,
255
+ `**Bridge:** ${discovery.bridgeUrl} (${discovery.bridgeOnline ? '✅ online' : '❌ offline'})`,
256
+ `**Total agents:** ${discovery.totalAgents} | **Online:** ${discovery.onlineAgents} | **Busy:** ${discovery.busyAgents}`,
257
+ ``,
258
+ ];
259
+ if (discovery.agents.length === 0) {
260
+ lines.push('Aucun agent trouvé dans ~/.overmind/hermes/profiles/');
261
+ }
262
+ else {
263
+ lines.push('| Agent | Status | Model | Provider | Skills | Description |');
264
+ lines.push('|-------|--------|-------|----------|--------|-------------|');
265
+ for (const a of discovery.agents) {
266
+ const statusIcon = a.status === 'online'
267
+ ? '🟢'
268
+ : a.status === 'busy'
269
+ ? '🟡'
270
+ : a.status === 'idle'
271
+ ? '⚪'
272
+ : '🔴';
273
+ lines.push(`| ${a.name === selfAgent ? '**' + a.name + ' (self)**' : a.name} | ${statusIcon} ${a.status} | ${a.model} | ${a.provider} | ${a.skillsCount} | ${a.description.slice(0, 40)} |`);
274
+ }
275
+ lines.push('');
276
+ lines.push('**Capacités A2A disponibles:**');
277
+ lines.push(' • `send` — Message synchrone A→B');
278
+ lines.push(' • `delegate` — Tâche async + callback');
279
+ lines.push(' • `pipeline` — Chaîne A→B→C');
280
+ lines.push(' • `fanout` — 1→N parallèle + merge');
281
+ lines.push(' • `query` — Question multi-agents');
282
+ lines.push(' • `broadcast` — Message global');
283
+ }
284
+ return {
285
+ content: [{ type: 'text', text: lines.join('\n') }],
286
+ };
287
+ }
288
+ // ═══════════════════════════════════════════════════════════════════════
289
+ // STATUS — État détaillé d'un agent
290
+ // ═══════════════════════════════════════════════════════════════════════
291
+ case 'status': {
292
+ if (!args.target) {
293
+ return {
294
+ content: [
295
+ {
296
+ type: 'text',
297
+ text: '❌ Paramètre `target` requis pour action=status',
298
+ },
299
+ ],
300
+ isError: true,
301
+ };
302
+ }
303
+ const result = rpcCall('agent.status', {
304
+ agentName: args.target,
305
+ runner: args.runner,
306
+ action: 'status',
307
+ });
308
+ const r = result;
309
+ if (r.error) {
310
+ return {
311
+ content: [
312
+ {
313
+ type: 'text',
314
+ text: `❌ Erreur status ${args.target}: ${r.error}`,
315
+ },
316
+ ],
317
+ isError: true,
318
+ };
319
+ }
320
+ const local = (r.local || r.result?.local);
321
+ const lines = [`📊 **Status: ${args.target}**`, ``];
322
+ if (local) {
323
+ lines.push(`**État local:** ${local.status || 'unknown'}`);
324
+ lines.push(`**Runner:** ${local.runner || 'unknown'}`);
325
+ lines.push(`**Total runs:** ${local.totalRuns || 0}`);
326
+ lines.push(`**Erreurs:** ${local.totalErrors || 0}`);
327
+ lines.push(`**A2A reçus:** ${local.a2aReceived || 0}`);
328
+ lines.push(`**A2A envoyés:** ${local.a2aSent || 0}`);
329
+ if (local.currentSessionId) {
330
+ lines.push(`**Session:** ${local.currentSessionId}`);
331
+ }
332
+ if (local.lastActivityAt) {
333
+ lines.push(`**Dernière activité:** ${new Date(local.lastActivityAt).toISOString()}`);
334
+ }
335
+ }
336
+ return {
337
+ content: [{ type: 'text', text: lines.join('\n') }],
338
+ };
339
+ }
340
+ // ═══════════════════════════════════════════════════════════════════════
341
+ // SEND — Message synchrone A→B (via agent.a2a)
342
+ // ═══════════════════════════════════════════════════════════════════════
343
+ case 'send': {
344
+ if (!args.target || !args.message) {
345
+ return {
346
+ content: [
347
+ {
348
+ type: 'text',
349
+ text: '❌ Paramètres `target` et `message` requis pour action=send',
350
+ },
351
+ ],
352
+ isError: true,
353
+ };
354
+ }
355
+ const result = rpcCall('agent.a2a', {
356
+ fromAgent: selfAgent,
357
+ toAgent: args.target,
358
+ runner: args.runner,
359
+ prompt: args.message,
360
+ ...(args.model ? { model: args.model } : {}),
361
+ });
362
+ const r = result;
363
+ if (r.error) {
364
+ return {
365
+ content: [
366
+ {
367
+ type: 'text',
368
+ text: `❌ Erreur send → ${args.target}: ${r.error}`,
369
+ },
370
+ ],
371
+ isError: true,
372
+ };
373
+ }
374
+ const content = (r.content || []);
375
+ const responseText = content.map((c) => c.text).join('\n');
376
+ return {
377
+ content: [
378
+ {
379
+ type: 'text',
380
+ text: `📤 **Message envoyé à ${args.target}**\n\n${responseText}`,
381
+ },
382
+ ],
383
+ };
384
+ }
385
+ // ═══════════════════════════════════════════════════════════════════════
386
+ // DELEGATE — Async fire-and-forget + callback
387
+ // ═══════════════════════════════════════════════════════════════════════
388
+ case 'delegate': {
389
+ if (!args.target || !args.message) {
390
+ return {
391
+ content: [
392
+ {
393
+ type: 'text',
394
+ text: '❌ Paramètres `target` et `message` requis pour action=delegate',
395
+ },
396
+ ],
397
+ isError: true,
398
+ };
399
+ }
400
+ const result = rpcCall('agent.delegate', {
401
+ fromAgent: selfAgent,
402
+ toAgent: args.target,
403
+ runner: args.runner,
404
+ prompt: args.message,
405
+ async: args.async,
406
+ ...(args.model ? { model: args.model } : {}),
407
+ ...(args.callbackUrl ? { callbackUrl: args.callbackUrl } : {}),
408
+ });
409
+ const r = result;
410
+ if (r.error) {
411
+ return {
412
+ content: [
413
+ {
414
+ type: 'text',
415
+ text: `❌ Erreur delegate → ${args.target}: ${r.error}`,
416
+ },
417
+ ],
418
+ isError: true,
419
+ };
420
+ }
421
+ if (args.async) {
422
+ return {
423
+ content: [
424
+ {
425
+ type: 'text',
426
+ text: `🤝 **Tâche déléguée à ${args.target}**\n\n**TaskId:** ${r.taskId}\n**Status:** ${r.status}\n${r.message || ''}`,
427
+ },
428
+ ],
429
+ };
430
+ }
431
+ const content = (r.content || []);
432
+ return {
433
+ content: [
434
+ {
435
+ type: 'text',
436
+ text: `✅ **${args.target} a terminé**\n\n${content.map((c) => c.text).join('\n')}`,
437
+ },
438
+ ],
439
+ };
440
+ }
441
+ // ═══════════════════════════════════════════════════════════════════════
442
+ // PIPELINE — Chaîne séquentielle A→B→C
443
+ // ═══════════════════════════════════════════════════════════════════════
444
+ case 'pipeline': {
445
+ if (!args.message || !args.steps || args.steps.length === 0) {
446
+ return {
447
+ content: [
448
+ {
449
+ type: 'text',
450
+ text: "❌ Paramètres `message` (prompt initial) et `steps` (chaîne d'agents) requis pour action=pipeline",
451
+ },
452
+ ],
453
+ isError: true,
454
+ };
455
+ }
456
+ const result = rpcCall('agent.pipeline', {
457
+ initiator: selfAgent,
458
+ runner: args.runner,
459
+ prompt: args.message,
460
+ steps: args.steps,
461
+ accumulateContext: args.accumulateContext,
462
+ ...(args.timeoutMs ? { totalTimeoutMs: args.timeoutMs } : {}),
463
+ });
464
+ const r = result;
465
+ if (r.error) {
466
+ return {
467
+ content: [
468
+ {
469
+ type: 'text',
470
+ text: `❌ Erreur pipeline: ${r.error}`,
471
+ },
472
+ ],
473
+ isError: true,
474
+ };
475
+ }
476
+ const outputs = (r.outputs || []);
477
+ const lines = [
478
+ `🔗 **Pipeline terminé** (${r.executedSteps}/${r.totalSteps} steps)`,
479
+ ``,
480
+ ];
481
+ for (const o of outputs) {
482
+ lines.push(`**${o.success ? '✅' : '❌'} ${o.agentName}:**`);
483
+ lines.push(o.output.slice(0, 500) + (o.output.length > 500 ? '...' : ''));
484
+ lines.push('');
485
+ }
486
+ lines.push(`**Output final:**`);
487
+ lines.push((r.finalOutput || '').slice(0, 1000));
488
+ return {
489
+ content: [{ type: 'text', text: lines.join('\n') }],
490
+ };
491
+ }
492
+ // ═══════════════════════════════════════════════════════════════════════
493
+ // FANOUT — 1→N parallèle + merge
494
+ // ═══════════════════════════════════════════════════════════════════════
495
+ case 'fanout': {
496
+ if (!args.message || !args.targets || args.targets.length === 0) {
497
+ return {
498
+ content: [
499
+ {
500
+ type: 'text',
501
+ text: '❌ Paramètres `message` et `targets` requis pour action=fanout',
502
+ },
503
+ ],
504
+ isError: true,
505
+ };
506
+ }
507
+ const result = rpcCall('agent.fanout', {
508
+ fromAgent: selfAgent,
509
+ runner: args.runner,
510
+ prompt: args.message,
511
+ targets: args.targets,
512
+ mergeStrategy: args.mergeStrategy,
513
+ ...(args.model ? { model: args.model } : {}),
514
+ ...(args.timeoutMs ? { agentTimeoutMs: args.timeoutMs } : {}),
515
+ });
516
+ const r = result;
517
+ if (r.error) {
518
+ return {
519
+ content: [
520
+ {
521
+ type: 'text',
522
+ text: `❌ Erreur fanout: ${r.error}`,
523
+ },
524
+ ],
525
+ isError: true,
526
+ };
527
+ }
528
+ const lines = [
529
+ `🌐 **Fanout terminé** (${r.successCount}/${r.totalResults} succès, merge=${r.mergeStrategy})`,
530
+ ...(r.winner ? [`**Gagnant:** ${r.winner}`] : []),
531
+ ``,
532
+ `**Résultat fusionné:**`,
533
+ (r.merged || '').slice(0, 2000),
534
+ ];
535
+ return {
536
+ content: [{ type: 'text', text: lines.join('\n') }],
537
+ };
538
+ }
539
+ // ═══════════════════════════════════════════════════════════════════════
540
+ // QUERY — Question multi-agents rapide
541
+ // ═══════════════════════════════════════════════════════════════════════
542
+ case 'query': {
543
+ if (!args.message || !args.targets || args.targets.length === 0) {
544
+ return {
545
+ content: [
546
+ {
547
+ type: 'text',
548
+ text: '❌ Paramètres `message` et `targets` requis pour action=query',
549
+ },
550
+ ],
551
+ isError: true,
552
+ };
553
+ }
554
+ const result = rpcCall('agent.query', {
555
+ fromAgent: selfAgent,
556
+ runner: args.runner,
557
+ prompt: args.message,
558
+ targets: args.targets,
559
+ ...(args.model ? { model: args.model } : {}),
560
+ ...(args.timeoutMs ? { agentTimeoutMs: args.timeoutMs } : {}),
561
+ });
562
+ const r = result;
563
+ if (r.error) {
564
+ return {
565
+ content: [
566
+ {
567
+ type: 'text',
568
+ text: `❌ Erreur query: ${r.error}`,
569
+ },
570
+ ],
571
+ isError: true,
572
+ };
573
+ }
574
+ const results = (r.results || []);
575
+ const lines = [
576
+ `❓ **Query multi-agents** (${r.successCount}/${r.totalQueried} réponses)`,
577
+ ``,
578
+ ];
579
+ for (const res of results) {
580
+ lines.push(`**${res.success ? '✅' : '❌'} ${res.agentName}:**`);
581
+ lines.push(res.text.slice(0, 500) + (res.text.length > 500 ? '...' : ''));
582
+ lines.push('');
583
+ }
584
+ return {
585
+ content: [{ type: 'text', text: lines.join('\n') }],
586
+ };
587
+ }
588
+ // ═══════════════════════════════════════════════════════════════════════
589
+ // BROADCAST — Message global à tous les agents online
590
+ // ═══════════════════════════════════════════════════════════════════════
591
+ case 'broadcast': {
592
+ if (!args.message) {
593
+ return {
594
+ content: [
595
+ {
596
+ type: 'text',
597
+ text: '❌ Paramètre `message` requis pour action=broadcast',
598
+ },
599
+ ],
600
+ isError: true,
601
+ };
602
+ }
603
+ const result = rpcCall('agent.broadcast', {
604
+ fromAgent: selfAgent,
605
+ runner: args.runner,
606
+ prompt: args.message,
607
+ race: args.race,
608
+ ...(args.targets ? { targets: args.targets } : {}),
609
+ ...(args.model ? { model: args.model } : {}),
610
+ ...(args.timeoutMs ? { agentTimeoutMs: args.timeoutMs } : {}),
611
+ });
612
+ const r = result;
613
+ if (r.error) {
614
+ return {
615
+ content: [
616
+ {
617
+ type: 'text',
618
+ text: `❌ Erreur broadcast: ${r.error}`,
619
+ },
620
+ ],
621
+ isError: true,
622
+ };
623
+ }
624
+ const results = (r.results || []);
625
+ const lines = [
626
+ `📡 **Broadcast ${r.race ? '(race mode)' : ''}** — ${r.successCount}/${r.total} succès`,
627
+ ``,
628
+ ];
629
+ for (const res of results) {
630
+ const text = res.content?.map((c) => c.text).join('\n') || res.error || '';
631
+ lines.push(`**${res.success ? '✅' : '❌'} ${res.agentName}:**`);
632
+ lines.push(text.slice(0, 300) + (text.length > 300 ? '...' : ''));
633
+ lines.push('');
634
+ }
635
+ return {
636
+ content: [{ type: 'text', text: lines.join('\n') }],
637
+ };
638
+ }
639
+ default:
640
+ return {
641
+ content: [
642
+ {
643
+ type: 'text',
644
+ text: `❌ Action inconnue: ${action}. Actions: discover, status, send, delegate, pipeline, fanout, query, broadcast`,
645
+ },
646
+ ],
647
+ isError: true,
648
+ };
649
+ }
650
+ }
651
+ catch (error) {
652
+ return {
653
+ content: [
654
+ {
655
+ type: 'text',
656
+ text: `❌ Erreur A2A Hub: ${error instanceof Error ? error.message : String(error)}`,
657
+ },
658
+ ],
659
+ isError: true,
660
+ };
661
+ }
662
+ }
663
+ //# sourceMappingURL=a2a_hub.js.map