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.
package/README.md CHANGED
@@ -28,6 +28,7 @@ _Orchestrateur universel agents IA multi-modèles via MCP. Pilote Hermes, Claude
28
28
  - 🌉 **Bridge HTTP JSON-RPC** : Orchestration A2A, scénarios, webhooks, sessions multi-tenant
29
29
  - 🛡️ **Anti-Zombie** : 1 seul process HTTP partagé, processRegistry avec TTL + cleanup auto
30
30
  - 📋 **14 Outils MCP** : run_agent, create_agent, memory_search/store, agent_control, etc.
31
+ - 🧠 **Mémoire par défaut** : tout agent créé via Overmind reçoit automatiquement le MCP `memory` (3 tools: memory_search/store/runs sur :3099). Pour l'accès complet 14 tools, utiliser `overmind` explicitement.
31
32
  - 🅾️ **HTTP Singleton** : FastMCP httpStream — 1 serveur, tous les agents
32
33
 
33
34
  ---
@@ -130,6 +130,37 @@ export declare class OverBridgeServer {
130
130
  * B reçoit un prompt enrichi avec le contexte de A.
131
131
  */
132
132
  private methodAgentA2A;
133
+ /**
134
+ * agent.broadcast — Un agent → tous les agents (fan-out global).
135
+ * Si race=true, retourne dès qu'un agent répond.
136
+ * Si race=false (default), attend tous et retourne les résultats.
137
+ */
138
+ private methodAgentBroadcast;
139
+ /**
140
+ * agent.pipeline — Chaîne séquentielle A→B→C.
141
+ * Le output de chaque step est injecté dans le prompt du suivant.
142
+ */
143
+ private methodAgentPipeline;
144
+ /**
145
+ * agent.fanout — Un agent → plusieurs en parallèle + merge.
146
+ * mergeStrategy: concat | best | vote | first_success
147
+ */
148
+ private methodAgentFanout;
149
+ /**
150
+ * agent.delegate — Délégation async (fire-and-forget avec taskId).
151
+ * Si async=true (default), retourne immédiatement avec un taskId.
152
+ * Si callbackUrl fourni, POST le résultat quand l'agent termine.
153
+ */
154
+ private methodAgentDelegate;
155
+ /**
156
+ * agent.query — Query multi-agents, retourne toutes les réponses rapidement.
157
+ * Comme fanout mais optimisé pour le read-only (courte réponse rapide).
158
+ */
159
+ private methodAgentQuery;
160
+ /**
161
+ * Construit un prompt A2A standardisé avec header.
162
+ */
163
+ private buildA2aPrompt;
133
164
  /**
134
165
  * agent.status — Status live d'un agent (busy/idle/online) via registry.
135
166
  * Possibilité de proxy vers Overmind MCP agent_control aussi.
@@ -1 +1 @@
1
- {"version":3,"file":"OverBridgeServer.d.ts","sourceRoot":"","sources":["../../src/bridge/OverBridgeServer.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"OverBridgeServer.d.ts","sourceRoot":"","sources":["../../src/bridge/OverBridgeServer.ts"],"names":[],"mappings":"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"}
@@ -66,6 +66,82 @@ const A2AParams = z.object({
66
66
  path: z.string().optional(),
67
67
  metadata: z.record(z.string(), z.unknown()).optional(),
68
68
  });
69
+ // ─── A2A Extended Schemas ──────────────────────────────────────────────────
70
+ /** agent.broadcast — Un agent → tous les agents (fan-out global) */
71
+ const BroadcastParams = z.object({
72
+ fromAgent: z.string().min(1),
73
+ runner: z.string().min(1),
74
+ prompt: z.string().min(1),
75
+ /** Liste des agents cibles. Si absent → tous les agents online. */
76
+ targets: z.array(z.string().min(1)).optional(),
77
+ /** Si true, retourne dès qu'un agent répond (default: false = attend tous) */
78
+ race: z.boolean().optional().default(false),
79
+ /** Timeout par agent en ms (default: 60_000) */
80
+ agentTimeoutMs: z.number().int().min(1000).max(3_600_000).optional().default(120_000),
81
+ model: z.string().optional(),
82
+ metadata: z.record(z.string(), z.unknown()).optional(),
83
+ });
84
+ /** agent.pipeline — Chaîne séquentielle A→B→C (output de l'un = input du suivant) */
85
+ const PipelineParams = z.object({
86
+ /** Agent qui initie la chaîne (fromAgent) */
87
+ initiator: z.string().min(1),
88
+ runner: z.string().min(1),
89
+ /** Prompt initial */
90
+ prompt: z.string().min(1),
91
+ /** Étapes de la pipeline — chaque step reçoit le output du précédent */
92
+ steps: z
93
+ .array(z.object({
94
+ agentName: z.string().min(1),
95
+ /** Prompt prefix optionnel (le output précédent est appended) */
96
+ promptPrefix: z.string().optional(),
97
+ model: z.string().optional(),
98
+ }))
99
+ .min(1),
100
+ /** Si true, chaque step reçoit TOUS les outputs précédents (default: false = juste le dernier) */
101
+ accumulateContext: z.boolean().optional().default(false),
102
+ /** Timeout total en ms (default: 3_600_000 = 1h) */
103
+ totalTimeoutMs: z.number().int().min(10_000).max(86_400_000).optional().default(3_600_000),
104
+ metadata: z.record(z.string(), z.unknown()).optional(),
105
+ });
106
+ /** agent.fanout — Un agent → plusieurs en parallèle + merge */
107
+ const FanoutParams = z.object({
108
+ fromAgent: z.string().min(1),
109
+ runner: z.string().min(1),
110
+ prompt: z.string().min(1),
111
+ targets: z.array(z.string().min(1)).min(1),
112
+ /** Stratégie de merge des résultats */
113
+ mergeStrategy: z.enum(['concat', 'best', 'vote', 'first_success']).optional().default('concat'),
114
+ /** Critère pour 'best' (default: le plus long) */
115
+ bestCriterion: z.string().optional(),
116
+ /** Timeout par agent en ms (default: 120_000) */
117
+ agentTimeoutMs: z.number().int().min(1000).max(3_600_000).optional().default(120_000),
118
+ model: z.string().optional(),
119
+ metadata: z.record(z.string(), z.unknown()).optional(),
120
+ });
121
+ /** agent.delegate — Délégation async (fire-and-forget avec ID de tracking) */
122
+ const DelegateParams = z.object({
123
+ fromAgent: z.string().min(1),
124
+ toAgent: z.string().min(1),
125
+ runner: z.string().min(1),
126
+ prompt: z.string().min(1),
127
+ model: z.string().optional(),
128
+ /** Si fourni, callback URL appelée quand l'agent termine (POST result) */
129
+ callbackUrl: z.string().url().optional(),
130
+ /** Si true, retourne immédiatement avec un taskId (default: true) */
131
+ async: z.boolean().optional().default(true),
132
+ metadata: z.record(z.string(), z.unknown()).optional(),
133
+ });
134
+ /** agent.query — Query multi-agents, retourne toutes les réponses */
135
+ const QueryParams = z.object({
136
+ fromAgent: z.string().min(1),
137
+ runner: z.string().min(1),
138
+ prompt: z.string().min(1),
139
+ targets: z.array(z.string().min(1)).min(1),
140
+ /** Timeout par agent en ms (default: 30_000) */
141
+ agentTimeoutMs: z.number().int().min(1000).max(3_600_000).optional().default(30_000),
142
+ model: z.string().optional(),
143
+ metadata: z.record(z.string(), z.unknown()).optional(),
144
+ });
69
145
  const AgentStatusParams = z.object({
70
146
  agentName: z.string().min(1),
71
147
  runner: z.string().optional(),
@@ -518,6 +594,16 @@ export class OverBridgeServer {
518
594
  return this.methodAgentRun(req);
519
595
  case 'agent.a2a':
520
596
  return this.methodAgentA2A(req);
597
+ case 'agent.broadcast':
598
+ return this.methodAgentBroadcast(req);
599
+ case 'agent.pipeline':
600
+ return this.methodAgentPipeline(req);
601
+ case 'agent.fanout':
602
+ return this.methodAgentFanout(req);
603
+ case 'agent.delegate':
604
+ return this.methodAgentDelegate(req);
605
+ case 'agent.query':
606
+ return this.methodAgentQuery(req);
521
607
  case 'agent.status':
522
608
  return this.methodAgentStatus(req);
523
609
  case 'agent.list':
@@ -762,6 +848,449 @@ export class OverBridgeServer {
762
848
  });
763
849
  return { jsonrpc: '2.0', id: req.id ?? null, result };
764
850
  }
851
+ // ─── A2A Extended Methods ────────────────────────────────────────────────
852
+ /**
853
+ * agent.broadcast — Un agent → tous les agents (fan-out global).
854
+ * Si race=true, retourne dès qu'un agent répond.
855
+ * Si race=false (default), attend tous et retourne les résultats.
856
+ */
857
+ async methodAgentBroadcast(req) {
858
+ const params = this.validateParams(BroadcastParams, req.params, req.id);
859
+ if (!params.ok)
860
+ return params.response;
861
+ const { fromAgent, runner, prompt, targets, race, model } = params.data;
862
+ const validatedFrom = validateAgentName(fromAgent);
863
+ const reqId = req.params?.__reqId;
864
+ // Determine target list
865
+ let targetAgents;
866
+ if (targets && targets.length > 0) {
867
+ targetAgents = targets.map(validateAgentName);
868
+ }
869
+ else {
870
+ // All online agents (excluding fromAgent)
871
+ const onlineAgents = this.registry
872
+ .list({ status: 'online' })
873
+ .map((a) => a.name)
874
+ .filter((name) => name !== validatedFrom);
875
+ if (onlineAgents.length === 0) {
876
+ return {
877
+ jsonrpc: '2.0',
878
+ id: req.id ?? null,
879
+ error: { code: -32005, message: 'No online agents to broadcast to' },
880
+ };
881
+ }
882
+ targetAgents = onlineAgents;
883
+ }
884
+ this.log.info(`[reqId=${reqId}] 📡 Broadcast from ${validatedFrom} → ${targetAgents.length} agents (race=${race})`);
885
+ // Register + increment counters
886
+ this.registry.register(validatedFrom, runner);
887
+ this.registry.incrementA2aSent(validatedFrom);
888
+ for (const t of targetAgents) {
889
+ this.registry.register(t, runner);
890
+ this.registry.incrementA2aReceived(t);
891
+ }
892
+ const enrichedPrompt = this.buildA2aPrompt(validatedFrom, 'BROADCAST', prompt);
893
+ // Run agents
894
+ const runOne = async (agentName) => {
895
+ try {
896
+ const result = await this.service.runAgent({
897
+ runner: runner,
898
+ prompt: enrichedPrompt,
899
+ agentName,
900
+ model,
901
+ });
902
+ return {
903
+ agentName,
904
+ success: !result.isError,
905
+ content: result.content,
906
+ sessionId: result.sessionId,
907
+ };
908
+ }
909
+ catch (err) {
910
+ return {
911
+ agentName,
912
+ success: false,
913
+ error: err.message,
914
+ content: [],
915
+ };
916
+ }
917
+ };
918
+ let results;
919
+ if (race) {
920
+ // Return first successful result
921
+ const promises = targetAgents.map((t) => runOne(t));
922
+ const first = await Promise.race(promises);
923
+ results = [first];
924
+ }
925
+ else {
926
+ results = await Promise.all(targetAgents.map((t) => runOne(t)));
927
+ }
928
+ const successCount = results.filter((r) => r.success).length;
929
+ this.log.info(`[reqId=${reqId}] 📡 Broadcast done: ${successCount}/${results.length} success`);
930
+ return {
931
+ jsonrpc: '2.0',
932
+ id: req.id ?? null,
933
+ result: {
934
+ from: validatedFrom,
935
+ targets: targetAgents,
936
+ total: results.length,
937
+ successCount,
938
+ race,
939
+ results,
940
+ },
941
+ };
942
+ }
943
+ /**
944
+ * agent.pipeline — Chaîne séquentielle A→B→C.
945
+ * Le output de chaque step est injecté dans le prompt du suivant.
946
+ */
947
+ async methodAgentPipeline(req) {
948
+ const params = this.validateParams(PipelineParams, req.params, req.id);
949
+ if (!params.ok)
950
+ return params.response;
951
+ const { initiator, runner, prompt, steps, accumulateContext, totalTimeoutMs } = params.data;
952
+ const validatedInitiator = validateAgentName(initiator);
953
+ const reqId = req.params?.__reqId;
954
+ this.log.info(`[reqId=${reqId}] 🔗 Pipeline: ${validatedInitiator} → ${steps.map((s) => s.agentName).join(' → ')}`);
955
+ const deadline = Date.now() + totalTimeoutMs;
956
+ const outputs = [];
957
+ let currentPrompt = prompt;
958
+ for (let i = 0; i < steps.length; i++) {
959
+ const step = steps[i];
960
+ if (Date.now() > deadline) {
961
+ this.log.warn(`[reqId=${reqId}] ⏱️ Pipeline timeout at step ${i}/${steps.length}`);
962
+ break;
963
+ }
964
+ const validatedAgent = validateAgentName(step.agentName);
965
+ this.registry.register(validatedAgent, runner);
966
+ // Build prompt for this step
967
+ const stepPrompt = (step.promptPrefix ? step.promptPrefix + '\n\n' : '') +
968
+ `[Pipeline Step ${i + 1}/${steps.length} — Input from previous]\n${currentPrompt}`;
969
+ try {
970
+ const result = await this.service.runAgent({
971
+ runner: runner,
972
+ prompt: stepPrompt,
973
+ agentName: validatedAgent,
974
+ model: step.model,
975
+ });
976
+ const outputText = result.content.map((c) => c.text).join('\n');
977
+ const success = !result.isError;
978
+ outputs.push({ agentName: validatedAgent, output: outputText, success });
979
+ if (!success) {
980
+ this.log.warn(`[reqId=${reqId}] ❌ Pipeline step ${i + 1} (${validatedAgent}) failed — stopping`);
981
+ break;
982
+ }
983
+ // Next step gets: just the last output, or all accumulated outputs
984
+ if (accumulateContext) {
985
+ currentPrompt = outputs.map((o) => `[${o.agentName}]: ${o.output}`).join('\n\n---\n\n');
986
+ }
987
+ else {
988
+ currentPrompt = outputText;
989
+ }
990
+ }
991
+ catch (err) {
992
+ this.log.error(`[reqId=${reqId}] 💥 Pipeline step ${i + 1} (${validatedAgent}) error: ${err.message}`);
993
+ outputs.push({
994
+ agentName: validatedAgent,
995
+ output: '',
996
+ success: false,
997
+ });
998
+ break;
999
+ }
1000
+ }
1001
+ return {
1002
+ jsonrpc: '2.0',
1003
+ id: req.id ?? null,
1004
+ result: {
1005
+ initiator: validatedInitiator,
1006
+ totalSteps: steps.length,
1007
+ executedSteps: outputs.length,
1008
+ allSuccess: outputs.every((o) => o.success),
1009
+ outputs,
1010
+ finalOutput: outputs.length > 0 ? outputs[outputs.length - 1].output : '',
1011
+ },
1012
+ };
1013
+ }
1014
+ /**
1015
+ * agent.fanout — Un agent → plusieurs en parallèle + merge.
1016
+ * mergeStrategy: concat | best | vote | first_success
1017
+ */
1018
+ async methodAgentFanout(req) {
1019
+ const params = this.validateParams(FanoutParams, req.params, req.id);
1020
+ if (!params.ok)
1021
+ return params.response;
1022
+ const { fromAgent, runner, prompt, targets, mergeStrategy, model } = params.data;
1023
+ const validatedFrom = validateAgentName(fromAgent);
1024
+ const reqId = req.params?.__reqId;
1025
+ const targetAgents = targets.map(validateAgentName);
1026
+ this.log.info(`[reqId=${reqId}] 🌐 Fanout from ${validatedFrom} → ${targetAgents.length} agents (merge=${mergeStrategy})`);
1027
+ const enrichedPrompt = this.buildA2aPrompt(validatedFrom, 'FANOUT', prompt);
1028
+ // Run all targets in parallel
1029
+ const results = await Promise.all(targetAgents.map(async (agentName) => {
1030
+ try {
1031
+ const result = await this.service.runAgent({
1032
+ runner: runner,
1033
+ prompt: enrichedPrompt,
1034
+ agentName,
1035
+ model,
1036
+ });
1037
+ return {
1038
+ agentName,
1039
+ success: !result.isError,
1040
+ text: result.content.map((c) => c.text).join('\n'),
1041
+ sessionId: result.sessionId,
1042
+ };
1043
+ }
1044
+ catch (err) {
1045
+ return {
1046
+ agentName,
1047
+ success: false,
1048
+ text: err.message,
1049
+ sessionId: undefined,
1050
+ };
1051
+ }
1052
+ }));
1053
+ // Merge results
1054
+ let merged;
1055
+ let winner;
1056
+ switch (mergeStrategy) {
1057
+ case 'first_success': {
1058
+ const first = results.find((r) => r.success);
1059
+ merged = first ? first.text : 'All agents failed';
1060
+ winner = first?.agentName;
1061
+ break;
1062
+ }
1063
+ case 'best': {
1064
+ // Default: longest response
1065
+ const sorted = [...results]
1066
+ .filter((r) => r.success)
1067
+ .sort((a, b) => b.text.length - a.text.length);
1068
+ merged = sorted.length > 0 ? sorted[0].text : 'All agents failed';
1069
+ winner = sorted[0]?.agentName;
1070
+ break;
1071
+ }
1072
+ case 'vote': {
1073
+ // Simple vote: count similar responses (by length bucket)
1074
+ const buckets = new Map();
1075
+ for (const r of results.filter((r) => r.success)) {
1076
+ const bucket = Math.floor(r.text.length / 100) * 100;
1077
+ if (!buckets.has(bucket))
1078
+ buckets.set(bucket, []);
1079
+ buckets.get(bucket).push({ agentName: r.agentName, text: r.text });
1080
+ }
1081
+ const sortedBuckets = [...buckets.entries()].sort((a, b) => b[1].length - a[1].length);
1082
+ merged = sortedBuckets.length > 0 ? sortedBuckets[0][1][0].text : 'No votes';
1083
+ winner = sortedBuckets[0]?.[1]?.[0]?.agentName;
1084
+ break;
1085
+ }
1086
+ case 'concat':
1087
+ default: {
1088
+ merged = results
1089
+ .map((r) => `### ${r.agentName}${r.success ? '' : ' (FAILED)'}\n${r.text}`)
1090
+ .join('\n\n---\n\n');
1091
+ break;
1092
+ }
1093
+ }
1094
+ return {
1095
+ jsonrpc: '2.0',
1096
+ id: req.id ?? null,
1097
+ result: {
1098
+ from: validatedFrom,
1099
+ targets: targetAgents,
1100
+ mergeStrategy,
1101
+ winner,
1102
+ totalResults: results.length,
1103
+ successCount: results.filter((r) => r.success).length,
1104
+ results,
1105
+ merged,
1106
+ },
1107
+ };
1108
+ }
1109
+ /**
1110
+ * agent.delegate — Délégation async (fire-and-forget avec taskId).
1111
+ * Si async=true (default), retourne immédiatement avec un taskId.
1112
+ * Si callbackUrl fourni, POST le résultat quand l'agent termine.
1113
+ */
1114
+ async methodAgentDelegate(req) {
1115
+ const params = this.validateParams(DelegateParams, req.params, req.id);
1116
+ if (!params.ok)
1117
+ return params.response;
1118
+ const { fromAgent, toAgent, runner, prompt, model, callbackUrl, async: isAsync, metadata, } = params.data;
1119
+ const validatedFrom = validateAgentName(fromAgent);
1120
+ const validatedTo = validateAgentName(toAgent);
1121
+ const reqId = req.params?.__reqId;
1122
+ const taskId = crypto.randomUUID();
1123
+ this.log.info(`[reqId=${reqId}] 🤝 Delegate ${validatedFrom} → ${validatedTo} (taskId=${taskId}, async=${isAsync})`);
1124
+ const enrichedPrompt = this.buildA2aPrompt(validatedFrom, 'DELEGATE', prompt);
1125
+ // Persist
1126
+ let messageId;
1127
+ if (this.messageLog) {
1128
+ messageId = await this.messageLog.create({
1129
+ fromAgent: validatedFrom,
1130
+ toAgent: validatedTo,
1131
+ runner: runner,
1132
+ prompt: enrichedPrompt,
1133
+ metadata: { ...(metadata ?? {}), taskId, delegate: true },
1134
+ });
1135
+ }
1136
+ // Run async (fire-and-forget)
1137
+ const runAsync = async () => {
1138
+ try {
1139
+ const result = await this.service.runAgent({
1140
+ runner: runner,
1141
+ prompt: enrichedPrompt,
1142
+ agentName: validatedTo,
1143
+ model,
1144
+ });
1145
+ const responseText = result.content.map((c) => c.text).join('\n');
1146
+ if (messageId && this.messageLog) {
1147
+ await this.messageLog.markDone(messageId, responseText, result.sessionId);
1148
+ }
1149
+ // Callback si URL fournie
1150
+ if (callbackUrl) {
1151
+ try {
1152
+ await fetch(callbackUrl, {
1153
+ method: 'POST',
1154
+ headers: { 'Content-Type': 'application/json' },
1155
+ body: JSON.stringify({
1156
+ taskId,
1157
+ from: validatedFrom,
1158
+ to: validatedTo,
1159
+ success: !result.isError,
1160
+ content: result.content,
1161
+ sessionId: result.sessionId,
1162
+ }),
1163
+ });
1164
+ }
1165
+ catch (err) {
1166
+ this.log.warn(`[reqId=${reqId}] ⚠️ Callback ${callbackUrl} failed: ${err.message}`);
1167
+ }
1168
+ }
1169
+ }
1170
+ catch (err) {
1171
+ const errorMsg = err.message;
1172
+ if (messageId && this.messageLog) {
1173
+ await this.messageLog.markFailed(messageId, errorMsg);
1174
+ }
1175
+ this.log.error(`[reqId=${reqId}] 💥 Delegate ${taskId} failed: ${errorMsg}`);
1176
+ }
1177
+ };
1178
+ if (isAsync) {
1179
+ // Fire-and-forget — return immediately with taskId
1180
+ void runAsync();
1181
+ return {
1182
+ jsonrpc: '2.0',
1183
+ id: req.id ?? null,
1184
+ result: {
1185
+ taskId,
1186
+ from: validatedFrom,
1187
+ to: validatedTo,
1188
+ status: 'delegated',
1189
+ message: `Task delegated to ${validatedTo}. Use agent.status to check progress.`,
1190
+ messageId,
1191
+ },
1192
+ };
1193
+ }
1194
+ // Sync mode — wait for result
1195
+ try {
1196
+ await runAsync();
1197
+ // Read the result from MessageLog
1198
+ if (messageId && this.messageLog) {
1199
+ const msg = await this.messageLog.getById(messageId);
1200
+ return {
1201
+ jsonrpc: '2.0',
1202
+ id: req.id ?? null,
1203
+ result: {
1204
+ taskId,
1205
+ from: validatedFrom,
1206
+ to: validatedTo,
1207
+ status: msg?.status ?? 'unknown',
1208
+ content: msg?.response ? [{ type: 'text', text: msg.response }] : [],
1209
+ messageId,
1210
+ },
1211
+ };
1212
+ }
1213
+ return {
1214
+ jsonrpc: '2.0',
1215
+ id: req.id ?? null,
1216
+ result: { taskId, status: 'done', messageId },
1217
+ };
1218
+ }
1219
+ catch (err) {
1220
+ return {
1221
+ jsonrpc: '2.0',
1222
+ id: req.id ?? null,
1223
+ error: { code: -32603, message: `Delegate failed: ${err.message}` },
1224
+ };
1225
+ }
1226
+ }
1227
+ /**
1228
+ * agent.query — Query multi-agents, retourne toutes les réponses rapidement.
1229
+ * Comme fanout mais optimisé pour le read-only (courte réponse rapide).
1230
+ */
1231
+ async methodAgentQuery(req) {
1232
+ const params = this.validateParams(QueryParams, req.params, req.id);
1233
+ if (!params.ok)
1234
+ return params.response;
1235
+ const { fromAgent, runner, prompt, targets, agentTimeoutMs, model } = params.data;
1236
+ const validatedFrom = validateAgentName(fromAgent);
1237
+ const reqId = req.params?.__reqId;
1238
+ const targetAgents = targets.map(validateAgentName);
1239
+ this.log.info(`[reqId=${reqId}] ❓ Query from ${validatedFrom} → ${targetAgents.length} agents`);
1240
+ const enrichedPrompt = this.buildA2aPrompt(validatedFrom, 'QUERY', prompt);
1241
+ // Run all in parallel with timeout
1242
+ const results = await Promise.all(targetAgents.map(async (agentName) => {
1243
+ const controller = new AbortController();
1244
+ const timer = setTimeout(() => controller.abort(), agentTimeoutMs);
1245
+ try {
1246
+ const result = await this.service.runAgent({
1247
+ runner: runner,
1248
+ prompt: enrichedPrompt,
1249
+ agentName,
1250
+ model,
1251
+ });
1252
+ clearTimeout(timer);
1253
+ return {
1254
+ agentName,
1255
+ success: !result.isError,
1256
+ text: result.content.map((c) => c.text).join('\n'),
1257
+ responseTimeMs: 0, // Could track if needed
1258
+ };
1259
+ }
1260
+ catch (err) {
1261
+ clearTimeout(timer);
1262
+ return {
1263
+ agentName,
1264
+ success: false,
1265
+ text: err.message,
1266
+ responseTimeMs: agentTimeoutMs,
1267
+ };
1268
+ }
1269
+ }));
1270
+ return {
1271
+ jsonrpc: '2.0',
1272
+ id: req.id ?? null,
1273
+ result: {
1274
+ from: validatedFrom,
1275
+ totalQueried: targetAgents.length,
1276
+ successCount: results.filter((r) => r.success).length,
1277
+ results,
1278
+ },
1279
+ };
1280
+ }
1281
+ // ─── A2A Helpers ──────────────────────────────────────────────────────────
1282
+ /**
1283
+ * Construit un prompt A2A standardisé avec header.
1284
+ */
1285
+ buildA2aPrompt(fromAgent, kind, prompt) {
1286
+ return [
1287
+ `[A2A — ${kind}]`,
1288
+ `FROM: ${fromAgent}`,
1289
+ `TIMESTAMP: ${new Date().toISOString()}`,
1290
+ `━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━`,
1291
+ prompt,
1292
+ ].join('\n');
1293
+ }
765
1294
  /**
766
1295
  * agent.status — Status live d'un agent (busy/idle/online) via registry.
767
1296
  * Possibilité de proxy vers Overmind MCP agent_control aussi.