UNPKG

claude-flow

Version:

Enterprise-grade AI agent orchestration with ruv-swarm integration (Alpha Release)

240 lines (196 loc) 7.52 kB
import { dirname } from 'node:path'; import { fileURLToPath } from 'node:url'; const __dirname = dirname(fileURLToPath(import.meta.url)); import { getErrorMessage } from '../utils/error-handler.js'; import * as fs from 'fs/promises'; import * as path from 'path'; import { Worker } from 'worker_threads'; import { PromptCopier } from './prompt-copier.js'; import type { CopyOptions, CopyResult, FileInfo } from './prompt-copier.js'; import { logger } from '../core/logger.js'; interface WorkerPool { workers: Worker[]; busy: Set<number>; queue: Array<() => void>; } export class EnhancedPromptCopier extends PromptCopier { private workerPool?: WorkerPool; private workerResults: Map<string, any> = new Map(); constructor(options: CopyOptions) { super(options); } protected async copyFilesParallel(): Promise<void> { const workerCount = Math.min((this as any).options.maxWorkers, (this as any).fileQueue.length); // Initialize worker pool this.workerPool = await this.initializeWorkerPool(workerCount); try { // Process files using worker pool await this.processWithWorkerPool(); } finally { // Cleanup workers await this.terminateWorkers(); } } private async initializeWorkerPool(workerCount: number): Promise<WorkerPool> { const workers: Worker[] = []; const pool: WorkerPool = { workers, busy: new Set(), queue: [] }; // Create workers for (let i = 0; i < workerCount; i++) { const worker = new Worker( path.join(__dirname, 'workers', 'copy-worker.js'), { workerData: { workerId: i } } ); // Setup worker message handler worker.on('message', (result) => { this.handleWorkerResult(result, i, pool); }); worker.on('error', (error) => { logger.error(`Worker ${i} error:`, error); (this as any).errors.push({ file: 'worker', error: (error instanceof Error ? error.message : String(error)), phase: 'write' }); }); workers.push(worker); } return pool; } private async processWithWorkerPool(): Promise<void> { const chunkSize = Math.max(1, Math.floor((this as any).fileQueue.length / this.workerPool!.workers.length / 2)); const chunks: FileInfo[][] = []; // Create chunks for better distribution for (let i = 0; i < (this as any).fileQueue.length; i += chunkSize) { chunks.push((this as any).fileQueue.slice(i, i + chunkSize)); } // Process chunks const promises: Promise<void>[] = []; for (const chunk of chunks) { promises.push(this.processChunkWithWorker(chunk)); } await Promise.all(promises); } private async processChunkWithWorker(chunk: FileInfo[]): Promise<void> { return new Promise((resolve, reject) => { const pool = this.workerPool!; const tryAssignWork = () => { // Find available worker const availableWorkerIndex = pool.workers.findIndex((_, index) => !pool.busy.has(index)); if (availableWorkerIndex === -1) { // No workers available, queue the work pool.queue.push(tryAssignWork); return; } // Mark worker as busy pool.busy.add(availableWorkerIndex); // Prepare worker data const workerData = { files: chunk.map(file => ({ sourcePath: file.path, destPath: path.join((this as any).options.destination, file.relativePath), permissions: (this as any).options.preservePermissions ? file.permissions : undefined, verify: (this as any).options.verify })), workerId: availableWorkerIndex }; let remainingFiles = chunk.length; const chunkResults: any[] = []; // Setup temporary message handler for this chunk const messageHandler = (result: any) => { chunkResults.push(result); remainingFiles--; if (remainingFiles === 0) { // Chunk complete pool.workers[availableWorkerIndex].off('message', messageHandler); pool.busy.delete(availableWorkerIndex); // Process next queued work if (pool.queue.length > 0) { const nextWork = pool.queue.shift()!; nextWork(); } // Process results this.processChunkResults(chunk, chunkResults); resolve(); } }; pool.workers[availableWorkerIndex].on('message', messageHandler); pool.workers[availableWorkerIndex].postMessage(workerData); }; tryAssignWork(); }); } private processChunkResults(chunk: FileInfo[], results: any[]): void { for (const result of results) { if (result.success) { (this as any).copiedFiles.add(result.file); if (result.hash) { this.workerResults.set(result.file, { hash: result.hash }); } } else { (this as any).errors.push({ file: result.file, error: result.error, phase: 'write' }); } } this.reportProgress((this as any).copiedFiles.size); } private handleWorkerResult(result: any, workerId: number, pool: WorkerPool): void { // This is a fallback handler, actual handling happens in processChunkWithWorker logger.debug(`Worker ${workerId} result:`, result); } private async terminateWorkers(): Promise<void> { if (!this.workerPool) return; const terminationPromises = this.workerPool.workers.map(worker => worker.terminate() ); await Promise.all(terminationPromises); this.workerPool = undefined; } // Override verification to use worker results protected async verifyFiles(): Promise<void> { logger.info('Verifying copied files...'); for (const file of (this as any).fileQueue) { if (!(this as any).copiedFiles.has(file.path)) continue; try { const destPath = path.join((this as any).options.destination, file.relativePath); // Verify file exists if (!await (this as any).fileExists(destPath)) { throw new Error('Destination file not found'); } // Verify size const destStats = await fs.stat(destPath); const sourceStats = await fs.stat(file.path); if (destStats.size !== sourceStats.size) { throw new Error(`Size mismatch: ${destStats.size} != ${sourceStats.size}`); } // Use hash from worker if available const workerResult = this.workerResults.get(file.path); if (workerResult?.hash) { const sourceHash = await (this as any).calculateFileHash(file.path); if (sourceHash !== workerResult.hash) { throw new Error(`Hash mismatch: ${sourceHash} != ${workerResult.hash}`); } } } catch (error) { (this as any).errors.push({ file: file.path, error: (error instanceof Error ? error.message : String(error)), phase: 'verify' }); } } } } // Export enhanced copy function export async function copyPromptsEnhanced(options: CopyOptions): Promise<CopyResult> { const copier = new EnhancedPromptCopier(options); return copier.copy(); }