@sparkleideas/swarm 3.0.0-alpha.7
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/MIGRATION.md +472 -0
- package/README.md +634 -0
- package/__tests__/consensus.test.ts +577 -0
- package/__tests__/coordinator.test.ts +501 -0
- package/__tests__/queen-coordinator.test.ts +1335 -0
- package/__tests__/topology.test.ts +621 -0
- package/package.json +32 -0
- package/src/agent-pool.ts +476 -0
- package/src/application/commands/create-task.command.ts +124 -0
- package/src/application/commands/spawn-agent.command.ts +122 -0
- package/src/application/index.ts +30 -0
- package/src/application/services/swarm-application-service.ts +200 -0
- package/src/attention-coordinator.ts +1000 -0
- package/src/consensus/byzantine.ts +431 -0
- package/src/consensus/gossip.ts +513 -0
- package/src/consensus/index.ts +267 -0
- package/src/consensus/raft.ts +443 -0
- package/src/coordination/agent-registry.ts +544 -0
- package/src/coordination/index.ts +23 -0
- package/src/coordination/swarm-hub.ts +776 -0
- package/src/coordination/task-orchestrator.ts +605 -0
- package/src/domain/entities/agent.d.ts +151 -0
- package/src/domain/entities/agent.d.ts.map +1 -0
- package/src/domain/entities/agent.js +280 -0
- package/src/domain/entities/agent.js.map +1 -0
- package/src/domain/entities/agent.ts +370 -0
- package/src/domain/entities/task.d.ts +133 -0
- package/src/domain/entities/task.d.ts.map +1 -0
- package/src/domain/entities/task.js +261 -0
- package/src/domain/entities/task.js.map +1 -0
- package/src/domain/entities/task.ts +319 -0
- package/src/domain/index.ts +41 -0
- package/src/domain/repositories/agent-repository.interface.d.ts +57 -0
- package/src/domain/repositories/agent-repository.interface.d.ts.map +1 -0
- package/src/domain/repositories/agent-repository.interface.js +9 -0
- package/src/domain/repositories/agent-repository.interface.js.map +1 -0
- package/src/domain/repositories/agent-repository.interface.ts +69 -0
- package/src/domain/repositories/task-repository.interface.d.ts +61 -0
- package/src/domain/repositories/task-repository.interface.d.ts.map +1 -0
- package/src/domain/repositories/task-repository.interface.js +9 -0
- package/src/domain/repositories/task-repository.interface.js.map +1 -0
- package/src/domain/repositories/task-repository.interface.ts +75 -0
- package/src/domain/services/coordination-service.ts +320 -0
- package/src/federation-hub.d.ts +284 -0
- package/src/federation-hub.d.ts.map +1 -0
- package/src/federation-hub.js +692 -0
- package/src/federation-hub.js.map +1 -0
- package/src/federation-hub.ts +979 -0
- package/src/index.ts +348 -0
- package/src/message-bus.ts +607 -0
- package/src/queen-coordinator.ts +2025 -0
- package/src/shared/events.ts +285 -0
- package/src/shared/types.ts +389 -0
- package/src/topology-manager.ts +656 -0
- package/src/types.ts +545 -0
- package/src/unified-coordinator.ts +1844 -0
- package/src/workers/index.ts +65 -0
- package/src/workers/worker-dispatch.d.ts +234 -0
- package/src/workers/worker-dispatch.d.ts.map +1 -0
- package/src/workers/worker-dispatch.js +842 -0
- package/src/workers/worker-dispatch.js.map +1 -0
- package/src/workers/worker-dispatch.ts +1076 -0
- package/tmp.json +0 -0
- package/tsconfig.json +9 -0
- package/vitest.config.ts +20 -0
|
@@ -0,0 +1,1844 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* V3 Unified Swarm Coordinator
|
|
3
|
+
* Consolidates SwarmCoordinator, HiveMind, Maestro, and AgentManager into a single system
|
|
4
|
+
* Supports the 15-agent hierarchical mesh structure with domain-based task routing
|
|
5
|
+
*
|
|
6
|
+
* Performance Targets:
|
|
7
|
+
* - Agent coordination: <100ms for 15 agents
|
|
8
|
+
* - Consensus: <100ms
|
|
9
|
+
* - Message throughput: 1000+ msgs/sec
|
|
10
|
+
*
|
|
11
|
+
* Agent Hierarchy:
|
|
12
|
+
* - Queen (Agent 1): Top-level coordinator
|
|
13
|
+
* - Security Domain (Agents 2-4): security-architect, security-auditor, test-architect
|
|
14
|
+
* - Core Domain (Agents 5-9): core-architect, type-modernization, memory-specialist, swarm-specialist, mcp-optimizer
|
|
15
|
+
* - Integration Domain (Agents 10-12): integration-architect, cli-modernizer, neural-integrator
|
|
16
|
+
* - Support Domain (Agents 13-15): test-architect, performance-engineer, deployment-engineer
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
import { EventEmitter } from 'events';
|
|
20
|
+
import {
|
|
21
|
+
SwarmId,
|
|
22
|
+
AgentId,
|
|
23
|
+
TaskId,
|
|
24
|
+
AgentState,
|
|
25
|
+
AgentType,
|
|
26
|
+
AgentStatus,
|
|
27
|
+
AgentCapabilities,
|
|
28
|
+
AgentMetrics,
|
|
29
|
+
TaskDefinition,
|
|
30
|
+
TaskType,
|
|
31
|
+
TaskStatus,
|
|
32
|
+
TaskPriority,
|
|
33
|
+
CoordinatorConfig,
|
|
34
|
+
CoordinatorState,
|
|
35
|
+
CoordinatorMetrics,
|
|
36
|
+
SwarmStatus,
|
|
37
|
+
SwarmEvent,
|
|
38
|
+
SwarmEventType,
|
|
39
|
+
TopologyConfig,
|
|
40
|
+
TopologyType,
|
|
41
|
+
ConsensusConfig,
|
|
42
|
+
ConsensusResult,
|
|
43
|
+
Message,
|
|
44
|
+
MessageType,
|
|
45
|
+
PerformanceReport,
|
|
46
|
+
IUnifiedSwarmCoordinator,
|
|
47
|
+
SWARM_CONSTANTS,
|
|
48
|
+
} from './types.js';
|
|
49
|
+
import { TopologyManager, createTopologyManager } from './topology-manager.js';
|
|
50
|
+
import { MessageBus, createMessageBus } from './message-bus.js';
|
|
51
|
+
import { AgentPool, createAgentPool } from './agent-pool.js';
|
|
52
|
+
import { ConsensusEngine, createConsensusEngine } from './consensus/index.js';
|
|
53
|
+
|
|
54
|
+
// =============================================================================
|
|
55
|
+
// Domain Types for 15-Agent Hierarchy
|
|
56
|
+
// =============================================================================
|
|
57
|
+
|
|
58
|
+
export type AgentDomain = 'queen' | 'security' | 'core' | 'integration' | 'support';
|
|
59
|
+
|
|
60
|
+
export interface DomainConfig {
|
|
61
|
+
name: AgentDomain;
|
|
62
|
+
agentNumbers: number[];
|
|
63
|
+
priority: number;
|
|
64
|
+
capabilities: string[];
|
|
65
|
+
description: string;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export interface TaskAssignment {
|
|
69
|
+
taskId: string;
|
|
70
|
+
domain: AgentDomain;
|
|
71
|
+
agentId: string;
|
|
72
|
+
priority: TaskPriority;
|
|
73
|
+
assignedAt: Date;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export interface ParallelExecutionResult {
|
|
77
|
+
taskId: string;
|
|
78
|
+
domain: AgentDomain;
|
|
79
|
+
success: boolean;
|
|
80
|
+
result?: unknown;
|
|
81
|
+
error?: Error;
|
|
82
|
+
durationMs: number;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export interface DomainStatus {
|
|
86
|
+
name: AgentDomain;
|
|
87
|
+
agentCount: number;
|
|
88
|
+
availableAgents: number;
|
|
89
|
+
busyAgents: number;
|
|
90
|
+
tasksQueued: number;
|
|
91
|
+
tasksCompleted: number;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// =============================================================================
|
|
95
|
+
// 15-Agent Domain Configuration
|
|
96
|
+
// =============================================================================
|
|
97
|
+
|
|
98
|
+
const DOMAIN_CONFIGS: DomainConfig[] = [
|
|
99
|
+
{
|
|
100
|
+
name: 'queen',
|
|
101
|
+
agentNumbers: [1],
|
|
102
|
+
priority: 0,
|
|
103
|
+
capabilities: ['coordination', 'planning', 'oversight', 'consensus'],
|
|
104
|
+
description: 'Top-level swarm coordination and orchestration',
|
|
105
|
+
},
|
|
106
|
+
{
|
|
107
|
+
name: 'security',
|
|
108
|
+
agentNumbers: [2, 3, 4],
|
|
109
|
+
priority: 1,
|
|
110
|
+
capabilities: ['security-architecture', 'cve-remediation', 'security-testing', 'threat-modeling'],
|
|
111
|
+
description: 'Security architecture, CVE fixes, and security testing',
|
|
112
|
+
},
|
|
113
|
+
{
|
|
114
|
+
name: 'core',
|
|
115
|
+
agentNumbers: [5, 6, 7, 8, 9],
|
|
116
|
+
priority: 2,
|
|
117
|
+
capabilities: ['ddd-design', 'type-modernization', 'memory-unification', 'swarm-coordination', 'mcp-optimization'],
|
|
118
|
+
description: 'Core architecture, DDD, memory unification, and MCP optimization',
|
|
119
|
+
},
|
|
120
|
+
{
|
|
121
|
+
name: 'integration',
|
|
122
|
+
agentNumbers: [10, 11, 12],
|
|
123
|
+
priority: 3,
|
|
124
|
+
capabilities: ['agentic-flow-integration', 'cli-modernization', 'neural-integration', 'hooks-system'],
|
|
125
|
+
description: '@sparkleideas/agentic-flow integration, CLI modernization, and neural features',
|
|
126
|
+
},
|
|
127
|
+
{
|
|
128
|
+
name: 'support',
|
|
129
|
+
agentNumbers: [13, 14, 15],
|
|
130
|
+
priority: 4,
|
|
131
|
+
capabilities: ['tdd-testing', 'performance-benchmarking', 'deployment', 'release-management'],
|
|
132
|
+
description: 'Testing, performance optimization, and deployment',
|
|
133
|
+
},
|
|
134
|
+
];
|
|
135
|
+
|
|
136
|
+
export class UnifiedSwarmCoordinator extends EventEmitter implements IUnifiedSwarmCoordinator {
|
|
137
|
+
private config: CoordinatorConfig;
|
|
138
|
+
private state: CoordinatorState;
|
|
139
|
+
private topologyManager: TopologyManager;
|
|
140
|
+
private messageBus: MessageBus;
|
|
141
|
+
private consensusEngine: ConsensusEngine;
|
|
142
|
+
private agentPools: Map<AgentType, AgentPool> = new Map();
|
|
143
|
+
|
|
144
|
+
// Domain-based tracking for 15-agent hierarchy
|
|
145
|
+
private domainConfigs: Map<AgentDomain, DomainConfig> = new Map();
|
|
146
|
+
private domainPools: Map<AgentDomain, AgentPool> = new Map();
|
|
147
|
+
private agentDomainMap: Map<string, AgentDomain> = new Map();
|
|
148
|
+
private taskAssignments: Map<string, TaskAssignment> = new Map();
|
|
149
|
+
private domainTaskQueues: Map<AgentDomain, string[]> = new Map();
|
|
150
|
+
|
|
151
|
+
// Performance tracking
|
|
152
|
+
private startTime?: Date;
|
|
153
|
+
private taskCounter: number = 0;
|
|
154
|
+
private agentCounter: number = 0;
|
|
155
|
+
private coordinationLatencies: number[] = [];
|
|
156
|
+
private lastMetricsUpdate: Date = new Date();
|
|
157
|
+
|
|
158
|
+
// Background intervals
|
|
159
|
+
private heartbeatInterval?: NodeJS.Timeout;
|
|
160
|
+
private healthCheckInterval?: NodeJS.Timeout;
|
|
161
|
+
private metricsInterval?: NodeJS.Timeout;
|
|
162
|
+
|
|
163
|
+
constructor(config: Partial<CoordinatorConfig> = {}) {
|
|
164
|
+
super();
|
|
165
|
+
|
|
166
|
+
this.config = this.createDefaultConfig(config);
|
|
167
|
+
this.state = this.createInitialState();
|
|
168
|
+
|
|
169
|
+
// Initialize components
|
|
170
|
+
this.topologyManager = createTopologyManager(this.config.topology);
|
|
171
|
+
this.messageBus = createMessageBus(this.config.messageBus);
|
|
172
|
+
this.consensusEngine = createConsensusEngine(
|
|
173
|
+
this.state.id.id,
|
|
174
|
+
this.config.consensus.algorithm,
|
|
175
|
+
this.config.consensus
|
|
176
|
+
);
|
|
177
|
+
|
|
178
|
+
// Initialize domain configurations
|
|
179
|
+
this.initializeDomainConfigs();
|
|
180
|
+
|
|
181
|
+
this.setupEventForwarding();
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
// =============================================================================
|
|
185
|
+
// Domain Configuration Initialization
|
|
186
|
+
// =============================================================================
|
|
187
|
+
|
|
188
|
+
private initializeDomainConfigs(): void {
|
|
189
|
+
for (const config of DOMAIN_CONFIGS) {
|
|
190
|
+
this.domainConfigs.set(config.name, config);
|
|
191
|
+
this.domainTaskQueues.set(config.name, []);
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
async initialize(): Promise<void> {
|
|
196
|
+
if (this.state.status !== 'initializing' && this.state.status !== 'stopped') {
|
|
197
|
+
throw new Error(`Cannot initialize from status: ${this.state.status}`);
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
const startTime = performance.now();
|
|
201
|
+
|
|
202
|
+
try {
|
|
203
|
+
// Initialize all components in parallel
|
|
204
|
+
await Promise.all([
|
|
205
|
+
this.topologyManager.initialize(this.config.topology),
|
|
206
|
+
this.messageBus.initialize(this.config.messageBus),
|
|
207
|
+
this.consensusEngine.initialize(this.config.consensus),
|
|
208
|
+
]);
|
|
209
|
+
|
|
210
|
+
// Initialize default agent pools
|
|
211
|
+
await this.initializeAgentPools();
|
|
212
|
+
|
|
213
|
+
// Start background processes
|
|
214
|
+
this.startBackgroundProcesses();
|
|
215
|
+
|
|
216
|
+
this.state.status = 'running';
|
|
217
|
+
this.startTime = new Date();
|
|
218
|
+
this.state.startedAt = this.startTime;
|
|
219
|
+
|
|
220
|
+
const duration = performance.now() - startTime;
|
|
221
|
+
this.recordCoordinationLatency(duration);
|
|
222
|
+
|
|
223
|
+
this.emitEvent('swarm.initialized', {
|
|
224
|
+
swarmId: this.state.id.id,
|
|
225
|
+
initDurationMs: duration,
|
|
226
|
+
});
|
|
227
|
+
|
|
228
|
+
this.emitEvent('swarm.started', {
|
|
229
|
+
swarmId: this.state.id.id,
|
|
230
|
+
topology: this.config.topology.type,
|
|
231
|
+
consensus: this.config.consensus.algorithm,
|
|
232
|
+
});
|
|
233
|
+
} catch (error) {
|
|
234
|
+
this.state.status = 'failed';
|
|
235
|
+
throw error;
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
async shutdown(): Promise<void> {
|
|
240
|
+
if (this.state.status === 'stopped') {
|
|
241
|
+
return;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
this.state.status = 'shutting_down';
|
|
245
|
+
|
|
246
|
+
// Stop background processes
|
|
247
|
+
this.stopBackgroundProcesses();
|
|
248
|
+
|
|
249
|
+
// Shutdown all components including domain pools
|
|
250
|
+
await Promise.all([
|
|
251
|
+
this.messageBus.shutdown(),
|
|
252
|
+
this.consensusEngine.shutdown(),
|
|
253
|
+
...Array.from(this.agentPools.values()).map(pool => pool.shutdown()),
|
|
254
|
+
...Array.from(this.domainPools.values()).map(pool => pool.shutdown()),
|
|
255
|
+
]);
|
|
256
|
+
|
|
257
|
+
// Clear all tracking data
|
|
258
|
+
this.state.agents.clear();
|
|
259
|
+
this.state.tasks.clear();
|
|
260
|
+
this.agentDomainMap.clear();
|
|
261
|
+
this.taskAssignments.clear();
|
|
262
|
+
for (const queue of this.domainTaskQueues.values()) {
|
|
263
|
+
queue.length = 0;
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
this.state.status = 'stopped';
|
|
267
|
+
|
|
268
|
+
this.emitEvent('swarm.stopped', {
|
|
269
|
+
swarmId: this.state.id.id,
|
|
270
|
+
totalTasks: this.state.metrics.totalTasks,
|
|
271
|
+
completedTasks: this.state.metrics.completedTasks,
|
|
272
|
+
});
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
async pause(): Promise<void> {
|
|
276
|
+
if (this.state.status !== 'running') {
|
|
277
|
+
return;
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
this.state.status = 'paused';
|
|
281
|
+
this.stopBackgroundProcesses();
|
|
282
|
+
|
|
283
|
+
this.emitEvent('swarm.paused', { swarmId: this.state.id.id });
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
async resume(): Promise<void> {
|
|
287
|
+
if (this.state.status !== 'paused') {
|
|
288
|
+
return;
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
this.startBackgroundProcesses();
|
|
292
|
+
this.state.status = 'running';
|
|
293
|
+
|
|
294
|
+
this.emitEvent('swarm.resumed', { swarmId: this.state.id.id });
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
// ===== AGENT MANAGEMENT =====
|
|
298
|
+
|
|
299
|
+
async registerAgent(
|
|
300
|
+
agentData: Omit<AgentState, 'id'>
|
|
301
|
+
): Promise<string> {
|
|
302
|
+
const startTime = performance.now();
|
|
303
|
+
|
|
304
|
+
if (this.state.agents.size >= this.config.maxAgents) {
|
|
305
|
+
throw new Error(`Maximum agents (${this.config.maxAgents}) reached`);
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
this.agentCounter++;
|
|
309
|
+
const agentId: AgentId = {
|
|
310
|
+
id: `agent_${this.state.id.id}_${this.agentCounter}`,
|
|
311
|
+
swarmId: this.state.id.id,
|
|
312
|
+
type: agentData.type,
|
|
313
|
+
instance: this.agentCounter,
|
|
314
|
+
};
|
|
315
|
+
|
|
316
|
+
const agent: AgentState = {
|
|
317
|
+
...agentData,
|
|
318
|
+
id: agentId,
|
|
319
|
+
lastHeartbeat: new Date(),
|
|
320
|
+
connections: [],
|
|
321
|
+
};
|
|
322
|
+
|
|
323
|
+
// Add to state
|
|
324
|
+
this.state.agents.set(agentId.id, agent);
|
|
325
|
+
|
|
326
|
+
// Add to topology
|
|
327
|
+
const role = this.determineTopologyRole(agent.type);
|
|
328
|
+
await this.topologyManager.addNode(agentId.id, role);
|
|
329
|
+
|
|
330
|
+
// Subscribe to message bus
|
|
331
|
+
this.messageBus.subscribe(agentId.id, (message) => {
|
|
332
|
+
this.handleAgentMessage(agentId.id, message);
|
|
333
|
+
});
|
|
334
|
+
|
|
335
|
+
// Add to consensus engine
|
|
336
|
+
this.consensusEngine.addNode(agentId.id);
|
|
337
|
+
|
|
338
|
+
const duration = performance.now() - startTime;
|
|
339
|
+
this.recordCoordinationLatency(duration);
|
|
340
|
+
|
|
341
|
+
this.emitEvent('agent.joined', {
|
|
342
|
+
agentId: agentId.id,
|
|
343
|
+
type: agent.type,
|
|
344
|
+
registrationDurationMs: duration,
|
|
345
|
+
});
|
|
346
|
+
|
|
347
|
+
return agentId.id;
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
async unregisterAgent(agentId: string): Promise<void> {
|
|
351
|
+
const agent = this.state.agents.get(agentId);
|
|
352
|
+
if (!agent) {
|
|
353
|
+
return;
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
// Cancel any assigned tasks
|
|
357
|
+
if (agent.currentTask) {
|
|
358
|
+
await this.cancelTask(agent.currentTask.id);
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
// Remove from components
|
|
362
|
+
await this.topologyManager.removeNode(agentId);
|
|
363
|
+
this.messageBus.unsubscribe(agentId);
|
|
364
|
+
this.consensusEngine.removeNode(agentId);
|
|
365
|
+
|
|
366
|
+
// Remove from state
|
|
367
|
+
this.state.agents.delete(agentId);
|
|
368
|
+
|
|
369
|
+
this.emitEvent('agent.left', { agentId });
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
getAgent(agentId: string): AgentState | undefined {
|
|
373
|
+
return this.state.agents.get(agentId);
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
getAllAgents(): AgentState[] {
|
|
377
|
+
return Array.from(this.state.agents.values());
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
getAgentsByType(type: AgentType): AgentState[] {
|
|
381
|
+
return this.getAllAgents().filter(a => a.type === type);
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
getAvailableAgents(): AgentState[] {
|
|
385
|
+
return this.getAllAgents().filter(a => a.status === 'idle');
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
// ===== TASK MANAGEMENT =====
|
|
389
|
+
|
|
390
|
+
async submitTask(
|
|
391
|
+
taskData: Omit<TaskDefinition, 'id' | 'status' | 'createdAt'>
|
|
392
|
+
): Promise<string> {
|
|
393
|
+
const startTime = performance.now();
|
|
394
|
+
|
|
395
|
+
if (this.state.tasks.size >= this.config.maxTasks) {
|
|
396
|
+
throw new Error(`Maximum tasks (${this.config.maxTasks}) reached`);
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
this.taskCounter++;
|
|
400
|
+
const taskId: TaskId = {
|
|
401
|
+
id: `task_${this.state.id.id}_${this.taskCounter}`,
|
|
402
|
+
swarmId: this.state.id.id,
|
|
403
|
+
sequence: this.taskCounter,
|
|
404
|
+
priority: taskData.priority,
|
|
405
|
+
};
|
|
406
|
+
|
|
407
|
+
const task: TaskDefinition = {
|
|
408
|
+
...taskData,
|
|
409
|
+
id: taskId,
|
|
410
|
+
status: 'created',
|
|
411
|
+
createdAt: new Date(),
|
|
412
|
+
};
|
|
413
|
+
|
|
414
|
+
this.state.tasks.set(taskId.id, task);
|
|
415
|
+
this.state.metrics.totalTasks++;
|
|
416
|
+
|
|
417
|
+
// Assign to available agent
|
|
418
|
+
const assignedAgent = await this.assignTask(task);
|
|
419
|
+
|
|
420
|
+
const duration = performance.now() - startTime;
|
|
421
|
+
this.recordCoordinationLatency(duration);
|
|
422
|
+
|
|
423
|
+
this.emitEvent('task.created', {
|
|
424
|
+
taskId: taskId.id,
|
|
425
|
+
type: task.type,
|
|
426
|
+
priority: task.priority,
|
|
427
|
+
assignedTo: assignedAgent?.id.id,
|
|
428
|
+
assignmentDurationMs: duration,
|
|
429
|
+
});
|
|
430
|
+
|
|
431
|
+
return taskId.id;
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
async cancelTask(taskId: string): Promise<void> {
|
|
435
|
+
const task = this.state.tasks.get(taskId);
|
|
436
|
+
if (!task) {
|
|
437
|
+
return;
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
// Notify assigned agent
|
|
441
|
+
if (task.assignedTo) {
|
|
442
|
+
await this.messageBus.send({
|
|
443
|
+
type: 'task_fail',
|
|
444
|
+
from: this.state.id.id,
|
|
445
|
+
to: task.assignedTo.id,
|
|
446
|
+
payload: { taskId, reason: 'cancelled' },
|
|
447
|
+
priority: 'high',
|
|
448
|
+
requiresAck: true,
|
|
449
|
+
ttlMs: SWARM_CONSTANTS.DEFAULT_MESSAGE_TTL_MS,
|
|
450
|
+
});
|
|
451
|
+
|
|
452
|
+
// Release agent
|
|
453
|
+
const agent = this.state.agents.get(task.assignedTo.id);
|
|
454
|
+
if (agent) {
|
|
455
|
+
agent.status = 'idle';
|
|
456
|
+
agent.currentTask = undefined;
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
task.status = 'cancelled';
|
|
461
|
+
|
|
462
|
+
this.emitEvent('task.failed', {
|
|
463
|
+
taskId,
|
|
464
|
+
reason: 'cancelled',
|
|
465
|
+
});
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
getTask(taskId: string): TaskDefinition | undefined {
|
|
469
|
+
return this.state.tasks.get(taskId);
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
getAllTasks(): TaskDefinition[] {
|
|
473
|
+
return Array.from(this.state.tasks.values());
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
getTasksByStatus(status: TaskStatus): TaskDefinition[] {
|
|
477
|
+
return this.getAllTasks().filter(t => t.status === status);
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
// ===== COORDINATION =====
|
|
481
|
+
|
|
482
|
+
async proposeConsensus(value: unknown): Promise<ConsensusResult> {
|
|
483
|
+
const startTime = performance.now();
|
|
484
|
+
|
|
485
|
+
const proposal = await this.consensusEngine.propose(value, this.state.id.id);
|
|
486
|
+
const result = await this.consensusEngine.awaitConsensus(proposal.id);
|
|
487
|
+
|
|
488
|
+
const duration = performance.now() - startTime;
|
|
489
|
+
this.recordCoordinationLatency(duration);
|
|
490
|
+
|
|
491
|
+
if (result.approved) {
|
|
492
|
+
this.emitEvent('consensus.achieved', {
|
|
493
|
+
proposalId: proposal.id,
|
|
494
|
+
approvalRate: result.approvalRate,
|
|
495
|
+
durationMs: duration,
|
|
496
|
+
});
|
|
497
|
+
} else {
|
|
498
|
+
this.emitEvent('consensus.failed', {
|
|
499
|
+
proposalId: proposal.id,
|
|
500
|
+
approvalRate: result.approvalRate,
|
|
501
|
+
reason: 'threshold_not_met',
|
|
502
|
+
});
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
return result;
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
async broadcastMessage(
|
|
509
|
+
payload: unknown,
|
|
510
|
+
priority: Message['priority'] = 'normal'
|
|
511
|
+
): Promise<void> {
|
|
512
|
+
await this.messageBus.broadcast({
|
|
513
|
+
type: 'broadcast',
|
|
514
|
+
from: this.state.id.id,
|
|
515
|
+
payload,
|
|
516
|
+
priority,
|
|
517
|
+
requiresAck: false,
|
|
518
|
+
ttlMs: SWARM_CONSTANTS.DEFAULT_MESSAGE_TTL_MS,
|
|
519
|
+
});
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
// ===== MONITORING =====
|
|
523
|
+
|
|
524
|
+
getState(): CoordinatorState {
|
|
525
|
+
return {
|
|
526
|
+
...this.state,
|
|
527
|
+
agents: new Map(this.state.agents),
|
|
528
|
+
tasks: new Map(this.state.tasks),
|
|
529
|
+
topology: this.topologyManager.getState(),
|
|
530
|
+
};
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
getMetrics(): CoordinatorMetrics {
|
|
534
|
+
return { ...this.state.metrics };
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
getPerformanceReport(): PerformanceReport {
|
|
538
|
+
const recentLatencies = this.coordinationLatencies.slice(-100);
|
|
539
|
+
const sortedLatencies = [...recentLatencies].sort((a, b) => a - b);
|
|
540
|
+
|
|
541
|
+
return {
|
|
542
|
+
timestamp: new Date(),
|
|
543
|
+
window: 60000, // 1 minute
|
|
544
|
+
coordinationLatencyP50: sortedLatencies[Math.floor(sortedLatencies.length * 0.5)] || 0,
|
|
545
|
+
coordinationLatencyP99: sortedLatencies[Math.floor(sortedLatencies.length * 0.99)] || 0,
|
|
546
|
+
messagesPerSecond: this.messageBus.getStats().messagesPerSecond,
|
|
547
|
+
taskThroughput: this.calculateTaskThroughput(),
|
|
548
|
+
agentUtilization: this.calculateAgentUtilization(),
|
|
549
|
+
consensusSuccessRate: this.state.metrics.consensusSuccessRate,
|
|
550
|
+
};
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
// ===== PRIVATE METHODS =====
|
|
554
|
+
|
|
555
|
+
private createDefaultConfig(config: Partial<CoordinatorConfig>): CoordinatorConfig {
|
|
556
|
+
return {
|
|
557
|
+
topology: {
|
|
558
|
+
type: config.topology?.type ?? 'mesh',
|
|
559
|
+
maxAgents: config.topology?.maxAgents ?? SWARM_CONSTANTS.DEFAULT_MAX_AGENTS,
|
|
560
|
+
replicationFactor: config.topology?.replicationFactor ?? 2,
|
|
561
|
+
partitionStrategy: config.topology?.partitionStrategy ?? 'hash',
|
|
562
|
+
failoverEnabled: config.topology?.failoverEnabled ?? true,
|
|
563
|
+
autoRebalance: config.topology?.autoRebalance ?? true,
|
|
564
|
+
},
|
|
565
|
+
consensus: {
|
|
566
|
+
algorithm: config.consensus?.algorithm ?? 'raft',
|
|
567
|
+
threshold: config.consensus?.threshold ?? SWARM_CONSTANTS.DEFAULT_CONSENSUS_THRESHOLD,
|
|
568
|
+
timeoutMs: config.consensus?.timeoutMs ?? SWARM_CONSTANTS.DEFAULT_CONSENSUS_TIMEOUT_MS,
|
|
569
|
+
maxRounds: config.consensus?.maxRounds ?? 10,
|
|
570
|
+
requireQuorum: config.consensus?.requireQuorum ?? true,
|
|
571
|
+
},
|
|
572
|
+
messageBus: {
|
|
573
|
+
maxQueueSize: config.messageBus?.maxQueueSize ?? SWARM_CONSTANTS.MAX_QUEUE_SIZE,
|
|
574
|
+
processingIntervalMs: config.messageBus?.processingIntervalMs ?? 10,
|
|
575
|
+
ackTimeoutMs: config.messageBus?.ackTimeoutMs ?? 5000,
|
|
576
|
+
retryAttempts: config.messageBus?.retryAttempts ?? SWARM_CONSTANTS.MAX_RETRIES,
|
|
577
|
+
enablePersistence: config.messageBus?.enablePersistence ?? false,
|
|
578
|
+
compressionEnabled: config.messageBus?.compressionEnabled ?? false,
|
|
579
|
+
},
|
|
580
|
+
maxAgents: config.maxAgents ?? SWARM_CONSTANTS.DEFAULT_MAX_AGENTS,
|
|
581
|
+
maxTasks: config.maxTasks ?? SWARM_CONSTANTS.DEFAULT_MAX_TASKS,
|
|
582
|
+
heartbeatIntervalMs: config.heartbeatIntervalMs ?? SWARM_CONSTANTS.DEFAULT_HEARTBEAT_INTERVAL_MS,
|
|
583
|
+
healthCheckIntervalMs: config.healthCheckIntervalMs ?? SWARM_CONSTANTS.DEFAULT_HEALTH_CHECK_INTERVAL_MS,
|
|
584
|
+
taskTimeoutMs: config.taskTimeoutMs ?? SWARM_CONSTANTS.DEFAULT_TASK_TIMEOUT_MS,
|
|
585
|
+
autoScaling: config.autoScaling ?? true,
|
|
586
|
+
autoRecovery: config.autoRecovery ?? true,
|
|
587
|
+
};
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
private createInitialState(): CoordinatorState {
|
|
591
|
+
const swarmId: SwarmId = {
|
|
592
|
+
id: `swarm_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`,
|
|
593
|
+
namespace: 'default',
|
|
594
|
+
version: '3.0.0',
|
|
595
|
+
createdAt: new Date(),
|
|
596
|
+
};
|
|
597
|
+
|
|
598
|
+
return {
|
|
599
|
+
id: swarmId,
|
|
600
|
+
status: 'initializing',
|
|
601
|
+
topology: {
|
|
602
|
+
type: 'mesh',
|
|
603
|
+
nodes: [],
|
|
604
|
+
edges: [],
|
|
605
|
+
partitions: [],
|
|
606
|
+
},
|
|
607
|
+
agents: new Map(),
|
|
608
|
+
tasks: new Map(),
|
|
609
|
+
metrics: {
|
|
610
|
+
uptime: 0,
|
|
611
|
+
activeAgents: 0,
|
|
612
|
+
totalTasks: 0,
|
|
613
|
+
completedTasks: 0,
|
|
614
|
+
failedTasks: 0,
|
|
615
|
+
avgTaskDurationMs: 0,
|
|
616
|
+
messagesPerSecond: 0,
|
|
617
|
+
consensusSuccessRate: 1.0,
|
|
618
|
+
coordinationLatencyMs: 0,
|
|
619
|
+
memoryUsageBytes: 0,
|
|
620
|
+
},
|
|
621
|
+
};
|
|
622
|
+
}
|
|
623
|
+
|
|
624
|
+
private async initializeAgentPools(): Promise<void> {
|
|
625
|
+
// Initialize type-based pools (legacy support)
|
|
626
|
+
const defaultPoolTypes: AgentType[] = ['worker', 'coordinator', 'researcher', 'coder'];
|
|
627
|
+
|
|
628
|
+
for (const type of defaultPoolTypes) {
|
|
629
|
+
const pool = createAgentPool({
|
|
630
|
+
name: `${type}-pool`,
|
|
631
|
+
type,
|
|
632
|
+
minSize: 0,
|
|
633
|
+
maxSize: Math.floor(this.config.maxAgents / defaultPoolTypes.length),
|
|
634
|
+
scaleUpThreshold: 0.8,
|
|
635
|
+
scaleDownThreshold: 0.2,
|
|
636
|
+
cooldownMs: 30000,
|
|
637
|
+
healthCheckIntervalMs: this.config.healthCheckIntervalMs,
|
|
638
|
+
});
|
|
639
|
+
|
|
640
|
+
await pool.initialize();
|
|
641
|
+
this.agentPools.set(type, pool);
|
|
642
|
+
}
|
|
643
|
+
|
|
644
|
+
// Initialize domain-based pools for 15-agent hierarchy
|
|
645
|
+
await this.initializeDomainPools();
|
|
646
|
+
}
|
|
647
|
+
|
|
648
|
+
private async initializeDomainPools(): Promise<void> {
|
|
649
|
+
for (const [domain, config] of this.domainConfigs) {
|
|
650
|
+
const agentType = this.domainToAgentType(domain);
|
|
651
|
+
const pool = createAgentPool({
|
|
652
|
+
name: `${domain}-domain-pool`,
|
|
653
|
+
type: agentType,
|
|
654
|
+
minSize: 0,
|
|
655
|
+
maxSize: config.agentNumbers.length,
|
|
656
|
+
scaleUpThreshold: 0.8,
|
|
657
|
+
scaleDownThreshold: 0.2,
|
|
658
|
+
cooldownMs: 30000,
|
|
659
|
+
healthCheckIntervalMs: this.config.healthCheckIntervalMs,
|
|
660
|
+
});
|
|
661
|
+
|
|
662
|
+
await pool.initialize();
|
|
663
|
+
this.domainPools.set(domain, pool);
|
|
664
|
+
}
|
|
665
|
+
}
|
|
666
|
+
|
|
667
|
+
private domainToAgentType(domain: AgentDomain): AgentType {
|
|
668
|
+
switch (domain) {
|
|
669
|
+
case 'queen':
|
|
670
|
+
return 'queen';
|
|
671
|
+
case 'security':
|
|
672
|
+
return 'specialist';
|
|
673
|
+
case 'core':
|
|
674
|
+
return 'architect';
|
|
675
|
+
case 'integration':
|
|
676
|
+
return 'coder';
|
|
677
|
+
case 'support':
|
|
678
|
+
return 'tester';
|
|
679
|
+
default:
|
|
680
|
+
return 'worker';
|
|
681
|
+
}
|
|
682
|
+
}
|
|
683
|
+
|
|
684
|
+
private setupEventForwarding(): void {
|
|
685
|
+
// Forward topology events
|
|
686
|
+
this.topologyManager.on('node.added', (data) => {
|
|
687
|
+
this.emitEvent('topology.updated', { action: 'node_added', ...data });
|
|
688
|
+
});
|
|
689
|
+
|
|
690
|
+
this.topologyManager.on('node.removed', (data) => {
|
|
691
|
+
this.emitEvent('topology.updated', { action: 'node_removed', ...data });
|
|
692
|
+
});
|
|
693
|
+
|
|
694
|
+
this.topologyManager.on('topology.rebalanced', (data) => {
|
|
695
|
+
this.emitEvent('topology.rebalanced', data);
|
|
696
|
+
});
|
|
697
|
+
|
|
698
|
+
// Forward consensus events
|
|
699
|
+
this.consensusEngine.on('consensus.achieved', (data) => {
|
|
700
|
+
this.state.metrics.consensusSuccessRate =
|
|
701
|
+
(this.state.metrics.consensusSuccessRate * 0.9) + (data.approved ? 0.1 : 0);
|
|
702
|
+
});
|
|
703
|
+
|
|
704
|
+
// Forward message bus events
|
|
705
|
+
this.messageBus.on('message.delivered', (data) => {
|
|
706
|
+
this.emitEvent('message.received', data);
|
|
707
|
+
});
|
|
708
|
+
}
|
|
709
|
+
|
|
710
|
+
private startBackgroundProcesses(): void {
|
|
711
|
+
// Heartbeat monitoring
|
|
712
|
+
this.heartbeatInterval = setInterval(() => {
|
|
713
|
+
this.checkHeartbeats();
|
|
714
|
+
}, this.config.heartbeatIntervalMs);
|
|
715
|
+
|
|
716
|
+
// Health checks
|
|
717
|
+
this.healthCheckInterval = setInterval(() => {
|
|
718
|
+
this.performHealthChecks();
|
|
719
|
+
}, this.config.healthCheckIntervalMs);
|
|
720
|
+
|
|
721
|
+
// Metrics collection
|
|
722
|
+
this.metricsInterval = setInterval(() => {
|
|
723
|
+
this.updateMetrics();
|
|
724
|
+
}, 1000);
|
|
725
|
+
}
|
|
726
|
+
|
|
727
|
+
private stopBackgroundProcesses(): void {
|
|
728
|
+
if (this.heartbeatInterval) {
|
|
729
|
+
clearInterval(this.heartbeatInterval);
|
|
730
|
+
this.heartbeatInterval = undefined;
|
|
731
|
+
}
|
|
732
|
+
if (this.healthCheckInterval) {
|
|
733
|
+
clearInterval(this.healthCheckInterval);
|
|
734
|
+
this.healthCheckInterval = undefined;
|
|
735
|
+
}
|
|
736
|
+
if (this.metricsInterval) {
|
|
737
|
+
clearInterval(this.metricsInterval);
|
|
738
|
+
this.metricsInterval = undefined;
|
|
739
|
+
}
|
|
740
|
+
}
|
|
741
|
+
|
|
742
|
+
private async assignTask(task: TaskDefinition): Promise<AgentState | undefined> {
|
|
743
|
+
// Find best available agent
|
|
744
|
+
const availableAgents = this.getAvailableAgents();
|
|
745
|
+
if (availableAgents.length === 0) {
|
|
746
|
+
task.status = 'queued';
|
|
747
|
+
return undefined;
|
|
748
|
+
}
|
|
749
|
+
|
|
750
|
+
// Score agents based on capabilities and workload
|
|
751
|
+
const scoredAgents = availableAgents.map(agent => ({
|
|
752
|
+
agent,
|
|
753
|
+
score: this.scoreAgentForTask(agent, task),
|
|
754
|
+
})).sort((a, b) => b.score - a.score);
|
|
755
|
+
|
|
756
|
+
const bestAgent = scoredAgents[0]?.agent;
|
|
757
|
+
if (!bestAgent) {
|
|
758
|
+
task.status = 'queued';
|
|
759
|
+
return undefined;
|
|
760
|
+
}
|
|
761
|
+
|
|
762
|
+
// Assign task
|
|
763
|
+
task.assignedTo = bestAgent.id;
|
|
764
|
+
task.status = 'assigned';
|
|
765
|
+
bestAgent.status = 'busy';
|
|
766
|
+
bestAgent.currentTask = task.id;
|
|
767
|
+
|
|
768
|
+
// Notify agent via message bus
|
|
769
|
+
await this.messageBus.send({
|
|
770
|
+
type: 'task_assign',
|
|
771
|
+
from: this.state.id.id,
|
|
772
|
+
to: bestAgent.id.id,
|
|
773
|
+
payload: { task },
|
|
774
|
+
priority: this.mapTaskPriorityToMessagePriority(task.priority),
|
|
775
|
+
requiresAck: true,
|
|
776
|
+
ttlMs: this.config.taskTimeoutMs,
|
|
777
|
+
});
|
|
778
|
+
|
|
779
|
+
this.emitEvent('task.assigned', {
|
|
780
|
+
taskId: task.id.id,
|
|
781
|
+
agentId: bestAgent.id.id,
|
|
782
|
+
});
|
|
783
|
+
|
|
784
|
+
return bestAgent;
|
|
785
|
+
}
|
|
786
|
+
|
|
787
|
+
private scoreAgentForTask(agent: AgentState, task: TaskDefinition): number {
|
|
788
|
+
let score = 100;
|
|
789
|
+
|
|
790
|
+
// Type matching
|
|
791
|
+
const typeScores: Record<TaskType, AgentType[]> = {
|
|
792
|
+
research: ['researcher'],
|
|
793
|
+
analysis: ['analyst', 'researcher'],
|
|
794
|
+
coding: ['coder'],
|
|
795
|
+
testing: ['tester'],
|
|
796
|
+
review: ['reviewer'],
|
|
797
|
+
documentation: ['documenter'],
|
|
798
|
+
coordination: ['coordinator', 'queen'],
|
|
799
|
+
consensus: ['coordinator', 'queen'],
|
|
800
|
+
custom: ['worker'],
|
|
801
|
+
};
|
|
802
|
+
|
|
803
|
+
const preferredTypes = typeScores[task.type] || ['worker'];
|
|
804
|
+
if (preferredTypes.includes(agent.type)) {
|
|
805
|
+
score += 50;
|
|
806
|
+
}
|
|
807
|
+
|
|
808
|
+
// Workload adjustment
|
|
809
|
+
score -= agent.workload * 20;
|
|
810
|
+
|
|
811
|
+
// Health adjustment
|
|
812
|
+
score *= agent.health;
|
|
813
|
+
|
|
814
|
+
// Metrics-based adjustment
|
|
815
|
+
score += agent.metrics.successRate * 10;
|
|
816
|
+
score -= (agent.metrics.averageExecutionTime / 60000) * 5;
|
|
817
|
+
|
|
818
|
+
return score;
|
|
819
|
+
}
|
|
820
|
+
|
|
821
|
+
private mapTaskPriorityToMessagePriority(
|
|
822
|
+
priority: TaskPriority
|
|
823
|
+
): Message['priority'] {
|
|
824
|
+
const mapping: Record<TaskPriority, Message['priority']> = {
|
|
825
|
+
critical: 'urgent',
|
|
826
|
+
high: 'high',
|
|
827
|
+
normal: 'normal',
|
|
828
|
+
low: 'low',
|
|
829
|
+
background: 'low',
|
|
830
|
+
};
|
|
831
|
+
return mapping[priority];
|
|
832
|
+
}
|
|
833
|
+
|
|
834
|
+
private determineTopologyRole(
|
|
835
|
+
agentType: AgentType
|
|
836
|
+
): 'queen' | 'worker' | 'coordinator' | 'peer' {
|
|
837
|
+
switch (agentType) {
|
|
838
|
+
case 'queen':
|
|
839
|
+
return 'queen';
|
|
840
|
+
case 'coordinator':
|
|
841
|
+
return 'coordinator';
|
|
842
|
+
default:
|
|
843
|
+
return this.config.topology.type === 'mesh' ? 'peer' : 'worker';
|
|
844
|
+
}
|
|
845
|
+
}
|
|
846
|
+
|
|
847
|
+
private handleAgentMessage(agentId: string, message: Message): void {
|
|
848
|
+
const agent = this.state.agents.get(agentId);
|
|
849
|
+
if (!agent) return;
|
|
850
|
+
|
|
851
|
+
// Update heartbeat
|
|
852
|
+
agent.lastHeartbeat = new Date();
|
|
853
|
+
agent.metrics.messagesProcessed++;
|
|
854
|
+
|
|
855
|
+
switch (message.type) {
|
|
856
|
+
case 'task_complete':
|
|
857
|
+
this.handleTaskComplete(agentId, message.payload as { taskId: string; result: unknown });
|
|
858
|
+
break;
|
|
859
|
+
case 'task_fail':
|
|
860
|
+
this.handleTaskFail(agentId, message.payload as { taskId: string; error: string });
|
|
861
|
+
break;
|
|
862
|
+
case 'heartbeat':
|
|
863
|
+
this.handleHeartbeat(agentId, message.payload as Record<string, unknown>);
|
|
864
|
+
break;
|
|
865
|
+
case 'status_update':
|
|
866
|
+
this.handleStatusUpdate(agentId, message.payload as Partial<AgentState>);
|
|
867
|
+
break;
|
|
868
|
+
}
|
|
869
|
+
}
|
|
870
|
+
|
|
871
|
+
private handleTaskComplete(agentId: string, data: { taskId: string; result: unknown }): void {
|
|
872
|
+
const task = this.state.tasks.get(data.taskId);
|
|
873
|
+
const agent = this.state.agents.get(agentId);
|
|
874
|
+
|
|
875
|
+
if (task && agent) {
|
|
876
|
+
task.status = 'completed';
|
|
877
|
+
task.output = data.result;
|
|
878
|
+
task.completedAt = new Date();
|
|
879
|
+
|
|
880
|
+
agent.status = 'idle';
|
|
881
|
+
agent.currentTask = undefined;
|
|
882
|
+
agent.metrics.tasksCompleted++;
|
|
883
|
+
|
|
884
|
+
this.state.metrics.completedTasks++;
|
|
885
|
+
|
|
886
|
+
// Update average task duration
|
|
887
|
+
if (task.startedAt) {
|
|
888
|
+
const duration = task.completedAt.getTime() - task.startedAt.getTime();
|
|
889
|
+
this.state.metrics.avgTaskDurationMs =
|
|
890
|
+
(this.state.metrics.avgTaskDurationMs * 0.9) + (duration * 0.1);
|
|
891
|
+
}
|
|
892
|
+
|
|
893
|
+
this.emitEvent('task.completed', {
|
|
894
|
+
taskId: data.taskId,
|
|
895
|
+
agentId,
|
|
896
|
+
result: data.result,
|
|
897
|
+
});
|
|
898
|
+
}
|
|
899
|
+
}
|
|
900
|
+
|
|
901
|
+
private handleTaskFail(agentId: string, data: { taskId: string; error: string }): void {
|
|
902
|
+
const task = this.state.tasks.get(data.taskId);
|
|
903
|
+
const agent = this.state.agents.get(agentId);
|
|
904
|
+
|
|
905
|
+
if (task && agent) {
|
|
906
|
+
// Check retry
|
|
907
|
+
if (task.retries < task.maxRetries) {
|
|
908
|
+
task.retries++;
|
|
909
|
+
task.status = 'queued';
|
|
910
|
+
task.assignedTo = undefined;
|
|
911
|
+
agent.currentTask = undefined;
|
|
912
|
+
agent.status = 'idle';
|
|
913
|
+
|
|
914
|
+
// Re-assign
|
|
915
|
+
this.assignTask(task);
|
|
916
|
+
} else {
|
|
917
|
+
task.status = 'failed';
|
|
918
|
+
agent.status = 'idle';
|
|
919
|
+
agent.currentTask = undefined;
|
|
920
|
+
agent.metrics.tasksFailed++;
|
|
921
|
+
|
|
922
|
+
this.state.metrics.failedTasks++;
|
|
923
|
+
|
|
924
|
+
this.emitEvent('task.failed', {
|
|
925
|
+
taskId: data.taskId,
|
|
926
|
+
agentId,
|
|
927
|
+
error: data.error,
|
|
928
|
+
});
|
|
929
|
+
}
|
|
930
|
+
}
|
|
931
|
+
}
|
|
932
|
+
|
|
933
|
+
private handleHeartbeat(agentId: string, data: Record<string, unknown>): void {
|
|
934
|
+
const agent = this.state.agents.get(agentId);
|
|
935
|
+
if (agent) {
|
|
936
|
+
agent.lastHeartbeat = new Date();
|
|
937
|
+
if (data.metrics) {
|
|
938
|
+
agent.metrics = { ...agent.metrics, ...(data.metrics as Partial<typeof agent.metrics>) };
|
|
939
|
+
}
|
|
940
|
+
|
|
941
|
+
this.emitEvent('agent.heartbeat', { agentId });
|
|
942
|
+
}
|
|
943
|
+
}
|
|
944
|
+
|
|
945
|
+
private handleStatusUpdate(agentId: string, data: Partial<AgentState>): void {
|
|
946
|
+
const agent = this.state.agents.get(agentId);
|
|
947
|
+
if (agent) {
|
|
948
|
+
if (data.status) agent.status = data.status;
|
|
949
|
+
if (data.health !== undefined) agent.health = data.health;
|
|
950
|
+
if (data.workload !== undefined) agent.workload = data.workload;
|
|
951
|
+
|
|
952
|
+
this.emitEvent('agent.status_changed', { agentId, status: agent.status });
|
|
953
|
+
}
|
|
954
|
+
}
|
|
955
|
+
|
|
956
|
+
private checkHeartbeats(): void {
|
|
957
|
+
const now = Date.now();
|
|
958
|
+
const timeout = this.config.heartbeatIntervalMs * 3;
|
|
959
|
+
|
|
960
|
+
for (const [agentId, agent] of this.state.agents) {
|
|
961
|
+
const timeSinceHeartbeat = now - agent.lastHeartbeat.getTime();
|
|
962
|
+
|
|
963
|
+
if (timeSinceHeartbeat > timeout && agent.status !== 'terminated') {
|
|
964
|
+
agent.status = 'error';
|
|
965
|
+
agent.health = Math.max(0, agent.health - 0.2);
|
|
966
|
+
|
|
967
|
+
// Auto-recovery
|
|
968
|
+
if (this.config.autoRecovery && agent.health <= 0.2) {
|
|
969
|
+
this.recoverAgent(agentId);
|
|
970
|
+
}
|
|
971
|
+
}
|
|
972
|
+
}
|
|
973
|
+
}
|
|
974
|
+
|
|
975
|
+
private async recoverAgent(agentId: string): Promise<void> {
|
|
976
|
+
const agent = this.state.agents.get(agentId);
|
|
977
|
+
if (!agent) return;
|
|
978
|
+
|
|
979
|
+
// Reassign any tasks
|
|
980
|
+
if (agent.currentTask) {
|
|
981
|
+
const task = this.state.tasks.get(agent.currentTask.id);
|
|
982
|
+
if (task) {
|
|
983
|
+
task.status = 'queued';
|
|
984
|
+
task.assignedTo = undefined;
|
|
985
|
+
await this.assignTask(task);
|
|
986
|
+
}
|
|
987
|
+
}
|
|
988
|
+
|
|
989
|
+
// Reset agent
|
|
990
|
+
agent.status = 'idle';
|
|
991
|
+
agent.currentTask = undefined;
|
|
992
|
+
agent.health = 1.0;
|
|
993
|
+
agent.lastHeartbeat = new Date();
|
|
994
|
+
}
|
|
995
|
+
|
|
996
|
+
private performHealthChecks(): void {
|
|
997
|
+
const activeAgents = this.getAllAgents().filter(
|
|
998
|
+
a => a.status === 'idle' || a.status === 'busy'
|
|
999
|
+
);
|
|
1000
|
+
|
|
1001
|
+
this.state.metrics.activeAgents = activeAgents.length;
|
|
1002
|
+
|
|
1003
|
+
// Update topology state
|
|
1004
|
+
this.state.topology = this.topologyManager.getState();
|
|
1005
|
+
}
|
|
1006
|
+
|
|
1007
|
+
private updateMetrics(): void {
|
|
1008
|
+
const now = new Date();
|
|
1009
|
+
const uptime = this.startTime
|
|
1010
|
+
? (now.getTime() - this.startTime.getTime()) / 1000
|
|
1011
|
+
: 0;
|
|
1012
|
+
|
|
1013
|
+
this.state.metrics.uptime = uptime;
|
|
1014
|
+
this.state.metrics.messagesPerSecond = this.messageBus.getStats().messagesPerSecond;
|
|
1015
|
+
|
|
1016
|
+
// Calculate coordination latency
|
|
1017
|
+
if (this.coordinationLatencies.length > 0) {
|
|
1018
|
+
const recent = this.coordinationLatencies.slice(-50);
|
|
1019
|
+
this.state.metrics.coordinationLatencyMs =
|
|
1020
|
+
recent.reduce((a, b) => a + b, 0) / recent.length;
|
|
1021
|
+
}
|
|
1022
|
+
|
|
1023
|
+
// Memory usage (approximation)
|
|
1024
|
+
this.state.metrics.memoryUsageBytes =
|
|
1025
|
+
(this.state.agents.size * 2000) +
|
|
1026
|
+
(this.state.tasks.size * 1000) +
|
|
1027
|
+
(this.messageBus.getQueueDepth() * 500);
|
|
1028
|
+
|
|
1029
|
+
this.lastMetricsUpdate = now;
|
|
1030
|
+
}
|
|
1031
|
+
|
|
1032
|
+
private recordCoordinationLatency(latencyMs: number): void {
|
|
1033
|
+
this.coordinationLatencies.push(latencyMs);
|
|
1034
|
+
if (this.coordinationLatencies.length > 1000) {
|
|
1035
|
+
this.coordinationLatencies.shift();
|
|
1036
|
+
}
|
|
1037
|
+
}
|
|
1038
|
+
|
|
1039
|
+
private calculateTaskThroughput(): number {
|
|
1040
|
+
if (!this.startTime) return 0;
|
|
1041
|
+
const uptimeSeconds = (Date.now() - this.startTime.getTime()) / 1000;
|
|
1042
|
+
return uptimeSeconds > 0
|
|
1043
|
+
? this.state.metrics.completedTasks / uptimeSeconds
|
|
1044
|
+
: 0;
|
|
1045
|
+
}
|
|
1046
|
+
|
|
1047
|
+
private calculateAgentUtilization(): number {
|
|
1048
|
+
const agents = this.getAllAgents();
|
|
1049
|
+
if (agents.length === 0) return 0;
|
|
1050
|
+
const busyAgents = agents.filter(a => a.status === 'busy').length;
|
|
1051
|
+
return busyAgents / agents.length;
|
|
1052
|
+
}
|
|
1053
|
+
|
|
1054
|
+
private emitEvent(type: SwarmEventType, data: Record<string, unknown>): void {
|
|
1055
|
+
const event: SwarmEvent = {
|
|
1056
|
+
id: `event_${Date.now()}_${Math.random().toString(36).substr(2, 6)}`,
|
|
1057
|
+
type,
|
|
1058
|
+
source: this.state.id.id,
|
|
1059
|
+
timestamp: new Date(),
|
|
1060
|
+
data,
|
|
1061
|
+
};
|
|
1062
|
+
|
|
1063
|
+
this.emit(type, event);
|
|
1064
|
+
this.emit('event', event);
|
|
1065
|
+
}
|
|
1066
|
+
|
|
1067
|
+
// ===== UTILITY METHODS =====
|
|
1068
|
+
|
|
1069
|
+
getTopology(): TopologyType {
|
|
1070
|
+
return this.config.topology.type;
|
|
1071
|
+
}
|
|
1072
|
+
|
|
1073
|
+
setTopology(type: TopologyType): void {
|
|
1074
|
+
this.config.topology.type = type;
|
|
1075
|
+
}
|
|
1076
|
+
|
|
1077
|
+
getConsensusAlgorithm(): string {
|
|
1078
|
+
return this.config.consensus.algorithm;
|
|
1079
|
+
}
|
|
1080
|
+
|
|
1081
|
+
isHealthy(): boolean {
|
|
1082
|
+
return (
|
|
1083
|
+
this.state.status === 'running' &&
|
|
1084
|
+
this.state.metrics.activeAgents > 0 &&
|
|
1085
|
+
this.state.metrics.coordinationLatencyMs < SWARM_CONSTANTS.COORDINATION_LATENCY_TARGET_MS * 2
|
|
1086
|
+
);
|
|
1087
|
+
}
|
|
1088
|
+
|
|
1089
|
+
getAgentPool(type: AgentType): AgentPool | undefined {
|
|
1090
|
+
return this.agentPools.get(type);
|
|
1091
|
+
}
|
|
1092
|
+
|
|
1093
|
+
// =============================================================================
|
|
1094
|
+
// DOMAIN-BASED TASK ROUTING (15-Agent Hierarchy Support)
|
|
1095
|
+
// =============================================================================
|
|
1096
|
+
|
|
1097
|
+
/**
|
|
1098
|
+
* Assign a task to a specific domain
|
|
1099
|
+
* Routes the task to the most suitable agent within that domain
|
|
1100
|
+
*/
|
|
1101
|
+
async assignTaskToDomain(taskId: string, domain: AgentDomain): Promise<string | undefined> {
|
|
1102
|
+
const startTime = performance.now();
|
|
1103
|
+
const task = this.state.tasks.get(taskId);
|
|
1104
|
+
|
|
1105
|
+
if (!task) {
|
|
1106
|
+
throw new Error(`Task ${taskId} not found`);
|
|
1107
|
+
}
|
|
1108
|
+
|
|
1109
|
+
const pool = this.domainPools.get(domain);
|
|
1110
|
+
if (!pool) {
|
|
1111
|
+
throw new Error(`Domain pool ${domain} not found`);
|
|
1112
|
+
}
|
|
1113
|
+
|
|
1114
|
+
// Try to acquire an agent from the domain pool
|
|
1115
|
+
const agent = await pool.acquire();
|
|
1116
|
+
if (!agent) {
|
|
1117
|
+
// Add to domain queue if no agents available
|
|
1118
|
+
const queue = this.domainTaskQueues.get(domain) || [];
|
|
1119
|
+
queue.push(taskId);
|
|
1120
|
+
this.domainTaskQueues.set(domain, queue);
|
|
1121
|
+
task.status = 'queued';
|
|
1122
|
+
|
|
1123
|
+
this.emitEvent('task.queued', {
|
|
1124
|
+
taskId,
|
|
1125
|
+
domain,
|
|
1126
|
+
queuePosition: queue.length,
|
|
1127
|
+
reason: 'no_available_agents'
|
|
1128
|
+
});
|
|
1129
|
+
|
|
1130
|
+
return undefined;
|
|
1131
|
+
}
|
|
1132
|
+
|
|
1133
|
+
// Update task
|
|
1134
|
+
task.status = 'assigned';
|
|
1135
|
+
task.assignedTo = agent.id;
|
|
1136
|
+
task.startedAt = new Date();
|
|
1137
|
+
|
|
1138
|
+
// Track assignment
|
|
1139
|
+
const assignment: TaskAssignment = {
|
|
1140
|
+
taskId,
|
|
1141
|
+
domain,
|
|
1142
|
+
agentId: agent.id.id,
|
|
1143
|
+
priority: task.priority,
|
|
1144
|
+
assignedAt: new Date(),
|
|
1145
|
+
};
|
|
1146
|
+
this.taskAssignments.set(taskId, assignment);
|
|
1147
|
+
|
|
1148
|
+
// Notify agent via message bus
|
|
1149
|
+
await this.messageBus.send({
|
|
1150
|
+
type: 'task_assign',
|
|
1151
|
+
from: this.state.id.id,
|
|
1152
|
+
to: agent.id.id,
|
|
1153
|
+
payload: { taskId, task, domain },
|
|
1154
|
+
priority: this.mapTaskPriorityToMessagePriority(task.priority),
|
|
1155
|
+
requiresAck: true,
|
|
1156
|
+
ttlMs: this.config.taskTimeoutMs,
|
|
1157
|
+
});
|
|
1158
|
+
|
|
1159
|
+
const duration = performance.now() - startTime;
|
|
1160
|
+
this.recordCoordinationLatency(duration);
|
|
1161
|
+
|
|
1162
|
+
this.emitEvent('task.assigned', {
|
|
1163
|
+
taskId,
|
|
1164
|
+
agentId: agent.id.id,
|
|
1165
|
+
domain,
|
|
1166
|
+
assignmentDurationMs: duration,
|
|
1167
|
+
});
|
|
1168
|
+
|
|
1169
|
+
return agent.id.id;
|
|
1170
|
+
}
|
|
1171
|
+
|
|
1172
|
+
/**
|
|
1173
|
+
* Get all agents belonging to a specific domain
|
|
1174
|
+
*/
|
|
1175
|
+
getAgentsByDomain(domain: AgentDomain): AgentState[] {
|
|
1176
|
+
const agents: AgentState[] = [];
|
|
1177
|
+
for (const [agentId, agentDomain] of this.agentDomainMap) {
|
|
1178
|
+
if (agentDomain === domain) {
|
|
1179
|
+
const agent = this.state.agents.get(agentId);
|
|
1180
|
+
if (agent) {
|
|
1181
|
+
agents.push(agent);
|
|
1182
|
+
}
|
|
1183
|
+
}
|
|
1184
|
+
}
|
|
1185
|
+
return agents;
|
|
1186
|
+
}
|
|
1187
|
+
|
|
1188
|
+
/**
|
|
1189
|
+
* Execute multiple tasks in parallel across different domains
|
|
1190
|
+
* This is the key method for achieving >85% agent utilization
|
|
1191
|
+
*/
|
|
1192
|
+
async executeParallel(tasks: Array<{
|
|
1193
|
+
task: Omit<TaskDefinition, 'id' | 'status' | 'createdAt'>;
|
|
1194
|
+
domain: AgentDomain;
|
|
1195
|
+
}>): Promise<ParallelExecutionResult[]> {
|
|
1196
|
+
const startTime = performance.now();
|
|
1197
|
+
const executionPromises: Promise<ParallelExecutionResult>[] = [];
|
|
1198
|
+
|
|
1199
|
+
// Submit all tasks first
|
|
1200
|
+
const taskIds: Array<{ taskId: string; domain: AgentDomain }> = [];
|
|
1201
|
+
for (const { task, domain } of tasks) {
|
|
1202
|
+
const taskId = await this.submitTask(task);
|
|
1203
|
+
taskIds.push({ taskId, domain });
|
|
1204
|
+
}
|
|
1205
|
+
|
|
1206
|
+
// Execute all tasks in parallel across domains
|
|
1207
|
+
for (const { taskId, domain } of taskIds) {
|
|
1208
|
+
const promise = this.executeTaskInDomain(taskId, domain);
|
|
1209
|
+
executionPromises.push(promise);
|
|
1210
|
+
}
|
|
1211
|
+
|
|
1212
|
+
// Wait for all tasks to complete (with individual error handling)
|
|
1213
|
+
const settledResults = await Promise.allSettled(executionPromises);
|
|
1214
|
+
|
|
1215
|
+
const results: ParallelExecutionResult[] = [];
|
|
1216
|
+
for (let i = 0; i < settledResults.length; i++) {
|
|
1217
|
+
const result = settledResults[i];
|
|
1218
|
+
const { taskId, domain } = taskIds[i];
|
|
1219
|
+
|
|
1220
|
+
if (result.status === 'fulfilled') {
|
|
1221
|
+
results.push(result.value);
|
|
1222
|
+
} else {
|
|
1223
|
+
results.push({
|
|
1224
|
+
taskId,
|
|
1225
|
+
domain,
|
|
1226
|
+
success: false,
|
|
1227
|
+
error: result.reason instanceof Error ? result.reason : new Error(String(result.reason)),
|
|
1228
|
+
durationMs: performance.now() - startTime,
|
|
1229
|
+
});
|
|
1230
|
+
}
|
|
1231
|
+
}
|
|
1232
|
+
|
|
1233
|
+
this.emitEvent('parallel.execution.completed', {
|
|
1234
|
+
totalTasks: tasks.length,
|
|
1235
|
+
successful: results.filter(r => r.success).length,
|
|
1236
|
+
failed: results.filter(r => !r.success).length,
|
|
1237
|
+
totalDurationMs: performance.now() - startTime,
|
|
1238
|
+
});
|
|
1239
|
+
|
|
1240
|
+
return results;
|
|
1241
|
+
}
|
|
1242
|
+
|
|
1243
|
+
private async executeTaskInDomain(taskId: string, domain: AgentDomain): Promise<ParallelExecutionResult> {
|
|
1244
|
+
const startTime = performance.now();
|
|
1245
|
+
|
|
1246
|
+
try {
|
|
1247
|
+
// Assign to domain
|
|
1248
|
+
const agentId = await this.assignTaskToDomain(taskId, domain);
|
|
1249
|
+
if (!agentId) {
|
|
1250
|
+
// Task was queued, wait for it to be assigned and complete
|
|
1251
|
+
return await this.waitForQueuedTask(taskId, domain, startTime);
|
|
1252
|
+
}
|
|
1253
|
+
|
|
1254
|
+
// Wait for completion
|
|
1255
|
+
const result = await this.waitForTaskCompletion(taskId, this.config.taskTimeoutMs);
|
|
1256
|
+
|
|
1257
|
+
return {
|
|
1258
|
+
taskId,
|
|
1259
|
+
domain,
|
|
1260
|
+
success: result.status === 'completed',
|
|
1261
|
+
result: result.output,
|
|
1262
|
+
durationMs: performance.now() - startTime,
|
|
1263
|
+
};
|
|
1264
|
+
} catch (error) {
|
|
1265
|
+
return {
|
|
1266
|
+
taskId,
|
|
1267
|
+
domain,
|
|
1268
|
+
success: false,
|
|
1269
|
+
error: error instanceof Error ? error : new Error(String(error)),
|
|
1270
|
+
durationMs: performance.now() - startTime,
|
|
1271
|
+
};
|
|
1272
|
+
}
|
|
1273
|
+
}
|
|
1274
|
+
|
|
1275
|
+
private async waitForQueuedTask(
|
|
1276
|
+
taskId: string,
|
|
1277
|
+
domain: AgentDomain,
|
|
1278
|
+
startTime: number
|
|
1279
|
+
): Promise<ParallelExecutionResult> {
|
|
1280
|
+
return new Promise((resolve) => {
|
|
1281
|
+
const checkInterval = setInterval(() => {
|
|
1282
|
+
const task = this.state.tasks.get(taskId);
|
|
1283
|
+
if (!task) {
|
|
1284
|
+
clearInterval(checkInterval);
|
|
1285
|
+
resolve({
|
|
1286
|
+
taskId,
|
|
1287
|
+
domain,
|
|
1288
|
+
success: false,
|
|
1289
|
+
error: new Error(`Task ${taskId} not found`),
|
|
1290
|
+
durationMs: performance.now() - startTime,
|
|
1291
|
+
});
|
|
1292
|
+
return;
|
|
1293
|
+
}
|
|
1294
|
+
|
|
1295
|
+
if (task.status === 'completed') {
|
|
1296
|
+
clearInterval(checkInterval);
|
|
1297
|
+
resolve({
|
|
1298
|
+
taskId,
|
|
1299
|
+
domain,
|
|
1300
|
+
success: true,
|
|
1301
|
+
result: task.output,
|
|
1302
|
+
durationMs: performance.now() - startTime,
|
|
1303
|
+
});
|
|
1304
|
+
} else if (task.status === 'failed' || task.status === 'cancelled' || task.status === 'timeout') {
|
|
1305
|
+
clearInterval(checkInterval);
|
|
1306
|
+
resolve({
|
|
1307
|
+
taskId,
|
|
1308
|
+
domain,
|
|
1309
|
+
success: false,
|
|
1310
|
+
error: new Error(`Task ${task.status}`),
|
|
1311
|
+
durationMs: performance.now() - startTime,
|
|
1312
|
+
});
|
|
1313
|
+
}
|
|
1314
|
+
}, 100);
|
|
1315
|
+
|
|
1316
|
+
// Timeout after configured duration
|
|
1317
|
+
setTimeout(() => {
|
|
1318
|
+
clearInterval(checkInterval);
|
|
1319
|
+
const task = this.state.tasks.get(taskId);
|
|
1320
|
+
if (task && task.status !== 'completed') {
|
|
1321
|
+
task.status = 'timeout';
|
|
1322
|
+
}
|
|
1323
|
+
resolve({
|
|
1324
|
+
taskId,
|
|
1325
|
+
domain,
|
|
1326
|
+
success: false,
|
|
1327
|
+
error: new Error('Task timed out'),
|
|
1328
|
+
durationMs: performance.now() - startTime,
|
|
1329
|
+
});
|
|
1330
|
+
}, this.config.taskTimeoutMs);
|
|
1331
|
+
});
|
|
1332
|
+
}
|
|
1333
|
+
|
|
1334
|
+
private async waitForTaskCompletion(taskId: string, timeoutMs: number): Promise<TaskDefinition> {
|
|
1335
|
+
return new Promise((resolve, reject) => {
|
|
1336
|
+
const checkInterval = setInterval(() => {
|
|
1337
|
+
const task = this.state.tasks.get(taskId);
|
|
1338
|
+
if (!task) {
|
|
1339
|
+
clearInterval(checkInterval);
|
|
1340
|
+
reject(new Error(`Task ${taskId} not found`));
|
|
1341
|
+
return;
|
|
1342
|
+
}
|
|
1343
|
+
|
|
1344
|
+
if (task.status === 'completed' || task.status === 'failed' || task.status === 'cancelled') {
|
|
1345
|
+
clearInterval(checkInterval);
|
|
1346
|
+
resolve(task);
|
|
1347
|
+
}
|
|
1348
|
+
}, 100);
|
|
1349
|
+
|
|
1350
|
+
setTimeout(() => {
|
|
1351
|
+
clearInterval(checkInterval);
|
|
1352
|
+
const task = this.state.tasks.get(taskId);
|
|
1353
|
+
if (task) {
|
|
1354
|
+
task.status = 'timeout';
|
|
1355
|
+
task.completedAt = new Date();
|
|
1356
|
+
resolve(task);
|
|
1357
|
+
} else {
|
|
1358
|
+
reject(new Error(`Task ${taskId} timed out`));
|
|
1359
|
+
}
|
|
1360
|
+
}, timeoutMs);
|
|
1361
|
+
});
|
|
1362
|
+
}
|
|
1363
|
+
|
|
1364
|
+
/**
|
|
1365
|
+
* Get the current status of all domains
|
|
1366
|
+
*/
|
|
1367
|
+
getStatus(): {
|
|
1368
|
+
swarmId: SwarmId;
|
|
1369
|
+
status: SwarmStatus;
|
|
1370
|
+
topology: TopologyType;
|
|
1371
|
+
domains: DomainStatus[];
|
|
1372
|
+
metrics: CoordinatorMetrics;
|
|
1373
|
+
} {
|
|
1374
|
+
const domains: DomainStatus[] = [];
|
|
1375
|
+
|
|
1376
|
+
for (const [domain, config] of this.domainConfigs) {
|
|
1377
|
+
const pool = this.domainPools.get(domain);
|
|
1378
|
+
const stats = pool?.getPoolStats();
|
|
1379
|
+
const queue = this.domainTaskQueues.get(domain) || [];
|
|
1380
|
+
|
|
1381
|
+
const completedTasks = Array.from(this.taskAssignments.values())
|
|
1382
|
+
.filter(a => a.domain === domain)
|
|
1383
|
+
.map(a => this.state.tasks.get(a.taskId))
|
|
1384
|
+
.filter(t => t?.status === 'completed')
|
|
1385
|
+
.length;
|
|
1386
|
+
|
|
1387
|
+
domains.push({
|
|
1388
|
+
name: domain,
|
|
1389
|
+
agentCount: stats?.total ?? 0,
|
|
1390
|
+
availableAgents: stats?.available ?? 0,
|
|
1391
|
+
busyAgents: stats?.busy ?? 0,
|
|
1392
|
+
tasksQueued: queue.length,
|
|
1393
|
+
tasksCompleted: completedTasks,
|
|
1394
|
+
});
|
|
1395
|
+
}
|
|
1396
|
+
|
|
1397
|
+
return {
|
|
1398
|
+
swarmId: this.state.id,
|
|
1399
|
+
status: this.state.status,
|
|
1400
|
+
topology: this.config.topology.type,
|
|
1401
|
+
domains,
|
|
1402
|
+
metrics: this.getMetrics(),
|
|
1403
|
+
};
|
|
1404
|
+
}
|
|
1405
|
+
|
|
1406
|
+
/**
|
|
1407
|
+
* Register an agent and automatically assign it to the appropriate domain
|
|
1408
|
+
* based on its agent number (1-15)
|
|
1409
|
+
*/
|
|
1410
|
+
async registerAgentWithDomain(
|
|
1411
|
+
agentData: Omit<AgentState, 'id'>,
|
|
1412
|
+
agentNumber: number
|
|
1413
|
+
): Promise<{ agentId: string; domain: AgentDomain }> {
|
|
1414
|
+
// First register the agent normally
|
|
1415
|
+
const agentId = await this.registerAgent(agentData);
|
|
1416
|
+
|
|
1417
|
+
// Determine domain based on agent number
|
|
1418
|
+
const domain = this.getAgentDomain(agentNumber);
|
|
1419
|
+
|
|
1420
|
+
// Add to domain tracking
|
|
1421
|
+
this.agentDomainMap.set(agentId, domain);
|
|
1422
|
+
|
|
1423
|
+
// Add to domain pool
|
|
1424
|
+
const pool = this.domainPools.get(domain);
|
|
1425
|
+
const agent = this.state.agents.get(agentId);
|
|
1426
|
+
if (pool && agent) {
|
|
1427
|
+
await pool.add(agent);
|
|
1428
|
+
}
|
|
1429
|
+
|
|
1430
|
+
this.emitEvent('agent.domain_assigned', {
|
|
1431
|
+
agentId,
|
|
1432
|
+
agentNumber,
|
|
1433
|
+
domain,
|
|
1434
|
+
});
|
|
1435
|
+
|
|
1436
|
+
return { agentId, domain };
|
|
1437
|
+
}
|
|
1438
|
+
|
|
1439
|
+
/**
|
|
1440
|
+
* Get the domain for a given agent number (1-15)
|
|
1441
|
+
*/
|
|
1442
|
+
getAgentDomain(agentNumber: number): AgentDomain {
|
|
1443
|
+
for (const [domain, config] of this.domainConfigs) {
|
|
1444
|
+
if (config.agentNumbers.includes(agentNumber)) {
|
|
1445
|
+
return domain;
|
|
1446
|
+
}
|
|
1447
|
+
}
|
|
1448
|
+
return 'core'; // Default to core domain
|
|
1449
|
+
}
|
|
1450
|
+
|
|
1451
|
+
/**
|
|
1452
|
+
* Spawn the full 15-agent hierarchy
|
|
1453
|
+
* Returns a map of agent numbers to their IDs and domains
|
|
1454
|
+
*/
|
|
1455
|
+
async spawnFullHierarchy(): Promise<Map<number, { agentId: string; domain: AgentDomain }>> {
|
|
1456
|
+
const results = new Map<number, { agentId: string; domain: AgentDomain }>();
|
|
1457
|
+
|
|
1458
|
+
for (const [domain, config] of this.domainConfigs) {
|
|
1459
|
+
for (const agentNumber of config.agentNumbers) {
|
|
1460
|
+
const agentType = this.domainToAgentType(domain);
|
|
1461
|
+
|
|
1462
|
+
const agentData: Omit<AgentState, 'id'> = {
|
|
1463
|
+
name: `${domain}-agent-${agentNumber}`,
|
|
1464
|
+
type: agentType,
|
|
1465
|
+
status: 'idle',
|
|
1466
|
+
capabilities: this.createDomainCapabilities(domain),
|
|
1467
|
+
metrics: this.createDefaultAgentMetrics(),
|
|
1468
|
+
workload: 0,
|
|
1469
|
+
health: 1.0,
|
|
1470
|
+
lastHeartbeat: new Date(),
|
|
1471
|
+
topologyRole: domain === 'queen' ? 'queen' : 'worker',
|
|
1472
|
+
connections: [],
|
|
1473
|
+
};
|
|
1474
|
+
|
|
1475
|
+
const result = await this.registerAgentWithDomain(agentData, agentNumber);
|
|
1476
|
+
results.set(agentNumber, result);
|
|
1477
|
+
}
|
|
1478
|
+
}
|
|
1479
|
+
|
|
1480
|
+
this.emitEvent('hierarchy.spawned', {
|
|
1481
|
+
totalAgents: results.size,
|
|
1482
|
+
domains: Array.from(this.domainConfigs.keys()),
|
|
1483
|
+
});
|
|
1484
|
+
|
|
1485
|
+
return results;
|
|
1486
|
+
}
|
|
1487
|
+
|
|
1488
|
+
private createDomainCapabilities(domain: AgentDomain): AgentCapabilities {
|
|
1489
|
+
const domainConfig = this.domainConfigs.get(domain);
|
|
1490
|
+
const capabilities = domainConfig?.capabilities || [];
|
|
1491
|
+
|
|
1492
|
+
return {
|
|
1493
|
+
codeGeneration: domain === 'core' || domain === 'integration',
|
|
1494
|
+
codeReview: domain === 'security' || domain === 'core',
|
|
1495
|
+
testing: domain === 'support' || domain === 'security',
|
|
1496
|
+
documentation: true,
|
|
1497
|
+
research: true,
|
|
1498
|
+
analysis: true,
|
|
1499
|
+
coordination: domain === 'queen',
|
|
1500
|
+
languages: ['typescript', 'javascript', 'python'],
|
|
1501
|
+
frameworks: ['node', 'react', 'vitest'],
|
|
1502
|
+
domains: capabilities,
|
|
1503
|
+
tools: ['git', 'npm', 'editor', 'claude'],
|
|
1504
|
+
maxConcurrentTasks: domain === 'queen' ? 1 : 3,
|
|
1505
|
+
maxMemoryUsage: 512 * 1024 * 1024,
|
|
1506
|
+
maxExecutionTime: SWARM_CONSTANTS.DEFAULT_TASK_TIMEOUT_MS,
|
|
1507
|
+
reliability: 0.95,
|
|
1508
|
+
speed: 1.0,
|
|
1509
|
+
quality: 0.9,
|
|
1510
|
+
};
|
|
1511
|
+
}
|
|
1512
|
+
|
|
1513
|
+
private createDefaultAgentMetrics(): AgentMetrics {
|
|
1514
|
+
return {
|
|
1515
|
+
tasksCompleted: 0,
|
|
1516
|
+
tasksFailed: 0,
|
|
1517
|
+
averageExecutionTime: 0,
|
|
1518
|
+
successRate: 1.0,
|
|
1519
|
+
cpuUsage: 0,
|
|
1520
|
+
memoryUsage: 0,
|
|
1521
|
+
messagesProcessed: 0,
|
|
1522
|
+
lastActivity: new Date(),
|
|
1523
|
+
responseTime: 0,
|
|
1524
|
+
health: 1.0,
|
|
1525
|
+
};
|
|
1526
|
+
}
|
|
1527
|
+
|
|
1528
|
+
/**
|
|
1529
|
+
* Get the domain pool for a specific domain
|
|
1530
|
+
*/
|
|
1531
|
+
getDomainPool(domain: AgentDomain): AgentPool | undefined {
|
|
1532
|
+
return this.domainPools.get(domain);
|
|
1533
|
+
}
|
|
1534
|
+
|
|
1535
|
+
/**
|
|
1536
|
+
* Get all domain configurations
|
|
1537
|
+
*/
|
|
1538
|
+
getDomainConfigs(): Map<AgentDomain, DomainConfig> {
|
|
1539
|
+
return new Map(this.domainConfigs);
|
|
1540
|
+
}
|
|
1541
|
+
|
|
1542
|
+
/**
|
|
1543
|
+
* Release an agent back to its domain pool after task completion
|
|
1544
|
+
*/
|
|
1545
|
+
async releaseAgentToDomain(agentId: string): Promise<void> {
|
|
1546
|
+
const domain = this.agentDomainMap.get(agentId);
|
|
1547
|
+
if (!domain) return;
|
|
1548
|
+
|
|
1549
|
+
const pool = this.domainPools.get(domain);
|
|
1550
|
+
if (pool) {
|
|
1551
|
+
await pool.release(agentId);
|
|
1552
|
+
}
|
|
1553
|
+
|
|
1554
|
+
// Check if there are queued tasks for this domain
|
|
1555
|
+
const queue = this.domainTaskQueues.get(domain) || [];
|
|
1556
|
+
if (queue.length > 0) {
|
|
1557
|
+
const nextTaskId = queue.shift()!;
|
|
1558
|
+
this.domainTaskQueues.set(domain, queue);
|
|
1559
|
+
await this.assignTaskToDomain(nextTaskId, domain);
|
|
1560
|
+
}
|
|
1561
|
+
}
|
|
1562
|
+
|
|
1563
|
+
// =============================================================================
|
|
1564
|
+
// MCP-Compatible API Methods (@sparkleideas/agentic-flow@alpha compatibility)
|
|
1565
|
+
// =============================================================================
|
|
1566
|
+
|
|
1567
|
+
/**
|
|
1568
|
+
* Spawn a new agent (MCP-compatible alias for registerAgent)
|
|
1569
|
+
* Compatible with @sparkleideas/agentic-flow@alpha's agent spawn API
|
|
1570
|
+
*
|
|
1571
|
+
* @param options - Agent spawn options
|
|
1572
|
+
* @returns Spawned agent ID and details
|
|
1573
|
+
*/
|
|
1574
|
+
async spawnAgent(options: {
|
|
1575
|
+
type: AgentType;
|
|
1576
|
+
name?: string;
|
|
1577
|
+
capabilities?: string[];
|
|
1578
|
+
domain?: AgentDomain;
|
|
1579
|
+
agentNumber?: number;
|
|
1580
|
+
metadata?: Record<string, unknown>;
|
|
1581
|
+
}): Promise<{
|
|
1582
|
+
agentId: string;
|
|
1583
|
+
domain: AgentDomain;
|
|
1584
|
+
status: AgentStatus;
|
|
1585
|
+
spawned: boolean;
|
|
1586
|
+
}> {
|
|
1587
|
+
const startTime = performance.now();
|
|
1588
|
+
|
|
1589
|
+
// Create agent data from options
|
|
1590
|
+
const agentData: Omit<AgentState, 'id'> = {
|
|
1591
|
+
name: options.name || `${options.type}-agent-${this.agentCounter + 1}`,
|
|
1592
|
+
type: options.type,
|
|
1593
|
+
status: 'idle',
|
|
1594
|
+
capabilities: this.createCapabilitiesFromList(options.capabilities || []),
|
|
1595
|
+
metrics: this.createDefaultAgentMetrics(),
|
|
1596
|
+
workload: 0,
|
|
1597
|
+
health: 1.0,
|
|
1598
|
+
lastHeartbeat: new Date(),
|
|
1599
|
+
topologyRole: options.type === 'queen' ? 'queen' : 'worker',
|
|
1600
|
+
connections: [],
|
|
1601
|
+
};
|
|
1602
|
+
|
|
1603
|
+
// Determine domain and agent number
|
|
1604
|
+
let domain: AgentDomain;
|
|
1605
|
+
let agentId: string;
|
|
1606
|
+
|
|
1607
|
+
if (options.agentNumber) {
|
|
1608
|
+
// Use provided agent number to determine domain
|
|
1609
|
+
const result = await this.registerAgentWithDomain(agentData, options.agentNumber);
|
|
1610
|
+
agentId = result.agentId;
|
|
1611
|
+
domain = result.domain;
|
|
1612
|
+
} else if (options.domain) {
|
|
1613
|
+
// Use provided domain, assign next available number in that domain
|
|
1614
|
+
const config = this.domainConfigs.get(options.domain);
|
|
1615
|
+
const existingAgents = Array.from(this.agentDomainMap.entries())
|
|
1616
|
+
.filter(([, d]) => d === options.domain)
|
|
1617
|
+
.length;
|
|
1618
|
+
const nextNumber = config?.agentNumbers[existingAgents] || config?.agentNumbers[0] || 1;
|
|
1619
|
+
const result = await this.registerAgentWithDomain(agentData, nextNumber);
|
|
1620
|
+
agentId = result.agentId;
|
|
1621
|
+
domain = result.domain;
|
|
1622
|
+
} else {
|
|
1623
|
+
// Auto-assign to most appropriate domain based on type
|
|
1624
|
+
domain = this.agentTypeToDomain(options.type);
|
|
1625
|
+
agentId = await this.registerAgent(agentData);
|
|
1626
|
+
this.agentDomainMap.set(agentId, domain);
|
|
1627
|
+
}
|
|
1628
|
+
|
|
1629
|
+
const duration = performance.now() - startTime;
|
|
1630
|
+
|
|
1631
|
+
this.emitEvent('agent.joined', {
|
|
1632
|
+
agentId,
|
|
1633
|
+
type: options.type,
|
|
1634
|
+
domain,
|
|
1635
|
+
durationMs: duration,
|
|
1636
|
+
});
|
|
1637
|
+
|
|
1638
|
+
return {
|
|
1639
|
+
agentId,
|
|
1640
|
+
domain,
|
|
1641
|
+
status: 'idle',
|
|
1642
|
+
spawned: true,
|
|
1643
|
+
};
|
|
1644
|
+
}
|
|
1645
|
+
|
|
1646
|
+
/**
|
|
1647
|
+
* Terminate an agent (MCP-compatible alias for unregisterAgent)
|
|
1648
|
+
* Compatible with @sparkleideas/agentic-flow@alpha's agent terminate API
|
|
1649
|
+
*
|
|
1650
|
+
* @param agentId - Agent ID to terminate
|
|
1651
|
+
* @param options - Termination options
|
|
1652
|
+
* @returns Termination result
|
|
1653
|
+
*/
|
|
1654
|
+
async terminateAgent(
|
|
1655
|
+
agentId: string,
|
|
1656
|
+
options?: {
|
|
1657
|
+
force?: boolean;
|
|
1658
|
+
reason?: string;
|
|
1659
|
+
gracePeriodMs?: number;
|
|
1660
|
+
}
|
|
1661
|
+
): Promise<{
|
|
1662
|
+
terminated: boolean;
|
|
1663
|
+
agentId: string;
|
|
1664
|
+
reason?: string;
|
|
1665
|
+
tasksReassigned?: number;
|
|
1666
|
+
}> {
|
|
1667
|
+
const agent = this.state.agents.get(agentId);
|
|
1668
|
+
if (!agent) {
|
|
1669
|
+
return {
|
|
1670
|
+
terminated: false,
|
|
1671
|
+
agentId,
|
|
1672
|
+
reason: 'Agent not found',
|
|
1673
|
+
};
|
|
1674
|
+
}
|
|
1675
|
+
|
|
1676
|
+
// If agent has active tasks and not forcing, wait or reassign
|
|
1677
|
+
let tasksReassigned = 0;
|
|
1678
|
+
if (agent.currentTask && !options?.force) {
|
|
1679
|
+
const gracePeriodMs = options?.gracePeriodMs || 5000;
|
|
1680
|
+
|
|
1681
|
+
// Wait for grace period or force terminate
|
|
1682
|
+
const task = agent.currentTask;
|
|
1683
|
+
await new Promise(resolve => setTimeout(resolve, gracePeriodMs));
|
|
1684
|
+
|
|
1685
|
+
// Check if task still running
|
|
1686
|
+
const currentAgent = this.state.agents.get(agentId);
|
|
1687
|
+
if (currentAgent?.currentTask?.id === task.id) {
|
|
1688
|
+
// Reassign the task
|
|
1689
|
+
await this.cancelTask(task.id);
|
|
1690
|
+
tasksReassigned = 1;
|
|
1691
|
+
}
|
|
1692
|
+
}
|
|
1693
|
+
|
|
1694
|
+
// Remove from domain tracking
|
|
1695
|
+
this.agentDomainMap.delete(agentId);
|
|
1696
|
+
|
|
1697
|
+
// Unregister the agent
|
|
1698
|
+
await this.unregisterAgent(agentId);
|
|
1699
|
+
|
|
1700
|
+
this.emitEvent('agent.left', {
|
|
1701
|
+
agentId,
|
|
1702
|
+
reason: options?.reason || 'manual termination',
|
|
1703
|
+
tasksReassigned,
|
|
1704
|
+
});
|
|
1705
|
+
|
|
1706
|
+
return {
|
|
1707
|
+
terminated: true,
|
|
1708
|
+
agentId,
|
|
1709
|
+
reason: options?.reason || 'manual termination',
|
|
1710
|
+
tasksReassigned,
|
|
1711
|
+
};
|
|
1712
|
+
}
|
|
1713
|
+
|
|
1714
|
+
/**
|
|
1715
|
+
* Get agent status by ID (MCP-compatible)
|
|
1716
|
+
*/
|
|
1717
|
+
async getAgentStatus(agentId: string): Promise<{
|
|
1718
|
+
found: boolean;
|
|
1719
|
+
agentId: string;
|
|
1720
|
+
status?: AgentStatus;
|
|
1721
|
+
domain?: AgentDomain;
|
|
1722
|
+
workload?: number;
|
|
1723
|
+
health?: number;
|
|
1724
|
+
currentTask?: string;
|
|
1725
|
+
metrics?: AgentMetrics;
|
|
1726
|
+
}> {
|
|
1727
|
+
const agent = this.state.agents.get(agentId);
|
|
1728
|
+
if (!agent) {
|
|
1729
|
+
return { found: false, agentId };
|
|
1730
|
+
}
|
|
1731
|
+
|
|
1732
|
+
const domain = this.agentDomainMap.get(agentId);
|
|
1733
|
+
|
|
1734
|
+
return {
|
|
1735
|
+
found: true,
|
|
1736
|
+
agentId,
|
|
1737
|
+
status: agent.status,
|
|
1738
|
+
domain,
|
|
1739
|
+
workload: agent.workload,
|
|
1740
|
+
health: agent.health,
|
|
1741
|
+
currentTask: agent.currentTask?.id,
|
|
1742
|
+
metrics: agent.metrics,
|
|
1743
|
+
};
|
|
1744
|
+
}
|
|
1745
|
+
|
|
1746
|
+
/**
|
|
1747
|
+
* List all agents with optional filters (MCP-compatible)
|
|
1748
|
+
*/
|
|
1749
|
+
listAgents(filters?: {
|
|
1750
|
+
status?: AgentStatus;
|
|
1751
|
+
domain?: AgentDomain;
|
|
1752
|
+
type?: AgentType;
|
|
1753
|
+
available?: boolean;
|
|
1754
|
+
}): Array<{
|
|
1755
|
+
agentId: string;
|
|
1756
|
+
name: string;
|
|
1757
|
+
type: AgentType;
|
|
1758
|
+
status: AgentStatus;
|
|
1759
|
+
domain?: AgentDomain;
|
|
1760
|
+
workload: number;
|
|
1761
|
+
health: number;
|
|
1762
|
+
}> {
|
|
1763
|
+
let agents = this.getAllAgents();
|
|
1764
|
+
|
|
1765
|
+
if (filters?.status) {
|
|
1766
|
+
agents = agents.filter(a => a.status === filters.status);
|
|
1767
|
+
}
|
|
1768
|
+
if (filters?.type) {
|
|
1769
|
+
agents = agents.filter(a => a.type === filters.type);
|
|
1770
|
+
}
|
|
1771
|
+
if (filters?.available) {
|
|
1772
|
+
agents = agents.filter(a => a.status === 'idle');
|
|
1773
|
+
}
|
|
1774
|
+
if (filters?.domain) {
|
|
1775
|
+
agents = agents.filter(a => {
|
|
1776
|
+
const domain = this.agentDomainMap.get(a.id.id);
|
|
1777
|
+
return domain === filters.domain;
|
|
1778
|
+
});
|
|
1779
|
+
}
|
|
1780
|
+
|
|
1781
|
+
return agents.map(a => ({
|
|
1782
|
+
agentId: a.id.id,
|
|
1783
|
+
name: a.name,
|
|
1784
|
+
type: a.type,
|
|
1785
|
+
status: a.status,
|
|
1786
|
+
domain: this.agentDomainMap.get(a.id.id),
|
|
1787
|
+
workload: a.workload,
|
|
1788
|
+
health: a.health,
|
|
1789
|
+
}));
|
|
1790
|
+
}
|
|
1791
|
+
|
|
1792
|
+
// =============================================================================
|
|
1793
|
+
// Helper Methods for MCP API
|
|
1794
|
+
// =============================================================================
|
|
1795
|
+
|
|
1796
|
+
private createCapabilitiesFromList(capabilities: string[]): AgentCapabilities {
|
|
1797
|
+
return {
|
|
1798
|
+
codeGeneration: capabilities.includes('code-generation'),
|
|
1799
|
+
codeReview: capabilities.includes('code-review'),
|
|
1800
|
+
testing: capabilities.includes('testing'),
|
|
1801
|
+
documentation: capabilities.includes('documentation'),
|
|
1802
|
+
research: capabilities.includes('research'),
|
|
1803
|
+
analysis: capabilities.includes('analysis'),
|
|
1804
|
+
coordination: capabilities.includes('coordination'),
|
|
1805
|
+
languages: ['typescript', 'javascript', 'python'],
|
|
1806
|
+
frameworks: ['node', 'react', 'vitest'],
|
|
1807
|
+
domains: capabilities,
|
|
1808
|
+
tools: ['git', 'npm', 'editor', 'claude'],
|
|
1809
|
+
maxConcurrentTasks: 3,
|
|
1810
|
+
maxMemoryUsage: 512 * 1024 * 1024,
|
|
1811
|
+
maxExecutionTime: SWARM_CONSTANTS.DEFAULT_TASK_TIMEOUT_MS,
|
|
1812
|
+
reliability: 0.95,
|
|
1813
|
+
speed: 1.0,
|
|
1814
|
+
quality: 0.9,
|
|
1815
|
+
};
|
|
1816
|
+
}
|
|
1817
|
+
|
|
1818
|
+
private agentTypeToDomain(type: AgentType): AgentDomain {
|
|
1819
|
+
const typeMapping: Record<string, AgentDomain> = {
|
|
1820
|
+
queen: 'queen',
|
|
1821
|
+
coordinator: 'queen',
|
|
1822
|
+
security: 'security',
|
|
1823
|
+
architect: 'core',
|
|
1824
|
+
coder: 'core',
|
|
1825
|
+
developer: 'core',
|
|
1826
|
+
tester: 'support',
|
|
1827
|
+
reviewer: 'security',
|
|
1828
|
+
researcher: 'integration',
|
|
1829
|
+
analyst: 'core',
|
|
1830
|
+
optimizer: 'support',
|
|
1831
|
+
documenter: 'support',
|
|
1832
|
+
monitor: 'support',
|
|
1833
|
+
specialist: 'core',
|
|
1834
|
+
worker: 'core',
|
|
1835
|
+
};
|
|
1836
|
+
return typeMapping[type as string] || 'core';
|
|
1837
|
+
}
|
|
1838
|
+
}
|
|
1839
|
+
|
|
1840
|
+
export function createUnifiedSwarmCoordinator(
|
|
1841
|
+
config?: Partial<CoordinatorConfig>
|
|
1842
|
+
): UnifiedSwarmCoordinator {
|
|
1843
|
+
return new UnifiedSwarmCoordinator(config);
|
|
1844
|
+
}
|