@sirmrmarty/n8n-nodes-tmux-orchestrator
Version:
n8n nodes for orchestrating Claude AI agents through tmux sessions
309 lines • 12 kB
JavaScript
;
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