claude-coordination-system
Version:
π€ Multi-Claude Parallel Processing Coordination System - Organize multiple Claude AI instances to work together seamlessly on complex development tasks
909 lines (745 loc) β’ 26.1 kB
JavaScript
/**
* Multi-Claude Worker Core Implementation
* Individual worker process that executes tasks with coordination
*/
const fs = require('fs-extra');
const path = require('path');
const { exec, spawn } = require('child_process');
const { promisify } = require('util');
const chalk = require('chalk');
const EventEmitter = require('events');
const MemoryManager = require('./memory-manager');
const { SecurityManager, SecurityError } = require('./security-manager');
const { logWorker, logError, logPerformance } = require('./development-logger');
const execAsync = promisify(exec);
class WorkerCore extends EventEmitter {
constructor(workerId, groupId, projectRoot, options = {}) {
super();
this.workerId = workerId;
this.groupId = groupId;
this.projectRoot = projectRoot;
this.coordinationDir = path.join(projectRoot, '.claude-coord');
this.stateFile = path.join(this.coordinationDir, 'system-state.json');
this.options = {
verbose: false,
dryRun: false,
maxRetries: 3,
taskTimeout: 300000, // 5 minutes per task
heartbeatInterval: 15000, // 15 seconds
...options
};
this.isRunning = false;
this.currentTask = null;
this.heartbeatTimer = null;
this.acquiredLocks = [];
// Initialize memory manager
this.memoryManager = new MemoryManager({
maxMemoryMB: options.maxMemoryMB || 256, // 256MB default per worker
checkInterval: 30000, // 30s check interval
warningThreshold: 0.8, // 80% warning
criticalThreshold: 0.9 // 90% critical
});
// Initialize security manager
this.securityManager = new SecurityManager(projectRoot, {
maxPathDepth: 10,
allowedCommands: ['npx', 'npm', 'tsc', 'eslint', 'prettier', 'node'],
maxCommandLength: 1000,
logSecurityEvents: options.verbose || false
});
console.log(chalk.blue(`π€ Worker initialized: ${this.workerId} (${this.groupId})`));
}
/**
* Execute command safely with security validation
*/
async executeSecureCommand(command, args = [], options = {}) {
try {
// Validate and sanitize command
const sanitized = this.securityManager.sanitizeCommand(command, args);
// Execute using spawn for better security control
return new Promise((resolve, reject) => {
const child = spawn(sanitized.command, sanitized.args, {
cwd: options.cwd || this.projectRoot,
timeout: options.timeout || this.options.taskTimeout,
stdio: ['ignore', 'pipe', 'pipe'],
shell: false // Disable shell to prevent injection
});
let stdout = '';
let stderr = '';
child.stdout.on('data', (data) => {
stdout += data.toString();
});
child.stderr.on('data', (data) => {
stderr += data.toString();
});
child.on('close', (code) => {
if (code === 0) {
resolve({ stdout, stderr, code });
} else {
reject(new Error(`Command failed with code ${code}: ${stderr}`));
}
});
child.on('error', (error) => {
reject(new Error(`Command execution failed: ${error.message}`));
});
// Handle timeout
if (options.timeout) {
setTimeout(() => {
child.kill('SIGTERM');
reject(new Error('Command execution timeout'));
}, options.timeout);
}
});
} catch (error) {
if (error instanceof SecurityError) {
await logError(this.workerId, 'Security violation in command execution', error);
throw error;
}
throw new Error(`Command execution failed: ${error.message}`);
}
}
/**
* Start worker execution
*/
async start() {
if (this.isRunning) {
throw new Error('Worker already running');
}
this.isRunning = true;
try {
console.log(chalk.green(`π ${this.workerId} starting work on ${this.groupId}`));
await logWorker(this.workerId, 'Starting Worker', {
description: `Worker starting with group assignment: ${this.groupId}`,
result: 'STARTED',
memory: `Memory limit: ${this.options.memoryLimit}MB`
});
// Start memory monitoring
this.memoryManager.startMonitoring();
// Setup memory event handlers
this.setupMemoryHandlers();
// Register with coordinator
await this.registerWithCoordinator();
// Start heartbeat
this.startHeartbeat();
// Check if in standby mode
if (this.groupId === 'STANDBY') {
console.log(chalk.yellow(`βΈοΈ ${this.workerId} waiting in standby mode...`));
await this.updateStatus('standby');
return; // Don't execute tasks, wait for assignment
}
// Wait for dependencies
await this.waitForDependencies();
// Execute tasks
await this.executeTasks();
// Mark as completed and switch to inactive/standby mode
await this.updateStatus('inactive');
console.log(chalk.green(`π ${this.workerId} completed all tasks`));
console.log(chalk.yellow(`βΈοΈ ${this.workerId} switching to inactive mode, waiting for new assignments...`));
await logWorker(this.workerId, 'Tasks Completed', {
description: `All tasks completed, switching to inactive mode`,
result: 'SUCCESS - INACTIVE',
notes: `Worker remains active for reassignment`
});
// Stay alive and wait for reassignment instead of exiting
return await this.waitForReassignment();
} catch (error) {
console.error(chalk.red(`β ${this.workerId} failed:`), error.message);
await this.updateStatus('error', { error: error.message });
throw error;
} finally {
await this.cleanup();
}
}
/**
* Register worker with coordination system
*/
async registerWithCoordinator() {
const state = await this.loadSystemState();
// Get group configuration
const groupConfig = state.dependencies[this.groupId];
if (!groupConfig) {
throw new Error(`Group configuration not found: ${this.groupId}`);
}
const workerInfo = {
group: this.groupId,
status: 'initializing',
started_at: new Date().toISOString(),
last_heartbeat: new Date().toISOString(),
current_files: [],
progress: {
total_tasks: this.getTaskCount(groupConfig),
completed_tasks: 0,
current_task: null
}
};
state.active_workers[this.workerId] = workerInfo;
state.task_progress.active_groups++;
await this.saveSystemState(state);
console.log(chalk.green(`β
${this.workerId} registered with coordinator`));
this.emit('registered');
}
/**
* Update worker status in coordination system
*/
async updateStatus(status, additionalData = {}) {
const state = await this.loadSystemState();
if (state.active_workers[this.workerId]) {
Object.assign(state.active_workers[this.workerId], {
status,
last_heartbeat: new Date().toISOString(),
...additionalData
});
await this.saveSystemState(state);
}
this.emit('status_updated', { status, ...additionalData });
}
/**
* Wait for dependencies to complete
*/
async waitForDependencies() {
console.log(chalk.blue(`β³ ${this.workerId} checking dependencies...`));
while (true) {
const dependencyCheck = await this.checkDependencies();
if (dependencyCheck.canProceed) {
console.log(chalk.green(`β
${this.workerId} dependencies satisfied`));
break;
}
console.log(chalk.yellow(
`β³ ${this.workerId} waiting for: ${dependencyCheck.blockers.join(', ')}`
));
await this.sleep(10000); // Wait 10 seconds
}
}
/**
* Check if dependencies are satisfied
*/
async checkDependencies() {
const state = await this.loadSystemState();
const groupConfig = state.dependencies[this.groupId];
if (!groupConfig || !groupConfig.blocked_by) {
return { canProceed: true, blockers: [] };
}
const blockers = [];
for (const blockingGroup of groupConfig.blocked_by) {
const blockingWorker = Object.values(state.active_workers).find(
w => w.group === blockingGroup
);
if (!blockingWorker || blockingWorker.status !== 'completed') {
blockers.push(blockingGroup);
}
}
return {
canProceed: blockers.length === 0,
blockers
};
}
/**
* Execute all tasks for this worker group
*/
async executeTasks() {
const state = await this.loadSystemState();
const groupConfig = state.dependencies[this.groupId];
console.log(chalk.blue(`π§ ${this.workerId} starting task execution`));
await this.updateStatus('working');
const tasks = this.generateTasks(groupConfig);
for (let i = 0; i < tasks.length; i++) {
const task = tasks[i];
if (!this.isRunning) {
console.log(chalk.yellow(`π ${this.workerId} stopping execution`));
break;
}
this.currentTask = task;
try {
await this.executeTask(task);
// Update progress
const currentState = await this.loadSystemState();
if (currentState.active_workers[this.workerId]) {
currentState.active_workers[this.workerId].progress.completed_tasks = i + 1;
await this.saveSystemState(currentState);
}
} catch (error) {
console.error(chalk.red(`β Task failed: ${task.name}`), error.message);
if (task.retries < this.options.maxRetries) {
console.log(chalk.yellow(`π Retrying task: ${task.name}`));
task.retries++;
i--; // Retry the same task
} else {
throw error;
}
}
// Brief pause between tasks
await this.sleep(1000);
}
}
/**
* Execute a single task
*/
async executeTask(task) {
console.log(chalk.blue(`π ${this.workerId} executing: ${task.name}`));
// Update current task
await this.updateStatus('working', {
current_files: task.files,
progress: { current_task: task.name }
});
// Acquire file locks
await this.acquireFileLocks(task.files);
try {
// Execute task based on type
switch (task.action) {
case 'typescript_fix':
await this.executeTypeScriptFix(task);
break;
case 'eslint_fix':
await this.executeESLintFix(task);
break;
case 'bundle_analysis':
await this.executeBundleAnalysis(task);
break;
case 'dependency_cleanup':
await this.executeDependencyCleanup(task);
break;
case 'custom':
if (typeof task.handler === 'function') {
await task.handler(task, this);
}
break;
default:
console.log(chalk.yellow(`β οΈ Unknown task action: ${task.action}`));
}
console.log(chalk.green(`β
${this.workerId} completed: ${task.name}`));
} finally {
// Always release file locks
await this.releaseFileLocks(task.files);
}
}
/**
* File locking system
*/
async acquireFileLocks(files) {
const state = await this.loadSystemState();
for (const file of files) {
// Check if file is locked by another worker
if (state.file_locks[file] && state.file_locks[file] !== this.workerId) {
// Wait for file to become available
await this.waitForFileAvailability(file);
}
// Acquire lock
state.file_locks[file] = this.workerId;
this.acquiredLocks.push(file);
}
await this.saveSystemState(state);
if (this.options.verbose) {
console.log(chalk.gray(`π ${this.workerId} acquired locks: ${files.join(', ')}`));
}
}
async releaseFileLocks(files) {
const state = await this.loadSystemState();
for (const file of files) {
if (state.file_locks[file] === this.workerId) {
delete state.file_locks[file];
this.acquiredLocks = this.acquiredLocks.filter(f => f !== file);
}
}
await this.saveSystemState(state);
if (this.options.verbose && files.length > 0) {
console.log(chalk.gray(`π ${this.workerId} released locks: ${files.join(', ')}`));
}
}
async waitForFileAvailability(file, maxWaitTime = 300000) {
const startTime = Date.now();
while (Date.now() - startTime < maxWaitTime) {
const state = await this.loadSystemState();
if (!state.file_locks[file] || state.file_locks[file] === this.workerId) {
return true;
}
console.log(chalk.yellow(
`β³ ${this.workerId} waiting for ${file} (locked by ${state.file_locks[file]})`
));
await this.sleep(5000);
}
throw new Error(`Timeout waiting for file: ${file}`);
}
/**
* Task implementations
*/
async executeTypeScriptFix(task) {
for (const file of task.files) {
if (this.options.dryRun) {
console.log(chalk.blue(`[DRY RUN] Would fix TypeScript errors in: ${file}`));
await this.sleep(1000);
continue;
}
try {
// Validate file path for security
const safePath = this.securityManager.sanitizePath(file);
// Check for TypeScript errors using secure command execution
const { stderr } = await this.executeSecureCommand('npx', [
'tsc',
'--noEmit',
path.relative(this.projectRoot, safePath)
], {
cwd: this.projectRoot,
timeout: this.options.taskTimeout
});
if (stderr.includes('error TS')) {
console.log(chalk.yellow(`π§ Found TypeScript errors in ${file}`));
// Here would be actual TypeScript fixing logic
await this.sleep(2000); // Simulate fix time
}
} catch (error) {
// TypeScript errors expected - continue with fixes
console.log(chalk.blue(`π§ Processing TypeScript fixes for ${file}`));
await this.sleep(2000);
}
}
}
async executeESLintFix(task) {
for (const file of task.files) {
if (this.options.dryRun) {
console.log(chalk.blue(`[DRY RUN] Would analyze ESLint issues in: ${file}`));
await this.sleep(1000);
continue;
}
try {
const { stdout } = await execAsync(
`npx eslint ${file} --format=json || echo "[]"`,
{ cwd: this.projectRoot }
);
const results = JSON.parse(stdout || '[]');
if (results.length > 0 && results[0].messages?.length > 0) {
console.log(chalk.yellow(
`π Found ${results[0].messages.length} ESLint issues in ${file}`
));
// Manual analysis - no auto-fix per Zero Protocol
}
} catch (error) {
console.log(chalk.yellow(`β οΈ ESLint analysis warning for ${file}: ${error.message}`));
}
await this.sleep(1500);
}
}
async executeBundleAnalysis(task) {
console.log(chalk.blue('π Analyzing bundle size and dependencies'));
if (!this.options.dryRun) {
// Actual bundle analysis would go here
await this.sleep(3000);
}
console.log(chalk.green('β
Bundle analysis completed'));
}
async executeDependencyCleanup(task) {
console.log(chalk.blue('π§Ή Analyzing unused dependencies'));
if (!this.options.dryRun) {
// Dependency cleanup logic would go here
await this.sleep(2000);
}
console.log(chalk.green('β
Dependency cleanup completed'));
}
/**
* Generate tasks for the worker group
*/
generateTasks(groupConfig) {
const baseTaskMap = {
GRUP1_TYPESCRIPT: [
{
name: 'Fix TypeScript syntax errors',
files: groupConfig.files.filter(f => f.includes('.ts')),
action: 'typescript_fix'
},
{
name: 'Validate build process',
files: ['tsconfig.json'],
action: 'typescript_fix'
}
],
GRUP2_ESLINT: [
{
name: 'Analyze ESLint warnings',
files: groupConfig.files,
action: 'eslint_fix'
}
],
GRUP3_BUNDLE: [
{
name: 'Analyze bundle size',
files: ['package.json', 'next.config.*'],
action: 'bundle_analysis'
},
{
name: 'Clean unused dependencies',
files: ['package.json', 'package-lock.json'],
action: 'dependency_cleanup'
}
]
};
const tasks = baseTaskMap[this.groupId] || [
{
name: `Process ${this.groupId} tasks`,
files: groupConfig.files,
action: 'custom'
}
];
// Add retry counter to each task
return tasks.map(task => ({
...task,
retries: 0
}));
}
getTaskCount(groupConfig) {
const tasks = this.generateTasks(groupConfig);
return tasks.length;
}
/**
* Heartbeat system
*/
startHeartbeat() {
this.heartbeatTimer = setInterval(async () => {
try {
await this.sendHeartbeat();
} catch (error) {
console.error(chalk.red(`β Heartbeat failed: ${error.message}`));
}
}, this.options.heartbeatInterval);
if (this.options.verbose) {
console.log(chalk.gray(`π Heartbeat started (${this.options.heartbeatInterval}ms)`));
}
}
async sendHeartbeat() {
await this.updateStatus(this.currentTask ? 'working' : 'idle');
}
/**
* System state management
*/
async loadSystemState() {
try {
const data = await fs.readFile(this.stateFile, 'utf8');
return JSON.parse(data);
} catch (error) {
throw new Error(`Failed to load system state: ${error.message}`);
}
}
async saveSystemState(state) {
try {
await fs.writeFile(this.stateFile, JSON.stringify(state, null, 2));
} catch (error) {
throw new Error(`Failed to save system state: ${error.message}`);
}
}
/**
* Setup memory management event handlers
*/
setupMemoryHandlers() {
this.memoryManager.on('memoryWarning', (stats) => {
console.log(chalk.yellow(`β οΈ ${this.workerId} memory warning: ${stats.current}MB/${stats.max}MB`));
});
this.memoryManager.on('memoryCritical', (stats) => {
console.log(chalk.red(`π¨ ${this.workerId} critical memory usage: ${stats.current}MB/${stats.max}MB`));
console.log(chalk.red('π Consider restarting this worker'));
});
this.memoryManager.on('criticalError', (error) => {
console.error(chalk.red(`π₯ ${this.workerId} critical error: ${error.message}`));
this.shutdown();
});
}
/**
* Get memory statistics
*/
getMemoryStats() {
return this.memoryManager.getStats();
}
/**
* Get comprehensive worker status for user queries
*/
async getWorkerStatus() {
const memStats = this.getMemoryStats();
const systemState = await this.loadSystemState();
const otherWorkers = Object.entries(systemState.active_workers || {})
.filter(([id]) => id !== this.workerId)
.map(([id, worker]) => ({
id,
group: worker.group,
status: worker.status,
memory: worker.memory,
lastSeen: worker.last_heartbeat ?
this.formatTimeAgo(new Date(worker.last_heartbeat)) : 'Unknown'
}));
return {
// Current worker info
self: {
workerId: this.workerId,
groupId: this.groupId,
status: this.isRunning ? 'working' : 'stopped',
currentTask: this.currentTask,
memory: {
current: memStats.current,
limit: memStats.limit,
usage: memStats.usage
},
lockedFiles: [...this.acquiredLocks]
},
// System overview
system: {
totalWorkers: Object.keys(systemState.active_workers || {}).length,
maxWorkers: systemState.system_info?.max_workers || 6,
totalFileLocks: Object.keys(systemState.file_locks || {}).length,
healthy: systemState.system_info?.healthy !== false
},
// Other workers
otherWorkers,
// Dependencies
dependencies: systemState.dependencies || {},
// Recent activity (if available)
recentActivity: systemState.recent_activity || []
};
}
/**
* Format user-friendly status response
*/
async getStatusResponse() {
const status = await this.getWorkerStatus();
let response = `π€ Current Status:\n`;
response += `Worker ID: ${status.self.workerId}\n`;
response += `Group: ${status.self.groupId}\n`;
if (status.self.currentTask) {
response += `Task: ${status.self.currentTask}\n`;
}
response += `Memory: ${status.self.memory.current}MB / ${status.self.memory.limit}MB (${status.self.memory.usage}%)\n`;
if (status.self.lockedFiles.length > 0) {
response += `Locked Files: ${status.self.lockedFiles.join(', ')}\n`;
}
response += `\nπ Coordination Status:\n`;
if (status.otherWorkers.length > 0) {
status.otherWorkers.forEach(worker => {
const memInfo = worker.memory ?
` - Memory ${worker.memory.usage}%` : '';
response += `- ${worker.id} (${worker.group}): ${worker.status}${memInfo}\n`;
});
} else {
response += `- No other workers active\n`;
}
response += `System ${status.system.healthy ? 'healthy β
' : 'unhealthy β'}`;
return response;
}
/**
* Format time ago helper
*/
formatTimeAgo(date) {
const now = new Date();
const diffMs = now - date;
const diffSecs = Math.floor(diffMs / 1000);
const diffMins = Math.floor(diffSecs / 60);
if (diffSecs < 60) return `${diffSecs}s ago`;
if (diffMins < 60) return `${diffMins}m ago`;
return date.toLocaleTimeString();
}
/**
* Cleanup and shutdown
*/
async cleanup() {
console.log(chalk.blue(`π§Ή ${this.workerId} cleaning up...`));
// Stop memory monitoring
if (this.memoryManager) {
await this.memoryManager.gracefulShutdown();
}
// Stop heartbeat
if (this.heartbeatTimer) {
clearInterval(this.heartbeatTimer);
this.heartbeatTimer = null;
}
// Release all file locks
if (this.acquiredLocks.length > 0) {
await this.releaseFileLocks([...this.acquiredLocks]);
}
// Update final status
if (this.isRunning) {
await this.updateStatus('stopped');
}
this.isRunning = false;
}
/**
* Assign worker to a specific group (for standby mode)
*/
async assignToGroup(groupId) {
if (this.groupId !== 'STANDBY') {
throw new Error('Worker must be in STANDBY mode to assign to group');
}
console.log(chalk.blue(`π Switching from STANDBY to ${groupId}`));
this.groupId = groupId;
// Update status
await this.updateStatus('initializing');
// Restart execution with new group
this.executeTasks().then(() => {
console.log(chalk.green(`π ${this.workerId} completed all tasks for ${groupId}`));
}).catch((error) => {
console.error(chalk.red(`β ${this.workerId} failed:`, error.message));
});
return true;
}
/**
* Pause worker operations (used when coordinator disconnects)
*/
async pause() {
if (!this.isRunning) return;
console.log(chalk.yellow(`βΈοΈ ${this.workerId} pausing operations`));
this.isRunning = false;
this.currentTask = null;
// Stop heartbeat
if (this.heartbeatTimer) {
clearTimeout(this.heartbeatTimer);
this.heartbeatTimer = null;
}
await this.updateStatus('paused');
this.emit('paused');
}
/**
* Resume worker operations (used when coordinator reconnects)
*/
async resume() {
if (this.isRunning) return;
console.log(chalk.green(`βΆοΈ ${this.workerId} resuming operations`));
this.isRunning = true;
await this.updateStatus('resuming');
this.emit('resumed');
// Restart execution
this.executeTasks().then(() => {
console.log(chalk.green(`π ${this.workerId} completed all tasks for ${this.groupId}`));
}).catch((error) => {
console.error(chalk.red(`β ${this.workerId} failed:`, error.message));
});
}
async shutdown() {
console.log(chalk.yellow(`π ${this.workerId} shutting down...`));
this.isRunning = false;
await this.cleanup();
this.emit('shutdown');
}
/**
* Wait for reassignment while in inactive mode
*/
async waitForReassignment() {
console.log(chalk.gray(`β³ ${this.workerId} waiting for new assignments...`));
// Check for reassignment every 10 seconds
const reassignmentInterval = setInterval(async () => {
try {
const state = await this.loadCoordinatorState();
const worker = state.active_workers[this.workerId];
if (worker && worker.status === 'reassigned') {
console.log(chalk.blue(`π ${this.workerId} received new assignment: ${worker.group}`));
clearInterval(reassignmentInterval);
// Update group and restart
this.groupId = worker.group;
await this.updateStatus('initializing');
return await this.executeTasks();
}
} catch (error) {
// Silently handle errors during reassignment check
}
}, 10000);
// Keep the worker alive
return new Promise(() => {
// This promise never resolves, keeping worker alive
});
}
sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
}
module.exports = WorkerCore;