claude-flow
Version:
Enterprise-grade AI agent orchestration with ruv-swarm integration (Alpha Release)
240 lines (196 loc) • 7.52 kB
text/typescript
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();
}