agentic-flow 1.6.2 → 1.6.4
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/CHANGELOG.md +175 -0
- package/README.md +142 -46
- package/dist/cli-proxy.js +75 -11
- package/dist/mcp/fastmcp/tools/swarm/init.js +18 -5
- package/dist/swarm/index.js +133 -0
- package/dist/swarm/quic-coordinator.js +460 -0
- package/dist/swarm/transport-router.js +374 -0
- package/dist/transport/quic-handshake.js +198 -0
- package/dist/transport/quic.js +581 -58
- package/dist/utils/cli.js +27 -12
- package/docs/architecture/QUIC-IMPLEMENTATION-SUMMARY.md +490 -0
- package/docs/architecture/QUIC-SWARM-INTEGRATION.md +593 -0
- package/docs/guides/QUIC-SWARM-QUICKSTART.md +543 -0
- package/docs/integration-docs/QUIC-WASM-INTEGRATION.md +537 -0
- package/docs/plans/QUIC/quic-tutorial.md +457 -0
- package/docs/quic/FINAL-VALIDATION.md +336 -0
- package/docs/quic/IMPLEMENTATION-COMPLETE-SUMMARY.md +349 -0
- package/docs/quic/PERFORMANCE-VALIDATION.md +282 -0
- package/docs/quic/QUIC-STATUS-OLD.md +513 -0
- package/docs/quic/QUIC-STATUS.md +451 -0
- package/docs/quic/QUIC-VALIDATION-REPORT.md +370 -0
- package/docs/quic/WASM-INTEGRATION-COMPLETE.md +382 -0
- package/package.json +1 -1
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
// Swarm Integration - Main export file for QUIC-enabled swarm coordination
|
|
2
|
+
// Provides unified interface for multi-agent swarm initialization and management
|
|
3
|
+
export { QuicCoordinator } from './quic-coordinator.js';
|
|
4
|
+
export { TransportRouter } from './transport-router.js';
|
|
5
|
+
import { QuicClient } from '../transport/quic.js';
|
|
6
|
+
import { TransportRouter } from './transport-router.js';
|
|
7
|
+
import { logger } from '../utils/logger.js';
|
|
8
|
+
/**
|
|
9
|
+
* Initialize a multi-agent swarm with QUIC transport
|
|
10
|
+
*
|
|
11
|
+
* @example
|
|
12
|
+
* ```typescript
|
|
13
|
+
* const swarm = await initSwarm({
|
|
14
|
+
* swarmId: 'my-swarm',
|
|
15
|
+
* topology: 'mesh',
|
|
16
|
+
* transport: 'quic',
|
|
17
|
+
* quicPort: 4433
|
|
18
|
+
* });
|
|
19
|
+
*
|
|
20
|
+
* await swarm.registerAgent({
|
|
21
|
+
* id: 'agent-1',
|
|
22
|
+
* role: 'worker',
|
|
23
|
+
* host: 'localhost',
|
|
24
|
+
* port: 4434,
|
|
25
|
+
* capabilities: ['compute', 'analyze']
|
|
26
|
+
* });
|
|
27
|
+
* ```
|
|
28
|
+
*/
|
|
29
|
+
export async function initSwarm(options) {
|
|
30
|
+
const { swarmId, topology, transport = 'auto', maxAgents = 10, quicPort = 4433, quicHost = 'localhost', enableFallback = true } = options;
|
|
31
|
+
logger.info('Initializing swarm', {
|
|
32
|
+
swarmId,
|
|
33
|
+
topology,
|
|
34
|
+
transport,
|
|
35
|
+
maxAgents,
|
|
36
|
+
quicPort
|
|
37
|
+
});
|
|
38
|
+
// Create transport router configuration
|
|
39
|
+
const transportConfig = {
|
|
40
|
+
protocol: transport,
|
|
41
|
+
enableFallback,
|
|
42
|
+
quicConfig: {
|
|
43
|
+
host: quicHost,
|
|
44
|
+
port: quicPort,
|
|
45
|
+
maxConnections: maxAgents * 2 // Allow some overhead
|
|
46
|
+
},
|
|
47
|
+
http2Config: {
|
|
48
|
+
host: quicHost,
|
|
49
|
+
port: quicPort + 1000, // HTTP/2 fallback port
|
|
50
|
+
maxConnections: maxAgents * 2,
|
|
51
|
+
secure: true
|
|
52
|
+
}
|
|
53
|
+
};
|
|
54
|
+
// Initialize transport router
|
|
55
|
+
const router = new TransportRouter(transportConfig);
|
|
56
|
+
await router.initialize();
|
|
57
|
+
const actualProtocol = router.getCurrentProtocol();
|
|
58
|
+
logger.info('Transport initialized', {
|
|
59
|
+
requestedProtocol: transport,
|
|
60
|
+
actualProtocol,
|
|
61
|
+
quicAvailable: router.isQuicAvailable()
|
|
62
|
+
});
|
|
63
|
+
// Initialize swarm coordinator if using QUIC
|
|
64
|
+
let coordinator;
|
|
65
|
+
if (actualProtocol === 'quic') {
|
|
66
|
+
coordinator = await router.initializeSwarm(swarmId, topology, maxAgents);
|
|
67
|
+
}
|
|
68
|
+
// Create swarm instance
|
|
69
|
+
const swarm = {
|
|
70
|
+
swarmId,
|
|
71
|
+
topology,
|
|
72
|
+
transport: actualProtocol,
|
|
73
|
+
coordinator,
|
|
74
|
+
router,
|
|
75
|
+
async registerAgent(agent) {
|
|
76
|
+
if (coordinator) {
|
|
77
|
+
await coordinator.registerAgent(agent);
|
|
78
|
+
}
|
|
79
|
+
else {
|
|
80
|
+
logger.warn('QUIC coordinator not available, agent registration skipped', {
|
|
81
|
+
agentId: agent.id
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
},
|
|
85
|
+
async unregisterAgent(agentId) {
|
|
86
|
+
if (coordinator) {
|
|
87
|
+
await coordinator.unregisterAgent(agentId);
|
|
88
|
+
}
|
|
89
|
+
else {
|
|
90
|
+
logger.warn('QUIC coordinator not available, agent unregistration skipped', {
|
|
91
|
+
agentId
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
},
|
|
95
|
+
async getStats() {
|
|
96
|
+
return {
|
|
97
|
+
swarmId,
|
|
98
|
+
topology,
|
|
99
|
+
transport: actualProtocol,
|
|
100
|
+
coordinatorStats: coordinator ? (await coordinator.getState()).stats : undefined,
|
|
101
|
+
transportStats: router.getStats(),
|
|
102
|
+
quicAvailable: router.isQuicAvailable()
|
|
103
|
+
};
|
|
104
|
+
},
|
|
105
|
+
async shutdown() {
|
|
106
|
+
logger.info('Shutting down swarm', { swarmId });
|
|
107
|
+
await router.shutdown();
|
|
108
|
+
logger.info('Swarm shutdown complete', { swarmId });
|
|
109
|
+
}
|
|
110
|
+
};
|
|
111
|
+
logger.info('Swarm initialized successfully', {
|
|
112
|
+
swarmId,
|
|
113
|
+
topology,
|
|
114
|
+
transport: actualProtocol,
|
|
115
|
+
quicEnabled: coordinator !== undefined
|
|
116
|
+
});
|
|
117
|
+
return swarm;
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* Check if QUIC transport is available
|
|
121
|
+
*/
|
|
122
|
+
export async function checkQuicAvailability() {
|
|
123
|
+
try {
|
|
124
|
+
const client = new QuicClient();
|
|
125
|
+
await client.initialize();
|
|
126
|
+
await client.shutdown();
|
|
127
|
+
return true;
|
|
128
|
+
}
|
|
129
|
+
catch (error) {
|
|
130
|
+
logger.debug('QUIC not available', { error });
|
|
131
|
+
return false;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
@@ -0,0 +1,460 @@
|
|
|
1
|
+
// QUIC-enabled Swarm Coordinator
|
|
2
|
+
// Manages agent-to-agent communication over QUIC transport with fallback to HTTP/2
|
|
3
|
+
import { logger } from '../utils/logger.js';
|
|
4
|
+
/**
|
|
5
|
+
* QuicCoordinator - Manages multi-agent swarm coordination over QUIC
|
|
6
|
+
*
|
|
7
|
+
* Features:
|
|
8
|
+
* - Agent-to-agent communication via QUIC streams
|
|
9
|
+
* - Topology-aware message routing (mesh, hierarchical, ring, star)
|
|
10
|
+
* - Connection pooling for efficient resource usage
|
|
11
|
+
* - Real-time state synchronization
|
|
12
|
+
* - Per-agent statistics tracking
|
|
13
|
+
*/
|
|
14
|
+
export class QuicCoordinator {
|
|
15
|
+
config;
|
|
16
|
+
state;
|
|
17
|
+
messageQueue;
|
|
18
|
+
heartbeatTimer;
|
|
19
|
+
syncTimer;
|
|
20
|
+
messageStats;
|
|
21
|
+
constructor(config) {
|
|
22
|
+
this.config = {
|
|
23
|
+
...config,
|
|
24
|
+
heartbeatInterval: config.heartbeatInterval || 10000,
|
|
25
|
+
statesSyncInterval: config.statesSyncInterval || 5000,
|
|
26
|
+
enableCompression: config.enableCompression ?? true
|
|
27
|
+
};
|
|
28
|
+
this.state = {
|
|
29
|
+
swarmId: config.swarmId,
|
|
30
|
+
topology: config.topology,
|
|
31
|
+
agents: new Map(),
|
|
32
|
+
connections: new Map(),
|
|
33
|
+
stats: {
|
|
34
|
+
totalAgents: 0,
|
|
35
|
+
activeAgents: 0,
|
|
36
|
+
totalMessages: 0,
|
|
37
|
+
messagesPerSecond: 0,
|
|
38
|
+
averageLatency: 0,
|
|
39
|
+
quicStats: {
|
|
40
|
+
totalConnections: 0,
|
|
41
|
+
activeConnections: 0,
|
|
42
|
+
totalStreams: 0,
|
|
43
|
+
activeStreams: 0,
|
|
44
|
+
bytesReceived: 0,
|
|
45
|
+
bytesSent: 0,
|
|
46
|
+
packetsLost: 0,
|
|
47
|
+
rttMs: 0
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
};
|
|
51
|
+
this.messageQueue = [];
|
|
52
|
+
this.messageStats = new Map();
|
|
53
|
+
logger.info('QUIC Coordinator initialized', {
|
|
54
|
+
swarmId: config.swarmId,
|
|
55
|
+
topology: config.topology,
|
|
56
|
+
maxAgents: config.maxAgents
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Start the coordinator (heartbeat and state sync)
|
|
61
|
+
*/
|
|
62
|
+
async start() {
|
|
63
|
+
logger.info('Starting QUIC Coordinator', { swarmId: this.config.swarmId });
|
|
64
|
+
// Initialize QUIC client
|
|
65
|
+
await this.config.quicClient.initialize();
|
|
66
|
+
// Start heartbeat
|
|
67
|
+
this.startHeartbeat();
|
|
68
|
+
// Start state synchronization
|
|
69
|
+
this.startStateSync();
|
|
70
|
+
logger.info('QUIC Coordinator started successfully');
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Stop the coordinator
|
|
74
|
+
*/
|
|
75
|
+
async stop() {
|
|
76
|
+
logger.info('Stopping QUIC Coordinator', { swarmId: this.config.swarmId });
|
|
77
|
+
// Stop timers
|
|
78
|
+
if (this.heartbeatTimer) {
|
|
79
|
+
clearInterval(this.heartbeatTimer);
|
|
80
|
+
}
|
|
81
|
+
if (this.syncTimer) {
|
|
82
|
+
clearInterval(this.syncTimer);
|
|
83
|
+
}
|
|
84
|
+
// Close all connections
|
|
85
|
+
for (const [agentId, connection] of this.state.connections.entries()) {
|
|
86
|
+
await this.config.quicClient.closeConnection(connection.id);
|
|
87
|
+
logger.debug('Closed connection', { agentId, connectionId: connection.id });
|
|
88
|
+
}
|
|
89
|
+
// Shutdown QUIC client
|
|
90
|
+
await this.config.quicClient.shutdown();
|
|
91
|
+
logger.info('QUIC Coordinator stopped');
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Register an agent in the swarm
|
|
95
|
+
*/
|
|
96
|
+
async registerAgent(agent) {
|
|
97
|
+
if (this.state.agents.size >= this.config.maxAgents) {
|
|
98
|
+
throw new Error(`Maximum agents (${this.config.maxAgents}) reached`);
|
|
99
|
+
}
|
|
100
|
+
logger.info('Registering agent', {
|
|
101
|
+
agentId: agent.id,
|
|
102
|
+
role: agent.role,
|
|
103
|
+
host: agent.host,
|
|
104
|
+
port: agent.port
|
|
105
|
+
});
|
|
106
|
+
// Establish QUIC connection to agent
|
|
107
|
+
const connection = await this.config.connectionPool.getConnection(agent.host, agent.port);
|
|
108
|
+
// Store agent and connection
|
|
109
|
+
this.state.agents.set(agent.id, agent);
|
|
110
|
+
this.state.connections.set(agent.id, connection);
|
|
111
|
+
this.messageStats.set(agent.id, { sent: 0, received: 0, latency: [] });
|
|
112
|
+
// Update stats
|
|
113
|
+
this.state.stats.totalAgents = this.state.agents.size;
|
|
114
|
+
this.state.stats.activeAgents = this.state.agents.size;
|
|
115
|
+
// Establish topology-specific connections
|
|
116
|
+
await this.establishTopologyConnections(agent);
|
|
117
|
+
logger.info('Agent registered successfully', { agentId: agent.id });
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* Unregister an agent from the swarm
|
|
121
|
+
*/
|
|
122
|
+
async unregisterAgent(agentId) {
|
|
123
|
+
const agent = this.state.agents.get(agentId);
|
|
124
|
+
if (!agent) {
|
|
125
|
+
logger.warn('Agent not found', { agentId });
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
logger.info('Unregistering agent', { agentId });
|
|
129
|
+
// Close connection
|
|
130
|
+
const connection = this.state.connections.get(agentId);
|
|
131
|
+
if (connection) {
|
|
132
|
+
await this.config.quicClient.closeConnection(connection.id);
|
|
133
|
+
}
|
|
134
|
+
// Remove from state
|
|
135
|
+
this.state.agents.delete(agentId);
|
|
136
|
+
this.state.connections.delete(agentId);
|
|
137
|
+
this.messageStats.delete(agentId);
|
|
138
|
+
// Update stats
|
|
139
|
+
this.state.stats.totalAgents = this.state.agents.size;
|
|
140
|
+
this.state.stats.activeAgents = this.state.agents.size;
|
|
141
|
+
logger.info('Agent unregistered successfully', { agentId });
|
|
142
|
+
}
|
|
143
|
+
/**
|
|
144
|
+
* Send message to one or more agents
|
|
145
|
+
*/
|
|
146
|
+
async sendMessage(message) {
|
|
147
|
+
const startTime = Date.now();
|
|
148
|
+
logger.debug('Sending message', {
|
|
149
|
+
messageId: message.id,
|
|
150
|
+
from: message.from,
|
|
151
|
+
to: message.to,
|
|
152
|
+
type: message.type
|
|
153
|
+
});
|
|
154
|
+
// Determine recipients based on topology
|
|
155
|
+
const recipients = this.resolveRecipients(message);
|
|
156
|
+
// Send message to each recipient
|
|
157
|
+
for (const recipientId of recipients) {
|
|
158
|
+
await this.sendToAgent(recipientId, message);
|
|
159
|
+
}
|
|
160
|
+
// Update stats
|
|
161
|
+
const latency = Date.now() - startTime;
|
|
162
|
+
this.updateMessageStats(message.from, 'sent', latency);
|
|
163
|
+
this.state.stats.totalMessages++;
|
|
164
|
+
logger.debug('Message sent successfully', {
|
|
165
|
+
messageId: message.id,
|
|
166
|
+
recipients: recipients.length,
|
|
167
|
+
latency
|
|
168
|
+
});
|
|
169
|
+
}
|
|
170
|
+
/**
|
|
171
|
+
* Broadcast message to all agents (except sender)
|
|
172
|
+
*/
|
|
173
|
+
async broadcast(message) {
|
|
174
|
+
const broadcastMessage = {
|
|
175
|
+
...message,
|
|
176
|
+
to: '*'
|
|
177
|
+
};
|
|
178
|
+
await this.sendMessage(broadcastMessage);
|
|
179
|
+
}
|
|
180
|
+
/**
|
|
181
|
+
* Get current swarm state
|
|
182
|
+
*/
|
|
183
|
+
async getState() {
|
|
184
|
+
return {
|
|
185
|
+
...this.state,
|
|
186
|
+
stats: await this.calculateStats()
|
|
187
|
+
};
|
|
188
|
+
}
|
|
189
|
+
/**
|
|
190
|
+
* Get agent statistics
|
|
191
|
+
*/
|
|
192
|
+
getAgentStats(agentId) {
|
|
193
|
+
const stats = this.messageStats.get(agentId);
|
|
194
|
+
if (!stats) {
|
|
195
|
+
return null;
|
|
196
|
+
}
|
|
197
|
+
return {
|
|
198
|
+
sent: stats.sent,
|
|
199
|
+
received: stats.received,
|
|
200
|
+
avgLatency: stats.latency.length > 0
|
|
201
|
+
? stats.latency.reduce((sum, l) => sum + l, 0) / stats.latency.length
|
|
202
|
+
: 0
|
|
203
|
+
};
|
|
204
|
+
}
|
|
205
|
+
/**
|
|
206
|
+
* Get all agent statistics
|
|
207
|
+
*/
|
|
208
|
+
getAllAgentStats() {
|
|
209
|
+
const allStats = new Map();
|
|
210
|
+
for (const [agentId, stats] of this.messageStats.entries()) {
|
|
211
|
+
allStats.set(agentId, {
|
|
212
|
+
sent: stats.sent,
|
|
213
|
+
received: stats.received,
|
|
214
|
+
avgLatency: stats.latency.length > 0
|
|
215
|
+
? stats.latency.reduce((sum, l) => sum + l, 0) / stats.latency.length
|
|
216
|
+
: 0
|
|
217
|
+
});
|
|
218
|
+
}
|
|
219
|
+
return allStats;
|
|
220
|
+
}
|
|
221
|
+
/**
|
|
222
|
+
* Synchronize state across all agents
|
|
223
|
+
*/
|
|
224
|
+
async syncState() {
|
|
225
|
+
logger.debug('Synchronizing swarm state', { swarmId: this.config.swarmId });
|
|
226
|
+
const stateMessage = {
|
|
227
|
+
id: `sync-${Date.now()}`,
|
|
228
|
+
from: 'coordinator',
|
|
229
|
+
to: '*',
|
|
230
|
+
type: 'sync',
|
|
231
|
+
payload: {
|
|
232
|
+
swarmId: this.state.swarmId,
|
|
233
|
+
topology: this.state.topology,
|
|
234
|
+
agents: Array.from(this.state.agents.values()),
|
|
235
|
+
stats: this.calculateStats()
|
|
236
|
+
},
|
|
237
|
+
timestamp: Date.now()
|
|
238
|
+
};
|
|
239
|
+
await this.broadcast(stateMessage);
|
|
240
|
+
}
|
|
241
|
+
// ========== Private Methods ==========
|
|
242
|
+
/**
|
|
243
|
+
* Establish topology-specific connections
|
|
244
|
+
*/
|
|
245
|
+
async establishTopologyConnections(agent) {
|
|
246
|
+
switch (this.config.topology) {
|
|
247
|
+
case 'mesh':
|
|
248
|
+
// In mesh, each agent connects to all others (handled by caller)
|
|
249
|
+
logger.debug('Mesh topology: agent connects to all', { agentId: agent.id });
|
|
250
|
+
break;
|
|
251
|
+
case 'hierarchical':
|
|
252
|
+
// In hierarchical, workers connect to coordinators
|
|
253
|
+
if (agent.role === 'worker') {
|
|
254
|
+
const coordinators = Array.from(this.state.agents.values())
|
|
255
|
+
.filter(a => a.role === 'coordinator');
|
|
256
|
+
logger.debug('Hierarchical topology: connecting worker to coordinators', {
|
|
257
|
+
agentId: agent.id,
|
|
258
|
+
coordinators: coordinators.length
|
|
259
|
+
});
|
|
260
|
+
}
|
|
261
|
+
break;
|
|
262
|
+
case 'ring':
|
|
263
|
+
// In ring, each agent connects to next agent in circular order
|
|
264
|
+
const agents = Array.from(this.state.agents.values());
|
|
265
|
+
if (agents.length > 1) {
|
|
266
|
+
logger.debug('Ring topology: establishing ring connections', {
|
|
267
|
+
agentId: agent.id,
|
|
268
|
+
totalAgents: agents.length
|
|
269
|
+
});
|
|
270
|
+
}
|
|
271
|
+
break;
|
|
272
|
+
case 'star':
|
|
273
|
+
// In star, all agents connect to central coordinator
|
|
274
|
+
if (agent.role === 'coordinator') {
|
|
275
|
+
logger.debug('Star topology: coordinator established', { agentId: agent.id });
|
|
276
|
+
}
|
|
277
|
+
else {
|
|
278
|
+
const coordinator = Array.from(this.state.agents.values())
|
|
279
|
+
.find(a => a.role === 'coordinator');
|
|
280
|
+
if (coordinator) {
|
|
281
|
+
logger.debug('Star topology: connecting to coordinator', {
|
|
282
|
+
agentId: agent.id,
|
|
283
|
+
coordinator: coordinator.id
|
|
284
|
+
});
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
break;
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
/**
|
|
291
|
+
* Resolve message recipients based on topology
|
|
292
|
+
*/
|
|
293
|
+
resolveRecipients(message) {
|
|
294
|
+
const recipients = [];
|
|
295
|
+
const to = Array.isArray(message.to) ? message.to : [message.to];
|
|
296
|
+
for (const target of to) {
|
|
297
|
+
if (target === '*') {
|
|
298
|
+
// Broadcast to all (except sender)
|
|
299
|
+
recipients.push(...Array.from(this.state.agents.keys())
|
|
300
|
+
.filter(id => id !== message.from));
|
|
301
|
+
}
|
|
302
|
+
else {
|
|
303
|
+
recipients.push(target);
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
// Apply topology-specific routing
|
|
307
|
+
return this.applyTopologyRouting(message.from, recipients);
|
|
308
|
+
}
|
|
309
|
+
/**
|
|
310
|
+
* Apply topology-specific routing rules
|
|
311
|
+
*/
|
|
312
|
+
applyTopologyRouting(senderId, recipients) {
|
|
313
|
+
switch (this.config.topology) {
|
|
314
|
+
case 'mesh':
|
|
315
|
+
// Direct routing in mesh topology
|
|
316
|
+
return recipients;
|
|
317
|
+
case 'hierarchical':
|
|
318
|
+
// Route through coordinator in hierarchical
|
|
319
|
+
const sender = this.state.agents.get(senderId);
|
|
320
|
+
if (sender?.role === 'worker') {
|
|
321
|
+
// Worker messages go through coordinator
|
|
322
|
+
const coordinators = Array.from(this.state.agents.values())
|
|
323
|
+
.filter(a => a.role === 'coordinator')
|
|
324
|
+
.map(a => a.id);
|
|
325
|
+
return coordinators.length > 0 ? coordinators : recipients;
|
|
326
|
+
}
|
|
327
|
+
return recipients;
|
|
328
|
+
case 'ring':
|
|
329
|
+
// Forward to next agent in ring
|
|
330
|
+
const agents = Array.from(this.state.agents.keys());
|
|
331
|
+
const currentIndex = agents.indexOf(senderId);
|
|
332
|
+
const nextIndex = (currentIndex + 1) % agents.length;
|
|
333
|
+
return [agents[nextIndex]];
|
|
334
|
+
case 'star':
|
|
335
|
+
// Route through central coordinator
|
|
336
|
+
const coordinator = Array.from(this.state.agents.values())
|
|
337
|
+
.find(a => a.role === 'coordinator');
|
|
338
|
+
if (coordinator && senderId !== coordinator.id) {
|
|
339
|
+
return [coordinator.id];
|
|
340
|
+
}
|
|
341
|
+
return recipients;
|
|
342
|
+
default:
|
|
343
|
+
return recipients;
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
/**
|
|
347
|
+
* Send message to specific agent
|
|
348
|
+
*/
|
|
349
|
+
async sendToAgent(agentId, message) {
|
|
350
|
+
const connection = this.state.connections.get(agentId);
|
|
351
|
+
if (!connection) {
|
|
352
|
+
logger.warn('Connection not found for agent', { agentId });
|
|
353
|
+
return;
|
|
354
|
+
}
|
|
355
|
+
try {
|
|
356
|
+
// Create QUIC stream
|
|
357
|
+
const stream = await this.config.quicClient.createStream(connection.id);
|
|
358
|
+
// Serialize message
|
|
359
|
+
const messageBytes = this.serializeMessage(message);
|
|
360
|
+
// Send message
|
|
361
|
+
await stream.send(messageBytes);
|
|
362
|
+
// Close stream
|
|
363
|
+
await stream.close();
|
|
364
|
+
logger.debug('Message sent to agent', { agentId, messageId: message.id });
|
|
365
|
+
}
|
|
366
|
+
catch (error) {
|
|
367
|
+
logger.error('Failed to send message to agent', { agentId, error });
|
|
368
|
+
throw error;
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
/**
|
|
372
|
+
* Serialize message to bytes
|
|
373
|
+
*/
|
|
374
|
+
serializeMessage(message) {
|
|
375
|
+
const json = JSON.stringify(message);
|
|
376
|
+
const encoder = new TextEncoder();
|
|
377
|
+
return encoder.encode(json);
|
|
378
|
+
}
|
|
379
|
+
/**
|
|
380
|
+
* Start heartbeat timer
|
|
381
|
+
*/
|
|
382
|
+
startHeartbeat() {
|
|
383
|
+
this.heartbeatTimer = setInterval(async () => {
|
|
384
|
+
logger.debug('Sending heartbeat', { swarmId: this.config.swarmId });
|
|
385
|
+
const heartbeat = {
|
|
386
|
+
id: `heartbeat-${Date.now()}`,
|
|
387
|
+
from: 'coordinator',
|
|
388
|
+
to: '*',
|
|
389
|
+
type: 'heartbeat',
|
|
390
|
+
payload: { timestamp: Date.now() },
|
|
391
|
+
timestamp: Date.now()
|
|
392
|
+
};
|
|
393
|
+
try {
|
|
394
|
+
await this.broadcast(heartbeat);
|
|
395
|
+
}
|
|
396
|
+
catch (error) {
|
|
397
|
+
logger.error('Heartbeat failed', { error });
|
|
398
|
+
}
|
|
399
|
+
}, this.config.heartbeatInterval);
|
|
400
|
+
}
|
|
401
|
+
/**
|
|
402
|
+
* Start state sync timer
|
|
403
|
+
*/
|
|
404
|
+
startStateSync() {
|
|
405
|
+
this.syncTimer = setInterval(async () => {
|
|
406
|
+
try {
|
|
407
|
+
await this.syncState();
|
|
408
|
+
}
|
|
409
|
+
catch (error) {
|
|
410
|
+
logger.error('State sync failed', { error });
|
|
411
|
+
}
|
|
412
|
+
}, this.config.statesSyncInterval);
|
|
413
|
+
}
|
|
414
|
+
/**
|
|
415
|
+
* Update message statistics
|
|
416
|
+
*/
|
|
417
|
+
updateMessageStats(agentId, type, latency) {
|
|
418
|
+
const stats = this.messageStats.get(agentId);
|
|
419
|
+
if (!stats) {
|
|
420
|
+
return;
|
|
421
|
+
}
|
|
422
|
+
if (type === 'sent') {
|
|
423
|
+
stats.sent++;
|
|
424
|
+
}
|
|
425
|
+
else {
|
|
426
|
+
stats.received++;
|
|
427
|
+
}
|
|
428
|
+
stats.latency.push(latency);
|
|
429
|
+
// Keep only last 100 latency measurements
|
|
430
|
+
if (stats.latency.length > 100) {
|
|
431
|
+
stats.latency.shift();
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
/**
|
|
435
|
+
* Calculate current swarm statistics
|
|
436
|
+
*/
|
|
437
|
+
async calculateStats() {
|
|
438
|
+
const quicStats = await this.config.quicClient.getStats();
|
|
439
|
+
// Calculate messages per second (last minute average)
|
|
440
|
+
const messagesPerSecond = this.state.stats.totalMessages / 60;
|
|
441
|
+
// Calculate average latency across all agents
|
|
442
|
+
let totalLatency = 0;
|
|
443
|
+
let latencyCount = 0;
|
|
444
|
+
for (const stats of this.messageStats.values()) {
|
|
445
|
+
if (stats.latency.length > 0) {
|
|
446
|
+
totalLatency += stats.latency.reduce((sum, l) => sum + l, 0);
|
|
447
|
+
latencyCount += stats.latency.length;
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
const averageLatency = latencyCount > 0 ? totalLatency / latencyCount : 0;
|
|
451
|
+
return {
|
|
452
|
+
totalAgents: this.state.agents.size,
|
|
453
|
+
activeAgents: this.state.agents.size,
|
|
454
|
+
totalMessages: this.state.stats.totalMessages,
|
|
455
|
+
messagesPerSecond,
|
|
456
|
+
averageLatency,
|
|
457
|
+
quicStats
|
|
458
|
+
};
|
|
459
|
+
}
|
|
460
|
+
}
|