UNPKG

@sirmrmarty/n8n-nodes-tmux-orchestrator

Version:

n8n nodes for orchestrating Claude AI agents through tmux sessions

309 lines 12 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.ConditionWaiter = void 0; class ConditionWaiter { static async waitForCondition(condition, options = {}) { const startTime = Date.now(); const timeout = options.timeout || this.DEFAULT_TIMEOUT; const baseInterval = options.interval || this.DEFAULT_INTERVAL; const maxRetries = options.retries || this.DEFAULT_RETRIES; const useExponentialBackoff = options.exponentialBackoff || false; const minInterval = options.minInterval || this.DEFAULT_MIN_INTERVAL; const maxInterval = options.maxInterval || this.DEFAULT_MAX_INTERVAL; let attempts = 0; let currentInterval = baseInterval; while (attempts < maxRetries && (Date.now() - startTime) < timeout) { attempts++; try { const result = await condition.check(); if (result) { return { success: true, duration: Date.now() - startTime, attempts, reason: 'Condition met' }; } } catch (error) { console.warn(`Condition check failed on attempt ${attempts}: ${error.message}`); } if (useExponentialBackoff) { currentInterval = Math.min(Math.max(currentInterval * 1.5, minInterval), maxInterval); } else { currentInterval = baseInterval; } await new Promise(resolve => setTimeout(resolve, currentInterval)); } return { success: false, duration: Date.now() - startTime, attempts, reason: `Timeout after ${timeout}ms or max retries (${maxRetries}) reached` }; } static async waitForTmuxWindow(bridge, sessionName, windowIndex, expectedContent, options = {}) { const condition = { check: async () => { try { const content = await bridge.captureWindowContent(sessionName, windowIndex, 20); if (typeof expectedContent === 'string') { return content.includes(expectedContent); } else { return expectedContent.test(content); } } catch (error) { return false; } }, description: `Waiting for tmux window ${sessionName}:${windowIndex} to contain: ${expectedContent}` }; return this.waitForCondition(condition, { timeout: 30000, interval: 1000, exponentialBackoff: true, ...options }); } static async waitForPrompt(bridge, sessionName, windowIndex, promptPattern = /[\$#%>]\s*$/m, options = {}) { const condition = { check: async () => { try { const content = await bridge.captureWindowContent(sessionName, windowIndex, 5); return promptPattern.test(content); } catch (error) { return false; } }, description: `Waiting for command prompt in ${sessionName}:${windowIndex}` }; return this.waitForCondition(condition, { timeout: 10000, interval: 500, exponentialBackoff: true, ...options }); } static async waitForClaudeReady(bridge, sessionName, windowIndex, options = {}) { const readyPatterns = [ /Claude\s+Code/i, /Model:\s+Claude/i, /Ready\s+to\s+help/i, /How\s+can\s+I\s+help/i, /What\s+would\s+you\s+like/i ]; const condition = { check: async () => { try { const content = await bridge.captureWindowContent(sessionName, windowIndex, 10); return readyPatterns.some(pattern => pattern.test(content)); } catch (error) { return false; } }, description: `Waiting for Claude agent to be ready in ${sessionName}:${windowIndex}` }; return this.waitForCondition(condition, { timeout: 60000, interval: 2000, exponentialBackoff: true, ...options }); } static async waitForGitOperation(bridge, sessionName, windowIndex, operation, options = {}) { const operationPatterns = { push: [/branch.*up.to.date/i, /everything.*up.to.date/i, /\d+\s+objects/i, /Total\s+\d+/i], pull: [/Already.*up.to.date/i, /Fast.forward/i, /files?\s+changed/i], commit: [/\d+\s+files?\s+changed/i, /create\s+mode/i, /\[\w+.*\]/i], branch: [/Switched\s+to/i, /already\s+on/i, /branch.*set\s+up/i] }; const errorPatterns = [ /error:/i, /fatal:/i, /failed/i, /rejected/i, /permission\s+denied/i, /authentication\s+failed/i ]; const condition = { check: async () => { try { const content = await bridge.captureWindowContent(sessionName, windowIndex, 10); if (errorPatterns.some(pattern => pattern.test(content))) { throw new Error(`Git operation failed: ${content.substring(0, 200)}`); } return operationPatterns[operation].some(pattern => pattern.test(content)); } catch (error) { if (error.message.includes('Git operation failed')) { throw error; } return false; } }, description: `Waiting for git ${operation} operation to complete in ${sessionName}:${windowIndex}` }; return this.waitForCondition(condition, { timeout: 30000, interval: 1000, exponentialBackoff: true, ...options }); } static async waitForPath(pathToCheck, options = {}) { const fs = require('fs').promises; const condition = { check: async () => { try { await fs.access(pathToCheck); return true; } catch { return false; } }, description: `Waiting for path to exist: ${pathToCheck}` }; return this.waitForCondition(condition, { timeout: 10000, interval: 200, ...options }); } static async waitForClaudeOperation(bridge, sessionName, windowIndex, operationDescription, options = {}) { const completionPatterns = [ /✅/, /completed/i, /finished/i, /done/i, /ready/i, /[>$#%]\s*$/m, ]; const condition = { check: async () => { try { const content = await bridge.captureWindowContent(sessionName, windowIndex, 20); return completionPatterns.some(pattern => pattern.test(content)); } catch (error) { return false; } }, description: `Waiting for Claude to complete: ${operationDescription} in ${sessionName}:${windowIndex}` }; return this.waitForCondition(condition, { timeout: 600000, interval: 10000, exponentialBackoff: false, ...options }); } static async waitForAgentResponsive(bridge, sessionName, windowIndex, options = {}) { const condition = { check: async () => { try { await bridge.sendCommandToWindow(sessionName, windowIndex, 'echo "agent_test_ready"'); await new Promise(resolve => setTimeout(resolve, 500)); const content = await bridge.captureWindowContent(sessionName, windowIndex, 5); return content.includes('agent_test_ready'); } catch (error) { return false; } }, description: `Waiting for agent to be responsive in ${sessionName}:${windowIndex}` }; return this.waitForCondition(condition, { timeout: 10000, interval: 2000, exponentialBackoff: false, ...options }); } static async waitForProcessCompletion(bridge, sessionName, windowIndex, options = {}) { const condition = { check: async () => { try { await bridge.sendCommandToWindow(sessionName, windowIndex, 'ps aux | grep -v grep | grep -c "[^]]$" || echo "0"'); await new Promise(resolve => setTimeout(resolve, 300)); const content = await bridge.captureWindowContent(sessionName, windowIndex, 3); const lines = content.trim().split('\n'); const lastLine = lines[lines.length - 1]; const processCount = parseInt(lastLine.trim()); return processCount <= 1; } catch (error) { return false; } }, description: `Waiting for processes to complete in ${sessionName}:${windowIndex}` }; return this.waitForCondition(condition, { timeout: 60000, interval: 2000, exponentialBackoff: true, ...options }); } static async waitForNetwork(host = 'google.com', options = {}) { const { exec } = require('child_process'); const { promisify } = require('util'); const execAsync = promisify(exec); const condition = { check: async () => { try { await execAsync(`ping -c 1 -W 2 ${host}`, { timeout: 3000 }); return true; } catch { return false; } }, description: `Waiting for network connectivity to ${host}` }; return this.waitForCondition(condition, { timeout: 30000, interval: 2000, exponentialBackoff: true, ...options }); } static combineConditions(...conditions) { return { check: async () => { for (const condition of conditions) { const result = await condition.check(); if (!result) return false; } return true; }, description: `Combined conditions: ${conditions.map(c => c.description).join(' AND ')}` }; } static anyCondition(...conditions) { return { check: async () => { for (const condition of conditions) { const result = await condition.check(); if (result) return true; } return false; }, description: `Any condition: ${conditions.map(c => c.description).join(' OR ')}` }; } } exports.ConditionWaiter = ConditionWaiter; ConditionWaiter.DEFAULT_TIMEOUT = 120000; ConditionWaiter.DEFAULT_INTERVAL = 500; ConditionWaiter.DEFAULT_MIN_INTERVAL = 100; ConditionWaiter.DEFAULT_MAX_INTERVAL = 5000; ConditionWaiter.DEFAULT_RETRIES = 60; //# sourceMappingURL=conditionWaiter.js.map