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 +1 -0
- package/dist/bridge/OverBridgeServer.d.ts +31 -0
- package/dist/bridge/OverBridgeServer.d.ts.map +1 -1
- package/dist/bridge/OverBridgeServer.js +529 -0
- package/dist/bridge/OverBridgeServer.js.map +1 -1
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +41 -1
- package/dist/server.js.map +1 -1
- package/dist/tools/a2a_hub.d.ts +73 -0
- package/dist/tools/a2a_hub.d.ts.map +1 -0
- package/dist/tools/a2a_hub.js +663 -0
- package/dist/tools/a2a_hub.js.map +1 -0
- package/package.json +1 -1
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":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAQH,OAAO,EAAE,iBAAiB,EAAE,MAAM,wBAAwB,CAAC;AAC3D,OAAO,EAAE,aAAa,EAAuB,MAAM,oBAAoB,CAAC;AACxE,OAAO,EAAE,UAAU,EAAE,KAAK,gBAAgB,EAAE,MAAM,iBAAiB,CAAC;AACpE,OAAO,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AAKjD,OAAO,EAAsB,KAAK,YAAY,EAAqB,MAAM,YAAY,CAAC;AAKtF,MAAM,WAAW,cAAc;IAC7B,OAAO,EAAE,KAAK,CAAC;IACf,EAAE,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAAC;IAC5B,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CAClC;AAED,MAAM,WAAW,eAAe;IAC9B,OAAO,EAAE,KAAK,CAAC;IACf,EAAE,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAAC;IAC3B,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,KAAK,CAAC,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,OAAO,CAAA;KAAE,CAAC;CAC3D;
|
|
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.
|