@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.
Files changed (65) hide show
  1. package/MIGRATION.md +472 -0
  2. package/README.md +634 -0
  3. package/__tests__/consensus.test.ts +577 -0
  4. package/__tests__/coordinator.test.ts +501 -0
  5. package/__tests__/queen-coordinator.test.ts +1335 -0
  6. package/__tests__/topology.test.ts +621 -0
  7. package/package.json +32 -0
  8. package/src/agent-pool.ts +476 -0
  9. package/src/application/commands/create-task.command.ts +124 -0
  10. package/src/application/commands/spawn-agent.command.ts +122 -0
  11. package/src/application/index.ts +30 -0
  12. package/src/application/services/swarm-application-service.ts +200 -0
  13. package/src/attention-coordinator.ts +1000 -0
  14. package/src/consensus/byzantine.ts +431 -0
  15. package/src/consensus/gossip.ts +513 -0
  16. package/src/consensus/index.ts +267 -0
  17. package/src/consensus/raft.ts +443 -0
  18. package/src/coordination/agent-registry.ts +544 -0
  19. package/src/coordination/index.ts +23 -0
  20. package/src/coordination/swarm-hub.ts +776 -0
  21. package/src/coordination/task-orchestrator.ts +605 -0
  22. package/src/domain/entities/agent.d.ts +151 -0
  23. package/src/domain/entities/agent.d.ts.map +1 -0
  24. package/src/domain/entities/agent.js +280 -0
  25. package/src/domain/entities/agent.js.map +1 -0
  26. package/src/domain/entities/agent.ts +370 -0
  27. package/src/domain/entities/task.d.ts +133 -0
  28. package/src/domain/entities/task.d.ts.map +1 -0
  29. package/src/domain/entities/task.js +261 -0
  30. package/src/domain/entities/task.js.map +1 -0
  31. package/src/domain/entities/task.ts +319 -0
  32. package/src/domain/index.ts +41 -0
  33. package/src/domain/repositories/agent-repository.interface.d.ts +57 -0
  34. package/src/domain/repositories/agent-repository.interface.d.ts.map +1 -0
  35. package/src/domain/repositories/agent-repository.interface.js +9 -0
  36. package/src/domain/repositories/agent-repository.interface.js.map +1 -0
  37. package/src/domain/repositories/agent-repository.interface.ts +69 -0
  38. package/src/domain/repositories/task-repository.interface.d.ts +61 -0
  39. package/src/domain/repositories/task-repository.interface.d.ts.map +1 -0
  40. package/src/domain/repositories/task-repository.interface.js +9 -0
  41. package/src/domain/repositories/task-repository.interface.js.map +1 -0
  42. package/src/domain/repositories/task-repository.interface.ts +75 -0
  43. package/src/domain/services/coordination-service.ts +320 -0
  44. package/src/federation-hub.d.ts +284 -0
  45. package/src/federation-hub.d.ts.map +1 -0
  46. package/src/federation-hub.js +692 -0
  47. package/src/federation-hub.js.map +1 -0
  48. package/src/federation-hub.ts +979 -0
  49. package/src/index.ts +348 -0
  50. package/src/message-bus.ts +607 -0
  51. package/src/queen-coordinator.ts +2025 -0
  52. package/src/shared/events.ts +285 -0
  53. package/src/shared/types.ts +389 -0
  54. package/src/topology-manager.ts +656 -0
  55. package/src/types.ts +545 -0
  56. package/src/unified-coordinator.ts +1844 -0
  57. package/src/workers/index.ts +65 -0
  58. package/src/workers/worker-dispatch.d.ts +234 -0
  59. package/src/workers/worker-dispatch.d.ts.map +1 -0
  60. package/src/workers/worker-dispatch.js +842 -0
  61. package/src/workers/worker-dispatch.js.map +1 -0
  62. package/src/workers/worker-dispatch.ts +1076 -0
  63. package/tmp.json +0 -0
  64. package/tsconfig.json +9 -0
  65. package/vitest.config.ts +20 -0
