@sparkleideas/integration 3.5.2-patch.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,933 @@
1
+ /**
2
+ * WorkerPool - Worker Pool Management
3
+ *
4
+ * Manages a collection of workers with intelligent routing,
5
+ * load balancing, and lifecycle management.
6
+ *
7
+ * Features:
8
+ * - Dynamic worker spawning and termination
9
+ * - Embedding-based task routing
10
+ * - Load balancing across workers
11
+ * - Health monitoring and auto-recovery
12
+ * - Type-safe worker registry
13
+ *
14
+ * Compatible with @sparkleideas/agentic-flow's worker pool patterns.
15
+ *
16
+ * @module v3/integration/worker-pool
17
+ * @version 3.0.0-alpha.1
18
+ */
19
+
20
+ import { EventEmitter } from 'events';
21
+ import {
22
+ WorkerBase,
23
+ WorkerConfig,
24
+ WorkerType,
25
+ WorkerMetrics,
26
+ WorkerHealth,
27
+ } from './worker-base.js';
28
+ import { SpecializedWorker, SpecializedWorkerConfig } from './specialized-worker.js';
29
+ import { LongRunningWorker, LongRunningWorkerConfig } from './long-running-worker.js';
30
+ import type { Task, TaskResult } from './agentic-flow-agent.js';
31
+
32
+ /**
33
+ * Worker pool configuration
34
+ */
35
+ export interface WorkerPoolConfig {
36
+ /** Pool identifier */
37
+ id?: string;
38
+ /** Pool name */
39
+ name?: string;
40
+ /** Minimum workers to maintain */
41
+ minWorkers?: number;
42
+ /** Maximum workers allowed */
43
+ maxWorkers?: number;
44
+ /** Default worker configuration */
45
+ defaultWorkerConfig?: Partial<WorkerConfig>;
46
+ /** Enable auto-scaling */
47
+ autoScale?: boolean;
48
+ /** Scale up threshold (0.0-1.0 utilization) */
49
+ scaleUpThreshold?: number;
50
+ /** Scale down threshold (0.0-1.0 utilization) */
51
+ scaleDownThreshold?: number;
52
+ /** Health check interval in milliseconds */
53
+ healthCheckInterval?: number;
54
+ /** Enable automatic health recovery */
55
+ autoRecover?: boolean;
56
+ /** Routing strategy */
57
+ routingStrategy?: RoutingStrategy;
58
+ /** Load balancing strategy */
59
+ loadBalancingStrategy?: LoadBalancingStrategy;
60
+ }
61
+
62
+ /**
63
+ * Task routing strategy
64
+ */
65
+ export type RoutingStrategy =
66
+ | 'round-robin'
67
+ | 'least-loaded'
68
+ | 'capability-match'
69
+ | 'embedding-similarity'
70
+ | 'priority-based'
71
+ | 'hybrid'
72
+ | 'custom';
73
+
74
+ /**
75
+ * Load balancing strategy
76
+ */
77
+ export type LoadBalancingStrategy =
78
+ | 'equal'
79
+ | 'weighted'
80
+ | 'adaptive'
81
+ | 'capacity-based';
82
+
83
+ /**
84
+ * Worker routing result
85
+ */
86
+ export interface RoutingResult {
87
+ /** Selected workers */
88
+ workers: WorkerBase[];
89
+ /** Routing scores for each worker */
90
+ scores: Map<string, number>;
91
+ /** Routing strategy used */
92
+ strategy: RoutingStrategy;
93
+ /** Routing metadata */
94
+ metadata: {
95
+ totalCandidates: number;
96
+ filtered: number;
97
+ matchThreshold: number;
98
+ };
99
+ }
100
+
101
+ /**
102
+ * Pool statistics
103
+ */
104
+ export interface PoolStats {
105
+ /** Pool identifier */
106
+ poolId: string;
107
+ /** Total workers */
108
+ totalWorkers: number;
109
+ /** Available workers */
110
+ availableWorkers: number;
111
+ /** Busy workers */
112
+ busyWorkers: number;
113
+ /** Unhealthy workers */
114
+ unhealthyWorkers: number;
115
+ /** Average utilization */
116
+ avgUtilization: number;
117
+ /** Average health score */
118
+ avgHealthScore: number;
119
+ /** Tasks processed */
120
+ tasksProcessed: number;
121
+ /** Tasks failed */
122
+ tasksFailed: number;
123
+ /** Average task duration */
124
+ avgTaskDuration: number;
125
+ /** Worker types breakdown */
126
+ workerTypes: Record<WorkerType, number>;
127
+ /** Uptime in milliseconds */
128
+ uptime: number;
129
+ }
130
+
131
+ /**
132
+ * Worker spawn options
133
+ */
134
+ export interface SpawnOptions {
135
+ /** Immediately initialize the worker */
136
+ initialize?: boolean;
137
+ /** Replace existing worker with same ID */
138
+ replace?: boolean;
139
+ /** Worker priority in pool */
140
+ poolPriority?: number;
141
+ }
142
+
143
+ /**
144
+ * WorkerPool - Manages a collection of workers
145
+ *
146
+ * Usage:
147
+ * ```typescript
148
+ * const pool = new WorkerPool({
149
+ * name: 'main-pool',
150
+ * minWorkers: 2,
151
+ * maxWorkers: 10,
152
+ * autoScale: true,
153
+ * routingStrategy: 'embedding-similarity',
154
+ * });
155
+ *
156
+ * await pool.initialize();
157
+ *
158
+ * // Spawn workers
159
+ * pool.spawn({
160
+ * id: 'coder-1',
161
+ * type: 'coder',
162
+ * capabilities: ['typescript', 'code-generation'],
163
+ * });
164
+ *
165
+ * // Route a task
166
+ * const workers = pool.routeTask(task, 3);
167
+ * for (const worker of workers) {
168
+ * const result = await worker.executeTask(task);
169
+ * }
170
+ * ```
171
+ */
172
+ export class WorkerPool extends EventEmitter {
173
+ /** Pool identifier */
174
+ readonly id: string;
175
+
176
+ /** Pool name */
177
+ readonly name: string;
178
+
179
+ /** Worker registry */
180
+ workers: Map<string, WorkerBase>;
181
+
182
+ /** Pool configuration */
183
+ protected config: WorkerPoolConfig;
184
+
185
+ /** Pool initialized state */
186
+ protected initialized: boolean = false;
187
+
188
+ /** Health check timer */
189
+ private healthCheckTimer: NodeJS.Timeout | null = null;
190
+
191
+ /** Pool creation time */
192
+ private createdAt: number;
193
+
194
+ /** Round-robin index */
195
+ private roundRobinIndex: number = 0;
196
+
197
+ /** Pool-level metrics */
198
+ private poolMetrics: {
199
+ tasksProcessed: number;
200
+ tasksFailed: number;
201
+ totalTaskDuration: number;
202
+ };
203
+
204
+ /**
205
+ * Create a new WorkerPool instance
206
+ *
207
+ * @param config - Pool configuration
208
+ */
209
+ constructor(config: WorkerPoolConfig = {}) {
210
+ super();
211
+
212
+ this.id = config.id || `pool_${Date.now()}`;
213
+ this.name = config.name || 'default-pool';
214
+ this.workers = new Map();
215
+ this.createdAt = Date.now();
216
+
217
+ this.config = {
218
+ minWorkers: config.minWorkers ?? 1,
219
+ maxWorkers: config.maxWorkers ?? 10,
220
+ defaultWorkerConfig: config.defaultWorkerConfig ?? {},
221
+ autoScale: config.autoScale ?? true,
222
+ scaleUpThreshold: config.scaleUpThreshold ?? 0.8,
223
+ scaleDownThreshold: config.scaleDownThreshold ?? 0.2,
224
+ healthCheckInterval: config.healthCheckInterval ?? 30000,
225
+ autoRecover: config.autoRecover ?? true,
226
+ routingStrategy: config.routingStrategy ?? 'hybrid',
227
+ loadBalancingStrategy: config.loadBalancingStrategy ?? 'adaptive',
228
+ ...config,
229
+ };
230
+
231
+ this.poolMetrics = {
232
+ tasksProcessed: 0,
233
+ tasksFailed: 0,
234
+ totalTaskDuration: 0,
235
+ };
236
+
237
+ this.emit('pool-created', { poolId: this.id, name: this.name });
238
+ }
239
+
240
+ /**
241
+ * Initialize the pool
242
+ */
243
+ async initialize(): Promise<void> {
244
+ if (this.initialized) {
245
+ return;
246
+ }
247
+
248
+ this.emit('pool-initializing', { poolId: this.id });
249
+
250
+ // Initialize all existing workers
251
+ const initPromises = Array.from(this.workers.values()).map((worker) =>
252
+ worker.initialize().catch((error) => {
253
+ this.emit('worker-init-failed', {
254
+ poolId: this.id,
255
+ workerId: worker.id,
256
+ error,
257
+ });
258
+ })
259
+ );
260
+
261
+ await Promise.all(initPromises);
262
+
263
+ // Start health checks
264
+ this.startHealthChecks();
265
+
266
+ this.initialized = true;
267
+
268
+ this.emit('pool-initialized', {
269
+ poolId: this.id,
270
+ workerCount: this.workers.size,
271
+ });
272
+ }
273
+
274
+ /**
275
+ * Shutdown the pool
276
+ */
277
+ async shutdown(): Promise<void> {
278
+ this.emit('pool-shutting-down', { poolId: this.id });
279
+
280
+ // Stop health checks
281
+ this.stopHealthChecks();
282
+
283
+ // Shutdown all workers
284
+ const shutdownPromises = Array.from(this.workers.values()).map((worker) =>
285
+ worker.shutdown().catch((error) => {
286
+ this.emit('worker-shutdown-failed', {
287
+ poolId: this.id,
288
+ workerId: worker.id,
289
+ error,
290
+ });
291
+ })
292
+ );
293
+
294
+ await Promise.all(shutdownPromises);
295
+
296
+ this.workers.clear();
297
+ this.initialized = false;
298
+
299
+ this.emit('pool-shutdown', { poolId: this.id });
300
+ }
301
+
302
+ /**
303
+ * Spawn a new worker in the pool
304
+ *
305
+ * @param config - Worker configuration
306
+ * @param options - Spawn options
307
+ * @returns Created worker
308
+ */
309
+ spawn(
310
+ config: WorkerConfig | SpecializedWorkerConfig | LongRunningWorkerConfig,
311
+ options: SpawnOptions = {}
312
+ ): WorkerBase {
313
+ // Check capacity
314
+ if (this.workers.size >= this.config.maxWorkers! && !options.replace) {
315
+ throw new Error(
316
+ `Pool ${this.id} at maximum capacity (${this.config.maxWorkers} workers)`
317
+ );
318
+ }
319
+
320
+ // Handle replacement
321
+ if (this.workers.has(config.id)) {
322
+ if (options.replace) {
323
+ this.terminate(config.id);
324
+ } else {
325
+ throw new Error(`Worker ${config.id} already exists in pool`);
326
+ }
327
+ }
328
+
329
+ // Merge with default config
330
+ const mergedConfig = {
331
+ ...this.config.defaultWorkerConfig,
332
+ ...config,
333
+ };
334
+
335
+ // Create appropriate worker type
336
+ let worker: WorkerBase;
337
+
338
+ if ('domain' in config) {
339
+ worker = new SpecializedWorker(config as SpecializedWorkerConfig);
340
+ } else if ('checkpointInterval' in config) {
341
+ worker = new LongRunningWorker(config as LongRunningWorkerConfig);
342
+ } else {
343
+ // Create a concrete implementation for generic workers
344
+ worker = new GenericWorker(mergedConfig);
345
+ }
346
+
347
+ // Add to registry
348
+ this.workers.set(worker.id, worker);
349
+
350
+ // Forward worker events
351
+ this.forwardWorkerEvents(worker);
352
+
353
+ // Initialize if requested and pool is initialized
354
+ if (options.initialize && this.initialized) {
355
+ worker.initialize().catch((error) => {
356
+ this.emit('worker-init-failed', {
357
+ poolId: this.id,
358
+ workerId: worker.id,
359
+ error,
360
+ });
361
+ });
362
+ }
363
+
364
+ this.emit('worker-spawned', {
365
+ poolId: this.id,
366
+ workerId: worker.id,
367
+ type: worker.type,
368
+ });
369
+
370
+ return worker;
371
+ }
372
+
373
+ /**
374
+ * Terminate a worker
375
+ *
376
+ * @param workerId - Worker ID to terminate
377
+ * @returns True if worker was terminated
378
+ */
379
+ terminate(workerId: string): boolean {
380
+ const worker = this.workers.get(workerId);
381
+
382
+ if (!worker) {
383
+ return false;
384
+ }
385
+
386
+ // Shutdown worker
387
+ worker.shutdown().catch((error) => {
388
+ this.emit('worker-shutdown-failed', {
389
+ poolId: this.id,
390
+ workerId,
391
+ error,
392
+ });
393
+ });
394
+
395
+ // Remove from registry
396
+ this.workers.delete(workerId);
397
+
398
+ this.emit('worker-terminated', {
399
+ poolId: this.id,
400
+ workerId,
401
+ });
402
+
403
+ return true;
404
+ }
405
+
406
+ /**
407
+ * Route a task to the best workers
408
+ *
409
+ * @param task - Task to route
410
+ * @param topK - Number of workers to return (default: 1)
411
+ * @returns Array of best-matched workers
412
+ */
413
+ routeTask(task: Task, topK: number = 1): WorkerBase[] {
414
+ const result = this.routeTaskWithDetails(task, topK);
415
+ return result.workers;
416
+ }
417
+
418
+ /**
419
+ * Route a task with detailed scoring information
420
+ *
421
+ * @param task - Task to route
422
+ * @param topK - Number of workers to return
423
+ * @returns Detailed routing result
424
+ */
425
+ routeTaskWithDetails(task: Task, topK: number = 1): RoutingResult {
426
+ const availableWorkers = this.getAvailableWorkers();
427
+ const scores = new Map<string, number>();
428
+
429
+ // Score each worker
430
+ for (const worker of availableWorkers) {
431
+ const score = this.scoreWorkerForTask(worker, task);
432
+ scores.set(worker.id, score);
433
+ }
434
+
435
+ // Sort by score and take top K
436
+ const sortedWorkers = availableWorkers
437
+ .sort((a, b) => (scores.get(b.id) || 0) - (scores.get(a.id) || 0))
438
+ .slice(0, topK);
439
+
440
+ return {
441
+ workers: sortedWorkers,
442
+ scores,
443
+ strategy: this.config.routingStrategy!,
444
+ metadata: {
445
+ totalCandidates: availableWorkers.length,
446
+ filtered: availableWorkers.length - sortedWorkers.length,
447
+ matchThreshold: 0.5,
448
+ },
449
+ };
450
+ }
451
+
452
+ /**
453
+ * Balance load across workers
454
+ *
455
+ * Redistributes tasks or adjusts worker priorities based on
456
+ * the configured load balancing strategy.
457
+ */
458
+ balanceLoad(): void {
459
+ const stats = this.getStats();
460
+
461
+ if (stats.avgUtilization > this.config.scaleUpThreshold! && this.config.autoScale) {
462
+ this.scaleUp();
463
+ } else if (
464
+ stats.avgUtilization < this.config.scaleDownThreshold! &&
465
+ this.config.autoScale
466
+ ) {
467
+ this.scaleDown();
468
+ }
469
+
470
+ this.emit('load-balanced', {
471
+ poolId: this.id,
472
+ avgUtilization: stats.avgUtilization,
473
+ workerCount: stats.totalWorkers,
474
+ });
475
+ }
476
+
477
+ /**
478
+ * Get a worker by ID
479
+ *
480
+ * @param workerId - Worker ID
481
+ * @returns Worker or undefined
482
+ */
483
+ getWorker(workerId: string): WorkerBase | undefined {
484
+ return this.workers.get(workerId);
485
+ }
486
+
487
+ /**
488
+ * Get all workers
489
+ */
490
+ getAllWorkers(): WorkerBase[] {
491
+ return Array.from(this.workers.values());
492
+ }
493
+
494
+ /**
495
+ * Get available workers (not at capacity)
496
+ */
497
+ getAvailableWorkers(): WorkerBase[] {
498
+ return Array.from(this.workers.values()).filter((worker) =>
499
+ worker.isAvailable()
500
+ );
501
+ }
502
+
503
+ /**
504
+ * Get workers by type
505
+ *
506
+ * @param type - Worker type to filter
507
+ */
508
+ getWorkersByType(type: WorkerType): WorkerBase[] {
509
+ return Array.from(this.workers.values()).filter(
510
+ (worker) => worker.type === type
511
+ );
512
+ }
513
+
514
+ /**
515
+ * Get workers by capability
516
+ *
517
+ * @param capability - Required capability
518
+ */
519
+ getWorkersByCapability(capability: string): WorkerBase[] {
520
+ return Array.from(this.workers.values()).filter((worker) =>
521
+ worker.capabilities.includes(capability)
522
+ );
523
+ }
524
+
525
+ /**
526
+ * Get pool statistics
527
+ */
528
+ getStats(): PoolStats {
529
+ const workers = Array.from(this.workers.values());
530
+ const availableWorkers = workers.filter((w) => w.isAvailable());
531
+ const busyWorkers = workers.filter(
532
+ (w) => w.status === 'busy' || w.load > 0.9
533
+ );
534
+ const unhealthyWorkers = workers.filter(
535
+ (w) => w.getHealth().status === 'unhealthy'
536
+ );
537
+
538
+ // Calculate averages
539
+ const avgUtilization =
540
+ workers.length > 0
541
+ ? workers.reduce((sum, w) => sum + w.load, 0) / workers.length
542
+ : 0;
543
+
544
+ const avgHealthScore =
545
+ workers.length > 0
546
+ ? workers.reduce((sum, w) => sum + w.getHealth().score, 0) / workers.length
547
+ : 1;
548
+
549
+ const avgTaskDuration =
550
+ this.poolMetrics.tasksProcessed > 0
551
+ ? this.poolMetrics.totalTaskDuration / this.poolMetrics.tasksProcessed
552
+ : 0;
553
+
554
+ // Worker type breakdown
555
+ const workerTypes: Record<WorkerType, number> = {
556
+ coder: 0,
557
+ reviewer: 0,
558
+ tester: 0,
559
+ researcher: 0,
560
+ planner: 0,
561
+ architect: 0,
562
+ coordinator: 0,
563
+ security: 0,
564
+ performance: 0,
565
+ specialized: 0,
566
+ 'long-running': 0,
567
+ generic: 0,
568
+ };
569
+
570
+ for (const worker of workers) {
571
+ if (worker.type in workerTypes) {
572
+ workerTypes[worker.type as WorkerType]++;
573
+ }
574
+ }
575
+
576
+ return {
577
+ poolId: this.id,
578
+ totalWorkers: workers.length,
579
+ availableWorkers: availableWorkers.length,
580
+ busyWorkers: busyWorkers.length,
581
+ unhealthyWorkers: unhealthyWorkers.length,
582
+ avgUtilization,
583
+ avgHealthScore,
584
+ tasksProcessed: this.poolMetrics.tasksProcessed,
585
+ tasksFailed: this.poolMetrics.tasksFailed,
586
+ avgTaskDuration,
587
+ workerTypes,
588
+ uptime: Date.now() - this.createdAt,
589
+ };
590
+ }
591
+
592
+ /**
593
+ * Execute a task on the best available worker
594
+ *
595
+ * @param task - Task to execute
596
+ * @returns Task result
597
+ */
598
+ async executeTask(task: Task): Promise<TaskResult> {
599
+ const workers = this.routeTask(task, 1);
600
+
601
+ if (workers.length === 0) {
602
+ throw new Error('No available workers for task');
603
+ }
604
+
605
+ const worker = workers[0];
606
+ const startTime = Date.now();
607
+
608
+ try {
609
+ const result = await worker.executeTask(task);
610
+
611
+ // Update pool metrics
612
+ this.poolMetrics.tasksProcessed++;
613
+ this.poolMetrics.totalTaskDuration += result.duration;
614
+
615
+ if (!result.success) {
616
+ this.poolMetrics.tasksFailed++;
617
+ }
618
+
619
+ return result;
620
+ } catch (error) {
621
+ this.poolMetrics.tasksProcessed++;
622
+ this.poolMetrics.tasksFailed++;
623
+
624
+ return {
625
+ taskId: task.id,
626
+ success: false,
627
+ error: error as Error,
628
+ duration: Date.now() - startTime,
629
+ };
630
+ }
631
+ }
632
+
633
+ // ===== Private Methods =====
634
+
635
+ /**
636
+ * Score a worker for a specific task
637
+ */
638
+ private scoreWorkerForTask(worker: WorkerBase, task: Task): number {
639
+ let score = 0;
640
+
641
+ switch (this.config.routingStrategy) {
642
+ case 'round-robin':
643
+ score = 1;
644
+ break;
645
+
646
+ case 'least-loaded':
647
+ score = 1 - worker.load;
648
+ break;
649
+
650
+ case 'capability-match':
651
+ score = this.calculateCapabilityScore(worker, task);
652
+ break;
653
+
654
+ case 'embedding-similarity':
655
+ score = this.calculateEmbeddingScore(worker, task);
656
+ break;
657
+
658
+ case 'priority-based':
659
+ score = (worker.config.priority || 5) / 10;
660
+ break;
661
+
662
+ case 'hybrid':
663
+ default:
664
+ // Weighted combination
665
+ const loadScore = (1 - worker.load) * 0.3;
666
+ const capabilityScore = this.calculateCapabilityScore(worker, task) * 0.3;
667
+ const embeddingScore = this.calculateEmbeddingScore(worker, task) * 0.25;
668
+ const healthScore = worker.getHealth().score * 0.15;
669
+ score = loadScore + capabilityScore + embeddingScore + healthScore;
670
+ break;
671
+ }
672
+
673
+ return score;
674
+ }
675
+
676
+ /**
677
+ * Calculate capability match score
678
+ */
679
+ private calculateCapabilityScore(worker: WorkerBase, task: Task): number {
680
+ const requiredCapabilities = this.extractRequiredCapabilities(task);
681
+
682
+ if (requiredCapabilities.length === 0) {
683
+ return 1;
684
+ }
685
+
686
+ const matched = requiredCapabilities.filter((cap) =>
687
+ worker.capabilities.includes(cap)
688
+ );
689
+
690
+ return matched.length / requiredCapabilities.length;
691
+ }
692
+
693
+ /**
694
+ * Calculate embedding similarity score
695
+ */
696
+ private calculateEmbeddingScore(worker: WorkerBase, task: Task): number {
697
+ if (worker instanceof SpecializedWorker) {
698
+ const matchResult = worker.matchTask(task);
699
+ return matchResult.breakdown.embeddingScore;
700
+ }
701
+
702
+ // Generate task embedding and calculate similarity
703
+ const taskEmbedding = this.generateTaskEmbedding(task);
704
+ return worker.calculateSimilarity(taskEmbedding);
705
+ }
706
+
707
+ /**
708
+ * Extract required capabilities from task
709
+ */
710
+ private extractRequiredCapabilities(task: Task): string[] {
711
+ if (task.metadata?.requiredCapabilities) {
712
+ return task.metadata.requiredCapabilities as string[];
713
+ }
714
+ return [];
715
+ }
716
+
717
+ /**
718
+ * Generate a simple task embedding
719
+ */
720
+ private generateTaskEmbedding(task: Task): Float32Array {
721
+ const dimension = 64;
722
+ const embedding = new Float32Array(dimension);
723
+
724
+ // Simple hash-based embedding from task description
725
+ const text = `${task.type} ${task.description}`;
726
+ let hash = 0;
727
+ for (let i = 0; i < text.length; i++) {
728
+ hash = ((hash << 5) - hash) + text.charCodeAt(i);
729
+ hash = hash & hash;
730
+ }
731
+
732
+ for (let i = 0; i < dimension; i++) {
733
+ embedding[i] = ((hash >> (i % 32)) & 1) ? 0.1 : -0.1;
734
+ }
735
+
736
+ // Normalize
737
+ const norm = Math.sqrt(embedding.reduce((sum, v) => sum + v * v, 0));
738
+ if (norm > 0) {
739
+ for (let i = 0; i < dimension; i++) {
740
+ embedding[i] /= norm;
741
+ }
742
+ }
743
+
744
+ return embedding;
745
+ }
746
+
747
+ /**
748
+ * Scale up the pool
749
+ */
750
+ private scaleUp(): void {
751
+ if (this.workers.size >= this.config.maxWorkers!) {
752
+ return;
753
+ }
754
+
755
+ // Spawn a new generic worker
756
+ const worker = this.spawn({
757
+ id: `auto-worker-${Date.now()}`,
758
+ type: 'generic',
759
+ capabilities: ['general'],
760
+ ...this.config.defaultWorkerConfig,
761
+ });
762
+
763
+ this.emit('pool-scaled-up', {
764
+ poolId: this.id,
765
+ newWorkerId: worker.id,
766
+ workerCount: this.workers.size,
767
+ });
768
+ }
769
+
770
+ /**
771
+ * Scale down the pool
772
+ */
773
+ private scaleDown(): void {
774
+ if (this.workers.size <= this.config.minWorkers!) {
775
+ return;
776
+ }
777
+
778
+ // Find least utilized worker
779
+ const workers = Array.from(this.workers.values())
780
+ .filter((w) => w.status === 'idle')
781
+ .sort((a, b) => a.load - b.load);
782
+
783
+ if (workers.length > 0) {
784
+ const worker = workers[0];
785
+ this.terminate(worker.id);
786
+
787
+ this.emit('pool-scaled-down', {
788
+ poolId: this.id,
789
+ removedWorkerId: worker.id,
790
+ workerCount: this.workers.size,
791
+ });
792
+ }
793
+ }
794
+
795
+ /**
796
+ * Start health check timer
797
+ */
798
+ private startHealthChecks(): void {
799
+ if (this.config.healthCheckInterval! <= 0) {
800
+ return;
801
+ }
802
+
803
+ this.healthCheckTimer = setInterval(() => {
804
+ this.performHealthChecks();
805
+ }, this.config.healthCheckInterval!);
806
+ }
807
+
808
+ /**
809
+ * Stop health check timer
810
+ */
811
+ private stopHealthChecks(): void {
812
+ if (this.healthCheckTimer) {
813
+ clearInterval(this.healthCheckTimer);
814
+ this.healthCheckTimer = null;
815
+ }
816
+ }
817
+
818
+ /**
819
+ * Perform health checks on all workers
820
+ */
821
+ private performHealthChecks(): void {
822
+ for (const worker of Array.from(this.workers.values())) {
823
+ const health = worker.getHealth();
824
+
825
+ if (health.status === 'unhealthy' && this.config.autoRecover) {
826
+ this.recoverWorker(worker);
827
+ }
828
+
829
+ this.emit('worker-health-check', {
830
+ poolId: this.id,
831
+ workerId: worker.id,
832
+ health,
833
+ });
834
+ }
835
+
836
+ // Balance load after health checks
837
+ this.balanceLoad();
838
+ }
839
+
840
+ /**
841
+ * Attempt to recover an unhealthy worker
842
+ */
843
+ private async recoverWorker(worker: WorkerBase): Promise<void> {
844
+ this.emit('worker-recovering', {
845
+ poolId: this.id,
846
+ workerId: worker.id,
847
+ });
848
+
849
+ try {
850
+ // Terminate and respawn
851
+ const config = { ...worker.config };
852
+ this.terminate(worker.id);
853
+
854
+ const newWorker = this.spawn(config, { initialize: true });
855
+
856
+ this.emit('worker-recovered', {
857
+ poolId: this.id,
858
+ oldWorkerId: worker.id,
859
+ newWorkerId: newWorker.id,
860
+ });
861
+ } catch (error) {
862
+ this.emit('worker-recovery-failed', {
863
+ poolId: this.id,
864
+ workerId: worker.id,
865
+ error: error as Error,
866
+ });
867
+ }
868
+ }
869
+
870
+ /**
871
+ * Forward worker events to pool
872
+ */
873
+ private forwardWorkerEvents(worker: WorkerBase): void {
874
+ worker.on('task-started', (data) => {
875
+ this.emit('worker-task-started', { poolId: this.id, ...data });
876
+ });
877
+
878
+ worker.on('task-completed', (data) => {
879
+ this.emit('worker-task-completed', { poolId: this.id, ...data });
880
+ });
881
+
882
+ worker.on('task-failed', (data) => {
883
+ this.emit('worker-task-failed', { poolId: this.id, ...data });
884
+ });
885
+
886
+ worker.on('load-updated', (data) => {
887
+ this.emit('worker-load-updated', { poolId: this.id, ...data });
888
+ });
889
+ }
890
+ }
891
+
892
+ /**
893
+ * Generic worker implementation for pool spawning
894
+ */
895
+ class GenericWorker extends WorkerBase {
896
+ async execute(task: Task): Promise<import('./worker-base.js').AgentOutput> {
897
+ // Simple execution that returns processed task info
898
+ return {
899
+ content: {
900
+ taskId: task.id,
901
+ processed: true,
902
+ workerId: this.id,
903
+ timestamp: Date.now(),
904
+ },
905
+ success: true,
906
+ duration: 0,
907
+ };
908
+ }
909
+ }
910
+
911
+ /**
912
+ * Create a worker pool with the given configuration
913
+ *
914
+ * @param config - Pool configuration
915
+ * @returns Configured WorkerPool
916
+ */
917
+ export function createWorkerPool(config: WorkerPoolConfig = {}): WorkerPool {
918
+ return new WorkerPool(config);
919
+ }
920
+
921
+ /**
922
+ * Create and initialize a worker pool
923
+ *
924
+ * @param config - Pool configuration
925
+ * @returns Initialized WorkerPool
926
+ */
927
+ export async function createAndInitializeWorkerPool(
928
+ config: WorkerPoolConfig = {}
929
+ ): Promise<WorkerPool> {
930
+ const pool = new WorkerPool(config);
931
+ await pool.initialize();
932
+ return pool;
933
+ }