@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,501 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* UnifiedSwarmCoordinator Tests
|
|
3
|
+
* Comprehensive tests for the unified swarm coordination system
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
|
|
7
|
+
import { UnifiedSwarmCoordinator, createUnifiedSwarmCoordinator } from '../src/unified-coordinator.js';
|
|
8
|
+
import type {
|
|
9
|
+
AgentState,
|
|
10
|
+
TaskDefinition,
|
|
11
|
+
CoordinatorConfig,
|
|
12
|
+
AgentType,
|
|
13
|
+
TaskType,
|
|
14
|
+
TaskPriority,
|
|
15
|
+
} from '../src/types.js';
|
|
16
|
+
|
|
17
|
+
describe('UnifiedSwarmCoordinator', () => {
|
|
18
|
+
let coordinator: UnifiedSwarmCoordinator;
|
|
19
|
+
|
|
20
|
+
beforeEach(async () => {
|
|
21
|
+
coordinator = createUnifiedSwarmCoordinator({
|
|
22
|
+
maxAgents: 20,
|
|
23
|
+
maxTasks: 100,
|
|
24
|
+
heartbeatIntervalMs: 1000,
|
|
25
|
+
healthCheckIntervalMs: 2000,
|
|
26
|
+
taskTimeoutMs: 10000,
|
|
27
|
+
topology: {
|
|
28
|
+
type: 'hierarchical',
|
|
29
|
+
maxAgents: 20,
|
|
30
|
+
},
|
|
31
|
+
consensus: {
|
|
32
|
+
algorithm: 'raft',
|
|
33
|
+
threshold: 0.66,
|
|
34
|
+
timeoutMs: 5000,
|
|
35
|
+
maxRounds: 5,
|
|
36
|
+
requireQuorum: true,
|
|
37
|
+
},
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
await coordinator.initialize();
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
afterEach(async () => {
|
|
44
|
+
await coordinator.shutdown();
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
describe('Initialization', () => {
|
|
48
|
+
it('should initialize successfully', () => {
|
|
49
|
+
const state = coordinator.getState();
|
|
50
|
+
expect(state.status).toBe('running');
|
|
51
|
+
expect(state.id).toBeDefined();
|
|
52
|
+
expect(state.id.id).toContain('swarm_');
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
it('should create default configuration', () => {
|
|
56
|
+
const state = coordinator.getState();
|
|
57
|
+
expect(state.topology.type).toBe('hierarchical');
|
|
58
|
+
expect(state.agents.size).toBe(0);
|
|
59
|
+
expect(state.tasks.size).toBe(0);
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
it('should initialize with mesh topology', async () => {
|
|
63
|
+
const meshCoordinator = createUnifiedSwarmCoordinator({
|
|
64
|
+
topology: { type: 'mesh', maxAgents: 10 },
|
|
65
|
+
});
|
|
66
|
+
await meshCoordinator.initialize();
|
|
67
|
+
|
|
68
|
+
expect(meshCoordinator.getTopology()).toBe('mesh');
|
|
69
|
+
|
|
70
|
+
await meshCoordinator.shutdown();
|
|
71
|
+
});
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
describe('Agent Registration', () => {
|
|
75
|
+
it('should register a new agent', async () => {
|
|
76
|
+
const agentData: Omit<AgentState, 'id'> = {
|
|
77
|
+
name: 'test-agent-1',
|
|
78
|
+
type: 'coder',
|
|
79
|
+
status: 'idle',
|
|
80
|
+
capabilities: createTestCapabilities(),
|
|
81
|
+
metrics: createTestMetrics(),
|
|
82
|
+
workload: 0,
|
|
83
|
+
health: 1.0,
|
|
84
|
+
lastHeartbeat: new Date(),
|
|
85
|
+
topologyRole: 'worker',
|
|
86
|
+
connections: [],
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
const agentId = await coordinator.registerAgent(agentData);
|
|
90
|
+
|
|
91
|
+
expect(agentId).toBeDefined();
|
|
92
|
+
expect(agentId).toContain('agent_');
|
|
93
|
+
|
|
94
|
+
const agent = coordinator.getAgent(agentId);
|
|
95
|
+
expect(agent).toBeDefined();
|
|
96
|
+
expect(agent?.name).toBe('test-agent-1');
|
|
97
|
+
expect(agent?.type).toBe('coder');
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
it('should register multiple agents', async () => {
|
|
101
|
+
const agentTypes: AgentType[] = ['coder', 'tester', 'reviewer'];
|
|
102
|
+
const agentIds: string[] = [];
|
|
103
|
+
|
|
104
|
+
for (const type of agentTypes) {
|
|
105
|
+
const agentData: Omit<AgentState, 'id'> = {
|
|
106
|
+
name: `agent-${type}`,
|
|
107
|
+
type,
|
|
108
|
+
status: 'idle',
|
|
109
|
+
capabilities: createTestCapabilities(),
|
|
110
|
+
metrics: createTestMetrics(),
|
|
111
|
+
workload: 0,
|
|
112
|
+
health: 1.0,
|
|
113
|
+
lastHeartbeat: new Date(),
|
|
114
|
+
topologyRole: 'worker',
|
|
115
|
+
connections: [],
|
|
116
|
+
};
|
|
117
|
+
|
|
118
|
+
const id = await coordinator.registerAgent(agentData);
|
|
119
|
+
agentIds.push(id);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
expect(agentIds).toHaveLength(3);
|
|
123
|
+
expect(coordinator.getAllAgents()).toHaveLength(3);
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
it('should throw error when max agents reached', async () => {
|
|
127
|
+
const smallCoordinator = createUnifiedSwarmCoordinator({
|
|
128
|
+
maxAgents: 2,
|
|
129
|
+
});
|
|
130
|
+
await smallCoordinator.initialize();
|
|
131
|
+
|
|
132
|
+
// Register 2 agents
|
|
133
|
+
for (let i = 0; i < 2; i++) {
|
|
134
|
+
await smallCoordinator.registerAgent({
|
|
135
|
+
name: `agent-${i}`,
|
|
136
|
+
type: 'worker',
|
|
137
|
+
status: 'idle',
|
|
138
|
+
capabilities: createTestCapabilities(),
|
|
139
|
+
metrics: createTestMetrics(),
|
|
140
|
+
workload: 0,
|
|
141
|
+
health: 1.0,
|
|
142
|
+
lastHeartbeat: new Date(),
|
|
143
|
+
topologyRole: 'worker',
|
|
144
|
+
connections: [],
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// Third should fail
|
|
149
|
+
await expect(
|
|
150
|
+
smallCoordinator.registerAgent({
|
|
151
|
+
name: 'agent-overflow',
|
|
152
|
+
type: 'worker',
|
|
153
|
+
status: 'idle',
|
|
154
|
+
capabilities: createTestCapabilities(),
|
|
155
|
+
metrics: createTestMetrics(),
|
|
156
|
+
workload: 0,
|
|
157
|
+
health: 1.0,
|
|
158
|
+
lastHeartbeat: new Date(),
|
|
159
|
+
topologyRole: 'worker',
|
|
160
|
+
connections: [],
|
|
161
|
+
})
|
|
162
|
+
).rejects.toThrow('Maximum agents');
|
|
163
|
+
|
|
164
|
+
await smallCoordinator.shutdown();
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
it('should unregister an agent', async () => {
|
|
168
|
+
const agentId = await coordinator.registerAgent({
|
|
169
|
+
name: 'temporary-agent',
|
|
170
|
+
type: 'worker',
|
|
171
|
+
status: 'idle',
|
|
172
|
+
capabilities: createTestCapabilities(),
|
|
173
|
+
metrics: createTestMetrics(),
|
|
174
|
+
workload: 0,
|
|
175
|
+
health: 1.0,
|
|
176
|
+
lastHeartbeat: new Date(),
|
|
177
|
+
topologyRole: 'worker',
|
|
178
|
+
connections: [],
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
expect(coordinator.getAgent(agentId)).toBeDefined();
|
|
182
|
+
|
|
183
|
+
await coordinator.unregisterAgent(agentId);
|
|
184
|
+
|
|
185
|
+
expect(coordinator.getAgent(agentId)).toBeUndefined();
|
|
186
|
+
});
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
describe('Task Management', () => {
|
|
190
|
+
let agentId: string;
|
|
191
|
+
|
|
192
|
+
beforeEach(async () => {
|
|
193
|
+
agentId = await coordinator.registerAgent({
|
|
194
|
+
name: 'worker-agent',
|
|
195
|
+
type: 'coder',
|
|
196
|
+
status: 'idle',
|
|
197
|
+
capabilities: createTestCapabilities(),
|
|
198
|
+
metrics: createTestMetrics(),
|
|
199
|
+
workload: 0,
|
|
200
|
+
health: 1.0,
|
|
201
|
+
lastHeartbeat: new Date(),
|
|
202
|
+
topologyRole: 'worker',
|
|
203
|
+
connections: [],
|
|
204
|
+
});
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
it('should submit a new task', async () => {
|
|
208
|
+
const taskData: Omit<TaskDefinition, 'id' | 'status' | 'createdAt'> = {
|
|
209
|
+
type: 'coding',
|
|
210
|
+
name: 'Test Task',
|
|
211
|
+
description: 'A test coding task',
|
|
212
|
+
priority: 'normal',
|
|
213
|
+
dependencies: [],
|
|
214
|
+
input: { code: 'console.log("test")' },
|
|
215
|
+
timeoutMs: 5000,
|
|
216
|
+
retries: 0,
|
|
217
|
+
maxRetries: 3,
|
|
218
|
+
metadata: {},
|
|
219
|
+
};
|
|
220
|
+
|
|
221
|
+
const taskId = await coordinator.submitTask(taskData);
|
|
222
|
+
|
|
223
|
+
expect(taskId).toBeDefined();
|
|
224
|
+
expect(taskId).toContain('task_');
|
|
225
|
+
|
|
226
|
+
const task = coordinator.getTask(taskId);
|
|
227
|
+
expect(task).toBeDefined();
|
|
228
|
+
expect(task?.name).toBe('Test Task');
|
|
229
|
+
expect(task?.status).toBe('assigned');
|
|
230
|
+
});
|
|
231
|
+
|
|
232
|
+
it('should assign task to available agent', async () => {
|
|
233
|
+
const taskId = await coordinator.submitTask({
|
|
234
|
+
type: 'coding',
|
|
235
|
+
name: 'Code Task',
|
|
236
|
+
description: 'Task for coder',
|
|
237
|
+
priority: 'high',
|
|
238
|
+
dependencies: [],
|
|
239
|
+
input: {},
|
|
240
|
+
timeoutMs: 5000,
|
|
241
|
+
retries: 0,
|
|
242
|
+
maxRetries: 3,
|
|
243
|
+
metadata: {},
|
|
244
|
+
});
|
|
245
|
+
|
|
246
|
+
const task = coordinator.getTask(taskId);
|
|
247
|
+
expect(task?.assignedTo).toBeDefined();
|
|
248
|
+
expect(task?.assignedTo?.id).toBe(agentId);
|
|
249
|
+
|
|
250
|
+
const agent = coordinator.getAgent(agentId);
|
|
251
|
+
expect(agent?.status).toBe('busy');
|
|
252
|
+
expect(agent?.currentTask?.id).toBe(taskId);
|
|
253
|
+
});
|
|
254
|
+
|
|
255
|
+
it('should cancel a task', async () => {
|
|
256
|
+
const taskId = await coordinator.submitTask({
|
|
257
|
+
type: 'testing',
|
|
258
|
+
name: 'Test Task',
|
|
259
|
+
description: 'Task to cancel',
|
|
260
|
+
priority: 'normal',
|
|
261
|
+
dependencies: [],
|
|
262
|
+
input: {},
|
|
263
|
+
timeoutMs: 5000,
|
|
264
|
+
retries: 0,
|
|
265
|
+
maxRetries: 3,
|
|
266
|
+
metadata: {},
|
|
267
|
+
});
|
|
268
|
+
|
|
269
|
+
await coordinator.cancelTask(taskId);
|
|
270
|
+
|
|
271
|
+
const task = coordinator.getTask(taskId);
|
|
272
|
+
expect(task?.status).toBe('cancelled');
|
|
273
|
+
|
|
274
|
+
const agent = coordinator.getAgent(agentId);
|
|
275
|
+
expect(agent?.status).toBe('idle');
|
|
276
|
+
expect(agent?.currentTask).toBeUndefined();
|
|
277
|
+
});
|
|
278
|
+
|
|
279
|
+
it('should get tasks by status', async () => {
|
|
280
|
+
await coordinator.submitTask({
|
|
281
|
+
type: 'coding',
|
|
282
|
+
name: 'Task 1',
|
|
283
|
+
description: 'First task',
|
|
284
|
+
priority: 'normal',
|
|
285
|
+
dependencies: [],
|
|
286
|
+
input: {},
|
|
287
|
+
timeoutMs: 5000,
|
|
288
|
+
retries: 0,
|
|
289
|
+
maxRetries: 3,
|
|
290
|
+
metadata: {},
|
|
291
|
+
});
|
|
292
|
+
|
|
293
|
+
const assignedTasks = coordinator.getTasksByStatus('assigned');
|
|
294
|
+
expect(assignedTasks).toHaveLength(1);
|
|
295
|
+
});
|
|
296
|
+
});
|
|
297
|
+
|
|
298
|
+
describe('Topology Management', () => {
|
|
299
|
+
it('should maintain hierarchical topology', async () => {
|
|
300
|
+
// Register queen
|
|
301
|
+
const queenId = await coordinator.registerAgent({
|
|
302
|
+
name: 'queen-agent',
|
|
303
|
+
type: 'queen',
|
|
304
|
+
status: 'idle',
|
|
305
|
+
capabilities: createTestCapabilities(),
|
|
306
|
+
metrics: createTestMetrics(),
|
|
307
|
+
workload: 0,
|
|
308
|
+
health: 1.0,
|
|
309
|
+
lastHeartbeat: new Date(),
|
|
310
|
+
topologyRole: 'queen',
|
|
311
|
+
connections: [],
|
|
312
|
+
});
|
|
313
|
+
|
|
314
|
+
// Register workers
|
|
315
|
+
const worker1Id = await coordinator.registerAgent({
|
|
316
|
+
name: 'worker-1',
|
|
317
|
+
type: 'worker',
|
|
318
|
+
status: 'idle',
|
|
319
|
+
capabilities: createTestCapabilities(),
|
|
320
|
+
metrics: createTestMetrics(),
|
|
321
|
+
workload: 0,
|
|
322
|
+
health: 1.0,
|
|
323
|
+
lastHeartbeat: new Date(),
|
|
324
|
+
topologyRole: 'worker',
|
|
325
|
+
connections: [],
|
|
326
|
+
});
|
|
327
|
+
|
|
328
|
+
const state = coordinator.getState();
|
|
329
|
+
expect(state.topology.nodes).toHaveLength(2);
|
|
330
|
+
|
|
331
|
+
const queen = state.topology.nodes.find(n => n.role === 'queen');
|
|
332
|
+
expect(queen).toBeDefined();
|
|
333
|
+
expect(queen?.agentId).toBe(queenId);
|
|
334
|
+
});
|
|
335
|
+
|
|
336
|
+
it('should change topology type', () => {
|
|
337
|
+
coordinator.setTopology('mesh');
|
|
338
|
+
expect(coordinator.getTopology()).toBe('mesh');
|
|
339
|
+
|
|
340
|
+
coordinator.setTopology('centralized');
|
|
341
|
+
expect(coordinator.getTopology()).toBe('centralized');
|
|
342
|
+
});
|
|
343
|
+
});
|
|
344
|
+
|
|
345
|
+
describe('Lifecycle Management', () => {
|
|
346
|
+
it('should pause and resume coordinator', async () => {
|
|
347
|
+
await coordinator.pause();
|
|
348
|
+
expect(coordinator.getState().status).toBe('paused');
|
|
349
|
+
|
|
350
|
+
await coordinator.resume();
|
|
351
|
+
expect(coordinator.getState().status).toBe('running');
|
|
352
|
+
});
|
|
353
|
+
|
|
354
|
+
it('should shutdown cleanly', async () => {
|
|
355
|
+
const agentId = await coordinator.registerAgent({
|
|
356
|
+
name: 'agent-to-cleanup',
|
|
357
|
+
type: 'worker',
|
|
358
|
+
status: 'idle',
|
|
359
|
+
capabilities: createTestCapabilities(),
|
|
360
|
+
metrics: createTestMetrics(),
|
|
361
|
+
workload: 0,
|
|
362
|
+
health: 1.0,
|
|
363
|
+
lastHeartbeat: new Date(),
|
|
364
|
+
topologyRole: 'worker',
|
|
365
|
+
connections: [],
|
|
366
|
+
});
|
|
367
|
+
|
|
368
|
+
await coordinator.shutdown();
|
|
369
|
+
|
|
370
|
+
expect(coordinator.getState().status).toBe('stopped');
|
|
371
|
+
expect(coordinator.getAllAgents()).toHaveLength(0);
|
|
372
|
+
});
|
|
373
|
+
});
|
|
374
|
+
|
|
375
|
+
describe('Domain-Based Routing', () => {
|
|
376
|
+
it('should assign tasks to specific domains', async () => {
|
|
377
|
+
// Register agent with domain
|
|
378
|
+
const result = await coordinator.registerAgentWithDomain(
|
|
379
|
+
{
|
|
380
|
+
name: 'security-agent',
|
|
381
|
+
type: 'specialist',
|
|
382
|
+
status: 'idle',
|
|
383
|
+
capabilities: createTestCapabilities(),
|
|
384
|
+
metrics: createTestMetrics(),
|
|
385
|
+
workload: 0,
|
|
386
|
+
health: 1.0,
|
|
387
|
+
lastHeartbeat: new Date(),
|
|
388
|
+
topologyRole: 'worker',
|
|
389
|
+
connections: [],
|
|
390
|
+
},
|
|
391
|
+
2 // Security domain (agent #2)
|
|
392
|
+
);
|
|
393
|
+
|
|
394
|
+
expect(result.domain).toBe('security');
|
|
395
|
+
expect(result.agentId).toBeDefined();
|
|
396
|
+
});
|
|
397
|
+
|
|
398
|
+
it('should get agent domain from agent number', () => {
|
|
399
|
+
expect(coordinator.getAgentDomain(1)).toBe('queen');
|
|
400
|
+
expect(coordinator.getAgentDomain(2)).toBe('security');
|
|
401
|
+
expect(coordinator.getAgentDomain(5)).toBe('core');
|
|
402
|
+
expect(coordinator.getAgentDomain(10)).toBe('integration');
|
|
403
|
+
expect(coordinator.getAgentDomain(13)).toBe('support');
|
|
404
|
+
});
|
|
405
|
+
|
|
406
|
+
it('should spawn full 15-agent hierarchy', async () => {
|
|
407
|
+
const hierarchy = await coordinator.spawnFullHierarchy();
|
|
408
|
+
|
|
409
|
+
expect(hierarchy.size).toBe(15);
|
|
410
|
+
expect(hierarchy.get(1)?.domain).toBe('queen');
|
|
411
|
+
expect(hierarchy.get(2)?.domain).toBe('security');
|
|
412
|
+
expect(hierarchy.get(5)?.domain).toBe('core');
|
|
413
|
+
|
|
414
|
+
const status = coordinator.getStatus();
|
|
415
|
+
expect(status.domains).toHaveLength(5);
|
|
416
|
+
});
|
|
417
|
+
});
|
|
418
|
+
|
|
419
|
+
describe('Performance Metrics', () => {
|
|
420
|
+
it('should track coordinator metrics', async () => {
|
|
421
|
+
const metrics = coordinator.getMetrics();
|
|
422
|
+
|
|
423
|
+
expect(metrics).toBeDefined();
|
|
424
|
+
expect(metrics.uptime).toBeGreaterThanOrEqual(0);
|
|
425
|
+
expect(metrics.activeAgents).toBe(0);
|
|
426
|
+
expect(metrics.totalTasks).toBe(0);
|
|
427
|
+
expect(metrics.completedTasks).toBe(0);
|
|
428
|
+
});
|
|
429
|
+
|
|
430
|
+
it('should generate performance report', async () => {
|
|
431
|
+
const report = coordinator.getPerformanceReport();
|
|
432
|
+
|
|
433
|
+
expect(report).toBeDefined();
|
|
434
|
+
expect(report.timestamp).toBeInstanceOf(Date);
|
|
435
|
+
expect(report.coordinationLatencyP50).toBeGreaterThanOrEqual(0);
|
|
436
|
+
expect(report.messagesPerSecond).toBeGreaterThanOrEqual(0);
|
|
437
|
+
});
|
|
438
|
+
|
|
439
|
+
it('should report healthy status', async () => {
|
|
440
|
+
// Add at least one agent to be considered healthy
|
|
441
|
+
await coordinator.registerAgent({
|
|
442
|
+
name: 'health-agent',
|
|
443
|
+
type: 'worker',
|
|
444
|
+
status: 'idle',
|
|
445
|
+
capabilities: createTestCapabilities(),
|
|
446
|
+
metrics: createTestMetrics(),
|
|
447
|
+
workload: 0,
|
|
448
|
+
health: 1.0,
|
|
449
|
+
lastHeartbeat: new Date(),
|
|
450
|
+
topologyRole: 'worker',
|
|
451
|
+
connections: [],
|
|
452
|
+
});
|
|
453
|
+
|
|
454
|
+
// Wait for health check interval (2000ms configured in beforeEach)
|
|
455
|
+
await new Promise(resolve => setTimeout(resolve, 2500));
|
|
456
|
+
|
|
457
|
+
const metrics = coordinator.getMetrics();
|
|
458
|
+
expect(metrics.activeAgents).toBeGreaterThan(0);
|
|
459
|
+
expect(coordinator.isHealthy()).toBe(true);
|
|
460
|
+
});
|
|
461
|
+
});
|
|
462
|
+
});
|
|
463
|
+
|
|
464
|
+
// ===== TEST UTILITIES =====
|
|
465
|
+
|
|
466
|
+
function createTestCapabilities() {
|
|
467
|
+
return {
|
|
468
|
+
codeGeneration: true,
|
|
469
|
+
codeReview: true,
|
|
470
|
+
testing: true,
|
|
471
|
+
documentation: true,
|
|
472
|
+
research: true,
|
|
473
|
+
analysis: true,
|
|
474
|
+
coordination: false,
|
|
475
|
+
languages: ['typescript', 'javascript'],
|
|
476
|
+
frameworks: ['node', 'vitest'],
|
|
477
|
+
domains: ['backend', 'testing'],
|
|
478
|
+
tools: ['git', 'npm'],
|
|
479
|
+
maxConcurrentTasks: 3,
|
|
480
|
+
maxMemoryUsage: 512 * 1024 * 1024,
|
|
481
|
+
maxExecutionTime: 300000,
|
|
482
|
+
reliability: 0.95,
|
|
483
|
+
speed: 1.0,
|
|
484
|
+
quality: 0.9,
|
|
485
|
+
};
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
function createTestMetrics() {
|
|
489
|
+
return {
|
|
490
|
+
tasksCompleted: 0,
|
|
491
|
+
tasksFailed: 0,
|
|
492
|
+
averageExecutionTime: 0,
|
|
493
|
+
successRate: 1.0,
|
|
494
|
+
cpuUsage: 0,
|
|
495
|
+
memoryUsage: 0,
|
|
496
|
+
messagesProcessed: 0,
|
|
497
|
+
lastActivity: new Date(),
|
|
498
|
+
responseTime: 0,
|
|
499
|
+
health: 1.0,
|
|
500
|
+
};
|
|
501
|
+
}
|