@@ -0,0 +1,476 @@
1
+ /**
2
+ * V3 Agent Pool
3
+ * Manages agent lifecycle, pooling, and auto-scaling
4
+ */
5
+
6
+ import { EventEmitter } from 'events';
7
+ import {
8
+ AgentState,
9
+ AgentType,
10
+ AgentStatus,
11
+ AgentCapabilities,
12
+ AgentMetrics,
13
+ AgentPoolConfig,
14
+ AgentPoolState,
15
+ IAgentPool,
16
+ SWARM_CONSTANTS,
17
+ } from './types.js';
18
+
19
+ interface PooledAgent {
20
+ agent: AgentState;
21
+ acquiredAt?: Date;
22
+ lastUsed: Date;
23
+ usageCount: number;
24
+ }
25
+
26
+ export class AgentPool extends EventEmitter implements IAgentPool {
27
+ private config: AgentPoolConfig;
28
+ private pooledAgents: Map<string, PooledAgent> = new Map();
29
+ private available: Set<string> = new Set();
30
+ private busy: Set<string> = new Set();
31
+ private pendingScale: number = 0;
32
+ private lastScaleOperation?: Date;
33
+ private healthCheckInterval?: NodeJS.Timeout;
34
+ private agentCounter: number = 0;
35
+
36
+ constructor(config: Partial<AgentPoolConfig> = {}) {
37
+ super();
38
+ this.config = {
39
+ name: config.name ?? 'default-pool',
40
+ type: config.type ?? 'worker',
41
+ minSize: config.minSize ?? 1,
42
+ maxSize: config.maxSize ?? 10,
43
+ scaleUpThreshold: config.scaleUpThreshold ?? 0.8,
44
+ scaleDownThreshold: config.scaleDownThreshold ?? 0.2,
45
+ cooldownMs: config.cooldownMs ?? 30000,
46
+ healthCheckIntervalMs: config.healthCheckIntervalMs ?? SWARM_CONSTANTS.DEFAULT_HEALTH_CHECK_INTERVAL_MS,
47
+ };
48
+ }
49
+
50
+ async initialize(config?: AgentPoolConfig): Promise<void> {
51
+ if (config) {
52
+ this.config = { ...this.config, ...config };
53
+ }
54
+
55
+ // Start with minimum pool size - create agents in parallel
56
+ const createPromises = Array.from(
57
+ { length: this.config.minSize },
58
+ () => this.createPooledAgent()
59
+ );
60
+ await Promise.all(createPromises);
61
+
62
+ // Start health checks
63
+ this.startHealthChecks();
64
+
65
+ this.emit('initialized', {
66
+ poolName: this.config.name,
67
+ size: this.pooledAgents.size
68
+ });
69
+ }
70
+
71
+ async shutdown(): Promise<void> {
72
+ if (this.healthCheckInterval) {
73
+ clearInterval(this.healthCheckInterval);
74
+ this.healthCheckInterval = undefined;
75
+ }
76
+
77
+ // Mark all agents as terminated
78
+ for (const pooled of this.pooledAgents.values()) {
79
+ pooled.agent.status = 'terminated';
80
+ }
81
+
82
+ this.pooledAgents.clear();
83
+ this.available.clear();
84
+ this.busy.clear();
85
+
86
+ this.emit('shutdown');
87
+ }
88
+
89
+ async acquire(): Promise<AgentState | undefined> {
90
+ // Try to get an available agent
91
+ const availableId = this.available.values().next().value as string | undefined;
92
+
93
+ if (availableId) {
94
+ const pooled = this.pooledAgents.get(availableId);
95
+ if (pooled) {
96
+ this.available.delete(availableId);
97
+ this.busy.add(availableId);
98
+ pooled.acquiredAt = new Date();
99
+ pooled.usageCount++;
100
+ pooled.agent.status = 'busy';
101
+
102
+ this.emit('agent.acquired', { agentId: availableId });
103
+
104
+ // Check if we need to scale up
105
+ await this.checkScaling();
106
+
107
+ return pooled.agent;
108
+ }
109
+ }
110
+
111
+ // No available agents, try to scale up
112
+ if (this.pooledAgents.size < this.config.maxSize) {
113
+ const agent = await this.createPooledAgent();
114
+ if (agent) {
115
+ const pooled = this.pooledAgents.get(agent.id.id)!;
116
+ this.available.delete(agent.id.id);
117
+ this.busy.add(agent.id.id);
118
+ pooled.acquiredAt = new Date();
119
+ pooled.usageCount++;
120
+ agent.status = 'busy';
121
+
122
+ this.emit('agent.acquired', { agentId: agent.id.id });
123
+
124
+ return agent;
125
+ }
126
+ }
127
+
128
+ // Pool exhausted
129
+ this.emit('pool.exhausted');
130
+ return undefined;
131
+ }
132
+
133
+ async release(agentId: string): Promise<void> {
134
+ const pooled = this.pooledAgents.get(agentId);
135
+ if (!pooled) {
136
+ return;
137
+ }
138
+
139
+ this.busy.delete(agentId);
140
+ this.available.add(agentId);
141
+ pooled.acquiredAt = undefined;
142
+ pooled.lastUsed = new Date();
143
+ pooled.agent.status = 'idle';
144
+ pooled.agent.currentTask = undefined;
145
+
146
+ this.emit('agent.released', { agentId });
147
+
148
+ // Check if we need to scale down
149
+ await this.checkScaling();
150
+ }
151
+
152
+ async add(agent: AgentState): Promise<void> {
153
+ if (this.pooledAgents.size >= this.config.maxSize) {
154
+ throw new Error(`Pool ${this.config.name} is at maximum capacity`);
155
+ }
156
+
157
+ const pooled: PooledAgent = {
158
+ agent,
159
+ lastUsed: new Date(),
160
+ usageCount: 0,
161
+ };
162
+
163
+ this.pooledAgents.set(agent.id.id, pooled);
164
+ this.available.add(agent.id.id);
165
+
166
+ this.emit('agent.added', { agentId: agent.id.id });
167
+ }
168
+
169
+ async remove(agentId: string): Promise<void> {
170
+ const pooled = this.pooledAgents.get(agentId);
171
+ if (!pooled) {
172
+ return;
173
+ }
174
+
175
+ this.pooledAgents.delete(agentId);
176
+ this.available.delete(agentId);
177
+ this.busy.delete(agentId);
178
+ pooled.agent.status = 'terminated';
179
+
180
+ this.emit('agent.removed', { agentId });
181
+ }
182
+
183
+ async scale(delta: number): Promise<void> {
184
+ const now = new Date();
185
+
186
+ // Check cooldown
187
+ if (this.lastScaleOperation) {
188
+ const timeSinceLastScale = now.getTime() - this.lastScaleOperation.getTime();
189
+ if (timeSinceLastScale < this.config.cooldownMs) {
190
+ return;
191
+ }
192
+ }
193
+
194
+ if (delta > 0) {
195
+ // Scale up - create agents in parallel
196
+ const targetSize = Math.min(
197
+ this.pooledAgents.size + delta,
198
+ this.config.maxSize
199
+ );
200
+ const toCreate = targetSize - this.pooledAgents.size;
201
+
202
+ const createPromises = Array.from(
203
+ { length: toCreate },
204
+ () => this.createPooledAgent()
205
+ );
206
+ await Promise.all(createPromises);
207
+
208
+ this.emit('pool.scaled_up', { added: toCreate });
209
+ } else if (delta < 0) {
210
+ // Scale down - remove agents in parallel
211
+ const targetSize = Math.max(
212
+ this.pooledAgents.size + delta,
213
+ this.config.minSize
214
+ );
215
+ const toRemove = this.pooledAgents.size - targetSize;
216
+
217
+ // Remove least recently used available agents
218
+ const sortedAvailable = Array.from(this.available)
219
+ .map(id => this.pooledAgents.get(id)!)
220
+ .filter(p => p !== undefined)
221
+ .sort((a, b) => a.lastUsed.getTime() - b.lastUsed.getTime());
222
+
223
+ const agentsToRemove = sortedAvailable.slice(0, toRemove);
224
+ await Promise.all(agentsToRemove.map(pooled => this.remove(pooled.agent.id.id)));
225
+
226
+ this.emit('pool.scaled_down', { removed: agentsToRemove.length });
227
+ }
228
+
229
+ this.lastScaleOperation = now;
230
+ }
231
+
232
+ getState(): AgentPoolState {
233
+ return {
234
+ id: `pool_${this.config.name}`,
235
+ config: { ...this.config },
236
+ agents: new Map(
237
+ Array.from(this.pooledAgents.entries()).map(
238
+ ([id, pooled]) => [id, pooled.agent]
239
+ )
240
+ ),
241
+ availableAgents: Array.from(this.available),
242
+ busyAgents: Array.from(this.busy),
243
+ pendingScale: this.pendingScale,
244
+ lastScaleOperation: this.lastScaleOperation,
245
+ };
246
+ }
247
+
248
+ getAvailableCount(): number {
249
+ return this.available.size;
250
+ }
251
+
252
+ getBusyCount(): number {
253
+ return this.busy.size;
254
+ }
255
+
256
+ getTotalCount(): number {
257
+ return this.pooledAgents.size;
258
+ }
259
+
260
+ getUtilization(): number {
261
+ if (this.pooledAgents.size === 0) return 0;
262
+ return this.busy.size / this.pooledAgents.size;
263
+ }
264
+
265
+ // ===== PRIVATE METHODS =====
266
+
267
+ private async createPooledAgent(): Promise<AgentState | undefined> {
268
+ if (this.pooledAgents.size >= this.config.maxSize) {
269
+ return undefined;
270
+ }
271
+
272
+ this.agentCounter++;
273
+ const agentId = `${this.config.name}_agent_${this.agentCounter}`;
274
+
275
+ const agent: AgentState = {
276
+ id: {
277
+ id: agentId,
278
+ swarmId: 'pool',
279
+ type: this.config.type,
280
+ instance: this.agentCounter,
281
+ },
282
+ name: `${this.config.name}-${this.agentCounter}`,
283
+ type: this.config.type,
284
+ status: 'idle',
285
+ capabilities: this.createDefaultCapabilities(),
286
+ metrics: this.createDefaultMetrics(),
287
+ workload: 0,
288
+ health: 1.0,
289
+ lastHeartbeat: new Date(),
290
+ connections: [],
291
+ };
292
+
293
+ const pooled: PooledAgent = {
294
+ agent,
295
+ lastUsed: new Date(),
296
+ usageCount: 0,
297
+ };
298
+
299
+ this.pooledAgents.set(agentId, pooled);
300
+ this.available.add(agentId);
301
+
302
+ this.emit('agent.created', { agentId });
303
+
304
+ return agent;
305
+ }
306
+
307
+ private createDefaultCapabilities(): AgentCapabilities {
308
+ return {
309
+ codeGeneration: true,
310
+ codeReview: true,
311
+ testing: true,
312
+ documentation: true,
313
+ research: true,
314
+ analysis: true,
315
+ coordination: this.config.type === 'coordinator',
316
+ languages: ['typescript', 'javascript', 'python'],
317
+ frameworks: ['node', 'deno', 'react'],
318
+ domains: ['development', 'testing', 'analysis'],
319
+ tools: ['git', 'npm', 'editor'],
320
+ maxConcurrentTasks: 3,
321
+ maxMemoryUsage: 512 * 1024 * 1024,
322
+ maxExecutionTime: SWARM_CONSTANTS.DEFAULT_TASK_TIMEOUT_MS,
323
+ reliability: 0.95,
324
+ speed: 1.0,
325
+ quality: 0.9,
326
+ };
327
+ }
328
+
329
+ private createDefaultMetrics(): AgentMetrics {
330
+ return {
331
+ tasksCompleted: 0,
332
+ tasksFailed: 0,
333
+ averageExecutionTime: 0,
334
+ successRate: 1.0,
335
+ cpuUsage: 0,
336
+ memoryUsage: 0,
337
+ messagesProcessed: 0,
338
+ lastActivity: new Date(),
339
+ responseTime: 0,
340
+ health: 1.0,
341
+ };
342
+ }
343
+
344
+ private async checkScaling(): Promise<void> {
345
+ const utilization = this.getUtilization();
346
+
347
+ if (utilization >= this.config.scaleUpThreshold &&
348
+ this.pooledAgents.size < this.config.maxSize) {
349
+ // Scale up by 1
350
+ this.pendingScale = 1;
351
+ await this.scale(1);
352
+ this.pendingScale = 0;
353
+ } else if (utilization <= this.config.scaleDownThreshold &&
354
+ this.pooledAgents.size > this.config.minSize) {
355
+ // Scale down by 1
356
+ this.pendingScale = -1;
357
+ await this.scale(-1);
358
+ this.pendingScale = 0;
359
+ }
360
+ }
361
+
362
+ private startHealthChecks(): void {
363
+ this.healthCheckInterval = setInterval(() => {
364
+ this.performHealthChecks();
365
+ }, this.config.healthCheckIntervalMs);
366
+ }
367
+
368
+ private performHealthChecks(): void {
369
+ const now = new Date();
370
+ const unhealthyThresholdMs = this.config.healthCheckIntervalMs * 3;
371
+
372
+ for (const [agentId, pooled] of this.pooledAgents) {
373
+ const timeSinceLastActivity = now.getTime() - pooled.agent.lastHeartbeat.getTime();
374
+
375
+ if (timeSinceLastActivity > unhealthyThresholdMs) {
376
+ // Agent is unhealthy
377
+ pooled.agent.health = Math.max(0, pooled.agent.health - 0.2);
378
+ pooled.agent.status = 'error';
379
+
380
+ this.emit('agent.unhealthy', { agentId, health: pooled.agent.health });
381
+
382
+ // If completely unhealthy, remove and replace
383
+ if (pooled.agent.health <= 0) {
384
+ this.replaceUnhealthyAgent(agentId);
385
+ }
386
+ } else {
387
+ // Update health positively
388
+ pooled.agent.health = Math.min(1.0, pooled.agent.health + 0.1);
389
+ pooled.agent.lastHeartbeat = now;
390
+ }
391
+ }
392
+ }
393
+
394
+ private async replaceUnhealthyAgent(agentId: string): Promise<void> {
395
+ const pooled = this.pooledAgents.get(agentId);
396
+ if (!pooled) return;
397
+
398
+ const wasBusy = this.busy.has(agentId);
399
+ await this.remove(agentId);
400
+
401
+ // Create replacement if below min size or was busy
402
+ if (this.pooledAgents.size < this.config.minSize || wasBusy) {
403
+ await this.createPooledAgent();
404
+ }
405
+
406
+ this.emit('agent.replaced', { oldAgentId: agentId });
407
+ }
408
+
409
+ // ===== UTILITY METHODS =====
410
+
411
+ getAgent(agentId: string): AgentState | undefined {
412
+ return this.pooledAgents.get(agentId)?.agent;
413
+ }
414
+
415
+ getAllAgents(): AgentState[] {
416
+ return Array.from(this.pooledAgents.values()).map(p => p.agent);
417
+ }
418
+
419
+ getAvailableAgents(): AgentState[] {
420
+ return Array.from(this.available)
421
+ .map(id => this.pooledAgents.get(id)?.agent)
422
+ .filter((a): a is AgentState => a !== undefined);
423
+ }
424
+
425
+ getBusyAgents(): AgentState[] {
426
+ return Array.from(this.busy)
427
+ .map(id => this.pooledAgents.get(id)?.agent)
428
+ .filter((a): a is AgentState => a !== undefined);
429
+ }
430
+
431
+ updateAgentHeartbeat(agentId: string): void {
432
+ const pooled = this.pooledAgents.get(agentId);
433
+ if (pooled) {
434
+ pooled.agent.lastHeartbeat = new Date();
435
+ pooled.agent.health = Math.min(1.0, pooled.agent.health + 0.05);
436
+ }
437
+ }
438
+
439
+ updateAgentMetrics(agentId: string, metrics: Partial<AgentMetrics>): void {
440
+ const pooled = this.pooledAgents.get(agentId);
441
+ if (pooled) {
442
+ pooled.agent.metrics = { ...pooled.agent.metrics, ...metrics };
443
+ pooled.agent.lastHeartbeat = new Date();
444
+ }
445
+ }
446
+
447
+ getPoolStats(): {
448
+ total: number;
449
+ available: number;
450
+ busy: number;
451
+ utilization: number;
452
+ avgHealth: number;
453
+ avgUsageCount: number;
454
+ } {
455
+ const agents = Array.from(this.pooledAgents.values());
456
+ const avgHealth = agents.length > 0
457
+ ? agents.reduce((sum, p) => sum + p.agent.health, 0) / agents.length
458
+ : 1.0;
459
+ const avgUsageCount = agents.length > 0
460
+ ? agents.reduce((sum, p) => sum + p.usageCount, 0) / agents.length
461
+ : 0;
462
+
463
+ return {
464
+ total: this.pooledAgents.size,
465
+ available: this.available.size,
466
+ busy: this.busy.size,
467
+ utilization: this.getUtilization(),
468
+ avgHealth,
469
+ avgUsageCount,
470
+ };
471
+ }
472
+ }
473
+
474
+ export function createAgentPool(config?: Partial<AgentPoolConfig>): AgentPool {
475
+ return new AgentPool(config);
476
+ }
@@ -0,0 +1,124 @@
1
+ /**
2
+ * Create Task Command - Application Layer (CQRS)
3
+ *
4
+ * Command for creating new tasks in the swarm.
5
+ *
6
+ * @module v3/swarm/application/commands
7
+ */
8
+
9
+ import { Task, TaskPriority, TaskProps } from '../../domain/entities/task.js';
10
+ import { ITaskRepository } from '../../domain/repositories/task-repository.interface.js';
11
+
12
+ /**
13
+ * Create Task Command Input
14
+ */
15
+ export interface CreateTaskInput {
16
+ title: string;
17
+ description: string;
18
+ type: string;
19
+ priority?: TaskPriority;
20
+ dependencies?: string[];
21
+ metadata?: Record<string, unknown>;
22
+ input?: unknown;
23
+ timeout?: number;
24
+ maxRetries?: number;
25
+ autoQueue?: boolean;
26
+ }
27
+
28
+ /**
29
+ * Create Task Command Result
30
+ */
31
+ export interface CreateTaskResult {
32
+ success: boolean;
33
+ taskId: string;
34
+ task: Task;
35
+ queuedAutomatically: boolean;
36
+ }
37
+
38
+ /**
39
+ * Create Task Command Handler
40
+ */
41
+ export class CreateTaskCommandHandler {
42
+ constructor(private readonly repository: ITaskRepository) {}
43
+
44
+ async execute(input: CreateTaskInput): Promise<CreateTaskResult> {
45
+ // Validate dependencies exist
46
+ if (input.dependencies && input.dependencies.length > 0) {
47
+ for (const depId of input.dependencies) {
48
+ const exists = await this.repository.exists(depId);
49
+ if (!exists) {
50
+ throw new Error(`Dependency task '${depId}' not found`);
51
+ }
52
+ }
53
+ }
54
+
55
+ // Create task
56
+ const task = Task.create({
57
+ title: input.title,
58
+ description: input.description,
59
+ type: input.type,
60
+ priority: input.priority,
61
+ dependencies: input.dependencies,
62
+ metadata: input.metadata,
63
+ input: input.input,
64
+ timeout: input.timeout,
65
+ maxRetries: input.maxRetries,
66
+ });
67
+
68
+ // Auto-queue if requested and no dependencies
69
+ let queuedAutomatically = false;
70
+ if (input.autoQueue && (!input.dependencies || input.dependencies.length === 0)) {
71
+ task.queue();
72
+ queuedAutomatically = true;
73
+ }
74
+
75
+ await this.repository.save(task);
76
+
77
+ return {
78
+ success: true,
79
+ taskId: task.id,
80
+ task,
81
+ queuedAutomatically,
82
+ };
83
+ }
84
+ }
85
+
86
+ /**
87
+ * Cancel Task Command Input
88
+ */
89
+ export interface CancelTaskInput {
90
+ taskId: string;
91
+ }
92
+
93
+ /**
94
+ * Cancel Task Command Result
95
+ */
96
+ export interface CancelTaskResult {
97
+ success: boolean;
98
+ taskId: string;
99
+ previousStatus: string;
100
+ }
101
+
102
+ /**
103
+ * Cancel Task Command Handler
104
+ */
105
+ export class CancelTaskCommandHandler {
106
+ constructor(private readonly repository: ITaskRepository) {}
107
+
108
+ async execute(input: CancelTaskInput): Promise<CancelTaskResult> {
109
+ const task = await this.repository.findById(input.taskId);
110
+ if (!task) {
111
+ throw new Error(`Task '${input.taskId}' not found`);
112
+ }
113
+
114
+ const previousStatus = task.status;
115
+ task.cancel();
116
+ await this.repository.save(task);
117
+
118
+ return {
119
+ success: true,
120
+ taskId: input.taskId,
121
+ previousStatus,
122
+ };
123
+ }
124
+ }
@@ -0,0 +1,122 @@
1
+ /**
2
+ * Spawn Agent Command - Application Layer (CQRS)
3
+ *
4
+ * Command for spawning a new agent in the swarm.
5
+ *
6
+ * @module v3/swarm/application/commands
7
+ */
8
+
9
+ import { Agent, AgentRole, AgentProps } from '../../domain/entities/agent.js';
10
+ import { IAgentRepository } from '../../domain/repositories/agent-repository.interface.js';
11
+
12
+ /**
13
+ * Spawn Agent Command Input
14
+ */
15
+ export interface SpawnAgentInput {
16
+ name: string;
17
+ role: AgentRole;
18
+ domain: string;
19
+ capabilities: string[];
20
+ parentId?: string;
21
+ metadata?: Record<string, unknown>;
22
+ maxConcurrentTasks?: number;
23
+ autoStart?: boolean;
24
+ }
25
+
26
+ /**
27
+ * Spawn Agent Command Result
28
+ */
29
+ export interface SpawnAgentResult {
30
+ success: boolean;
31
+ agentId: string;
32
+ agent: Agent;
33
+ startedAutomatically: boolean;
34
+ }
35
+
36
+ /**
37
+ * Spawn Agent Command Handler
38
+ */
39
+ export class SpawnAgentCommandHandler {
40
+ constructor(private readonly repository: IAgentRepository) {}
41
+
42
+ async execute(input: SpawnAgentInput): Promise<SpawnAgentResult> {
43
+ // Check if agent with same name exists
44
+ const existing = await this.repository.findByName(input.name);
45
+ if (existing) {
46
+ throw new Error(`Agent with name '${input.name}' already exists`);
47
+ }
48
+
49
+ // Create agent
50
+ const agent = Agent.create({
51
+ name: input.name,
52
+ role: input.role,
53
+ domain: input.domain,
54
+ capabilities: input.capabilities,
55
+ parentId: input.parentId,
56
+ metadata: input.metadata,
57
+ maxConcurrentTasks: input.maxConcurrentTasks,
58
+ });
59
+
60
+ // Auto-start if requested
61
+ let startedAutomatically = false;
62
+ if (input.autoStart) {
63
+ agent.start();
64
+ startedAutomatically = true;
65
+ }
66
+
67
+ await this.repository.save(agent);
68
+
69
+ return {
70
+ success: true,
71
+ agentId: agent.id,
72
+ agent,
73
+ startedAutomatically,
74
+ };
75
+ }
76
+ }
77
+
78
+ /**
79
+ * Terminate Agent Command Input
80
+ */
81
+ export interface TerminateAgentInput {
82
+ agentId: string;
83
+ force?: boolean;
84
+ }
85
+
86
+ /**
87
+ * Terminate Agent Command Result
88
+ */
89
+ export interface TerminateAgentResult {
90
+ success: boolean;
91
+ agentId: string;
92
+ tasksReassigned: number;
93
+ }
94
+
95
+ /**
96
+ * Terminate Agent Command Handler
97
+ */
98
+ export class TerminateAgentCommandHandler {
99
+ constructor(private readonly repository: IAgentRepository) {}
100
+
101
+ async execute(input: TerminateAgentInput): Promise<TerminateAgentResult> {
102
+ const agent = await this.repository.findById(input.agentId);
103
+ if (!agent) {
104
+ throw new Error(`Agent '${input.agentId}' not found`);
105
+ }
106
+
107
+ const currentTasks = agent.currentTaskCount;
108
+
109
+ if (currentTasks > 0 && !input.force) {
110
+ throw new Error(`Agent has ${currentTasks} active tasks. Use force=true to terminate anyway.`);
111
+ }
112
+
113
+ agent.terminate();
114
+ await this.repository.save(agent);
115
+
116
+ return {
117
+ success: true,
118
+ agentId: input.agentId,
119
+ tasksReassigned: currentTasks,
120
+ };
121
+ }
122
+ }