UNPKG

@sirmrmarty/n8n-nodes-tmux-orchestrator

Version:

n8n nodes for orchestrating Claude AI agents through tmux sessions

419 lines (411 loc) 18.1 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.ProjectOrchestrator = void 0; const conditionWaiter_1 = require("./conditionWaiter"); const resourceManager_1 = require("./resourceManager"); const circuitBreaker_1 = require("./circuitBreaker"); const threadSafeState_1 = require("./threadSafeState"); class ProjectOrchestrator { constructor(bridge, config = {}) { this.monitoredProjects = threadSafeState_1.stateManager.getMap('monitoredProjects'); this.isMonitoring = false; this.bridge = bridge; this.config = { checkInterval: 60000, maxRetries: 5, autoCreatePR: true, ...config, }; } async addProject(projectName) { if (!this.monitoredProjects.safeHas(projectName)) { try { await this.monitoredProjects.safeSet(projectName, { projectName, isComplete: false, qaApproved: false, readyForPR: false, prCreated: false, lastCheck: new Date(), retryCount: 0, }); console.log(`Added project ${projectName} to monitoring queue`); if (!this.isMonitoring) { this.startMonitoring(); } } catch (error) { console.error(`Failed to add project ${projectName} to monitoring:`, error.message); throw error; } } } async removeProject(projectName) { try { const deleted = await this.monitoredProjects.safeDelete(projectName); if (deleted) { console.log(`Removed project ${projectName} from monitoring`); } if (this.monitoredProjects.safeSize() === 0) { await this.stopMonitoring(); } } catch (error) { console.error(`Failed to remove project ${projectName} from monitoring:`, error.message); throw error; } } startMonitoring() { if (this.isMonitoring) { return; } console.log('Starting autonomous project monitoring...'); this.isMonitoring = true; const { id } = resourceManager_1.resourceManager.createInterval(async () => { await this.checkAllProjects(); }, this.config.checkInterval, 'Project orchestrator monitoring'); this.monitoringResourceId = id; } async stopMonitoring() { if (this.monitoringResourceId) { await resourceManager_1.resourceManager.cleanup(this.monitoringResourceId); this.monitoringResourceId = undefined; } this.isMonitoring = false; console.log('Stopped autonomous project monitoring'); } async checkAllProjects() { const circuitBreaker = circuitBreaker_1.circuitRegistry.getBreaker('project-monitoring', { failureThreshold: 5, recoveryTimeout: 180000, successThreshold: 3, monitoringWindow: 600000, maxRetryAttempts: 2, onStateChange: (state, reason) => { console.log(`[Circuit Breaker] Project monitoring: ${state} - ${reason}`); }, onFailure: (error) => { console.error(`[Circuit Breaker] Project monitoring failure:`, error.message); } }); await circuitBreaker.execute(async () => { const promises = this.monitoredProjects.safeKeys().map(async (projectName) => { try { await this.checkProject(projectName); } catch (error) { console.error(`Error checking project ${projectName}:`, error.message); } }); await Promise.allSettled(promises); }, async () => { console.warn('[Circuit Breaker] Project monitoring is in open state, skipping monitoring cycle'); }); } async checkProject(projectName) { const circuitBreaker = circuitBreaker_1.circuitRegistry.getBreaker(`project-check-${projectName}`, { failureThreshold: 3, recoveryTimeout: 120000, successThreshold: 2, maxRetryAttempts: 1, onStateChange: (state, reason) => { console.log(`[Circuit Breaker] Project check for ${projectName}: ${state} - ${reason}`); } }); await circuitBreaker.execute(async () => { return await this.performProjectCheck(projectName); }, async () => { const status = this.monitoredProjects.safeGet(projectName); if (status) { try { await this.monitoredProjects.safeUpdate(projectName, (currentStatus) => { if (currentStatus) { currentStatus.retryCount++; currentStatus.lastCheck = new Date(); } return currentStatus; }); console.warn(`[Circuit Breaker] Project check for ${projectName} failed, retry count: ${status.retryCount}`); if (status.retryCount >= (this.config.maxRetries || 5)) { console.error(`Max retries reached for ${projectName} - removing from monitoring due to circuit breaker`); await this.removeProject(projectName); } } catch (error) { console.error(`Failed to update status for ${projectName}:`, error.message); } } }); } async performProjectCheck(projectName) { const status = this.monitoredProjects.safeGet(projectName); if (!status || status.prCreated) { return; } try { const sessions = await this.bridge.getTmuxSessions(); const projectSession = sessions.find(s => s.name === projectName); if (!projectSession) { console.warn(`Project session ${projectName} not found - removing from monitoring`); await this.removeProject(projectName); return; } const completionStatus = await this.checkProjectCompletion(projectName); try { await this.monitoredProjects.safeUpdate(projectName, (currentStatus) => { if (currentStatus) { currentStatus.isComplete = completionStatus.isComplete; currentStatus.qaApproved = completionStatus.qaApproved; currentStatus.readyForPR = completionStatus.readyForPR; currentStatus.lastCheck = new Date(); } return currentStatus; }); } catch (error) { console.error(`Failed to update project status for ${projectName}:`, error.message); return; } const currentStatus = this.monitoredProjects.safeGet(projectName); if (!currentStatus) { return; } if (currentStatus.readyForPR && !currentStatus.prCreated && this.config.autoCreatePR) { console.log(`Project ${projectName} is ready - initiating automatic PR creation...`); const prResult = await this.createAutomaticPR(projectName); if (prResult.success) { try { await this.monitoredProjects.safeUpdate(projectName, (status) => { if (status) { status.prCreated = true; status.prUrl = prResult.prUrl; } return status; }); console.log(`Autonomous PR creation successful for ${projectName}: ${prResult.prUrl}`); await this.notifyProjectCompletion(projectName, prResult.prUrl); await this.removeProject(projectName); } catch (error) { console.error(`Failed to update PR completion status for ${projectName}:`, error.message); } } else { try { const updatedStatus = await this.monitoredProjects.safeUpdate(projectName, (status) => { if (status) { status.retryCount++; } return status; }); console.error(`PR creation failed for ${projectName} (attempt ${updatedStatus.retryCount}): ${prResult.error}`); if (updatedStatus.retryCount >= (this.config.maxRetries || 5)) { console.error(`Max retries reached for ${projectName} - removing from monitoring`); await this.removeProject(projectName); } } catch (error) { console.error(`Failed to update retry count for ${projectName}:`, error.message); } } } } catch (error) { try { const updatedStatus = await this.monitoredProjects.safeUpdate(projectName, (status) => { if (status) { status.retryCount++; } return status; }); console.error(`Error monitoring project ${projectName}:`, error.message); if (updatedStatus && updatedStatus.retryCount >= (this.config.maxRetries || 5)) { console.error(`Max retries reached for ${projectName} - removing from monitoring`); await this.removeProject(projectName); } } catch (updateError) { console.error(`Failed to update error count for ${projectName}:`, updateError.message); } } } async checkProjectCompletion(projectName) { const circuitBreaker = circuitBreaker_1.circuitRegistry.getBreaker(`completion-check-${projectName}`, { failureThreshold: 3, recoveryTimeout: 90000, successThreshold: 2, maxRetryAttempts: 1, onStateChange: (state, reason) => { console.log(`[Circuit Breaker] Completion check for ${projectName}: ${state} - ${reason}`); } }); return await circuitBreaker.execute(async () => { return await this.performCompletionCheck(projectName); }, async () => { console.warn(`[Circuit Breaker] Completion check for ${projectName} failed, returning safe status`); return { isComplete: false, qaApproved: false, readyForPR: false }; }); } async performCompletionCheck(projectName) { await this.bridge.sendClaudeMessage(`${projectName}:0`, 'AUTONOMOUS STATUS CHECK: Please respond with "PROJECT COMPLETE" if all objectives are met and ready for PR. This is an automated check.'); await conditionWaiter_1.ConditionWaiter.waitForPrompt(this.bridge, projectName, 0); const output = await this.bridge.captureWindowContent(projectName, 0, 20); let response = ''; if (typeof output === 'string') { response = output.split('\n').slice(-10).join('\n').toLowerCase(); } const completionSignals = [ 'project complete', 'ready for pr', 'ready for pull request', 'objectives met', 'deliverables complete', 'implementation finished' ]; const isComplete = completionSignals.some(signal => response.includes(signal)); const qualityIndicators = [ 'tests passed', 'validation complete', 'quality check', 'all tests', 'qa complete', 'approved' ]; const qaApproved = isComplete && (qualityIndicators.some(indicator => response.includes(indicator)) || response.includes('project complete')); return { isComplete, qaApproved, readyForPR: isComplete && qaApproved, }; } async createAutomaticPR(projectName) { const circuitBreaker = circuitBreaker_1.circuitRegistry.getBreaker(`pr-creation-${projectName}`, { failureThreshold: 2, recoveryTimeout: 300000, successThreshold: 1, maxRetryAttempts: 1, onStateChange: (state, reason) => { console.log(`[Circuit Breaker] PR creation for ${projectName}: ${state} - ${reason}`); } }); return await circuitBreaker.execute(async () => { return await this.performPRCreation(projectName); }, async () => { console.warn(`[Circuit Breaker] PR creation for ${projectName} failed, circuit breaker is open`); return { success: false, error: 'PR creation temporarily unavailable due to circuit breaker protection' }; }); } async performPRCreation(projectName) { try { await this.bridge.sendCommandToWindow(projectName, 0, 'pwd'); await conditionWaiter_1.ConditionWaiter.waitForPrompt(this.bridge, projectName, 0); const output = await this.bridge.captureWindowContent(projectName, 0, 5); let projectPath = ''; if (typeof output === 'string') { const lines = output.trim().split('\n'); projectPath = lines[lines.length - 1].trim(); } if (!projectPath) { throw new Error('Could not determine project path'); } const branchCmd = `cd ${projectPath} && git branch --show-current`; await this.bridge.sendCommandToWindow(projectName, 0, branchCmd); await conditionWaiter_1.ConditionWaiter.waitForPrompt(this.bridge, projectName, 0); const branchOutput = await this.bridge.captureWindowContent(projectName, 0, 5); let currentBranch = ''; if (typeof branchOutput === 'string') { const lines = branchOutput.trim().split('\n'); currentBranch = lines[lines.length - 1].trim(); } const pushCmd = `cd ${projectPath} && git add . && git commit -m "Autonomous completion commit" && git push -u origin ${currentBranch}`; await this.bridge.sendCommandToWindow(projectName, 0, pushCmd); await conditionWaiter_1.ConditionWaiter.waitForGitOperation(this.bridge, projectName, 0, 'push'); const prTitle = `[Autonomous] ${projectName} - Project Complete`; const prDescription = this.generatePRDescription(projectName); const prResult = await this.bridge.createGitHubPR(projectPath, { title: prTitle, body: prDescription, base: 'main', head: currentBranch, credentials: this.config.credentials, }); return prResult; } catch (error) { return { success: false, error: error.message, }; } } generatePRDescription(projectName) { const template = this.config.credentials?.githubConfig?.prTemplate || ` ## Summary Autonomous completion of ${projectName} project. ## Changes - Project implementation completed autonomously - All objectives met and validated - Quality checks passed ## Test Plan - Automated testing completed - Code quality validation passed - Ready for review and merge ## Quality Status ✅ Quality Approved - All validations passed 🤖 Generated autonomously with Claude Code Tmux Orchestrator `.trim(); return template .replace(/{project_name}/g, projectName) .replace(/{project_description}/g, `Autonomous completion of ${projectName}`) .replace(/{changes_summary}/g, 'Implementation completed autonomously') .replace(/{test_summary}/g, 'All tests passed, quality approved') .replace(/{qa_status}/g, '✅ Quality Approved'); } async notifyProjectCompletion(projectName, prUrl) { try { const completionMessage = ` 🎉 AUTONOMOUS PROJECT COMPLETION SUCCESS! 🎉 Project: ${projectName} Pull Request: ${prUrl} The project has been completed autonomously and is ready for final review and merge. Next Steps: - Review the pull request - Merge when ready - Deploy to production Autonomous orchestration complete! 🚀 `.trim(); await this.bridge.sendClaudeMessage(`${projectName}:0`, completionMessage); console.log(`Claude Code instance notified of autonomous completion for ${projectName}`); } catch (error) { console.error(`Failed to notify Claude Code instance for ${projectName}:`, error.message); } } getMonitoringStatus() { return { isMonitoring: this.isMonitoring, projectCount: this.monitoredProjects.safeSize(), projects: this.monitoredProjects.safeValues(), }; } async destroy() { await this.stopMonitoring(); try { await this.monitoredProjects.safeClear(); } catch (error) { console.error('Failed to clear monitored projects:', error.message); } console.log('Project orchestrator destroyed'); } } exports.ProjectOrchestrator = ProjectOrchestrator; //# sourceMappingURL=projectOrchestrator.js.map