UNPKG

cortexweaver

Version:

CortexWeaver is a command-line interface (CLI) tool that orchestrates a swarm of specialized AI agents, powered by Claude Code and Gemini CLI, to assist in software development. It transforms a high-level project plan (plan.md) into a series of coordinate

211 lines 8.47 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.SessionManager = void 0; const child_process_1 = require("child_process"); const util_1 = require("util"); const execAsync = (0, util_1.promisify)(child_process_1.exec); class SessionManager { constructor() { this.sessions = new Map(); // Initialize session manager } async createSession(taskId, workingDirectory) { const sessionId = `cortex-${taskId}-${Date.now()}`; try { // Create detached tmux session await execAsync(`tmux new-session -d -s ${sessionId} -c ${workingDirectory}`); const sessionInfo = { sessionId, taskId, status: 'running', createdAt: new Date() }; this.sessions.set(sessionId, sessionInfo); return sessionInfo; } catch (error) { throw new Error(`Failed to create tmux session: ${error.message}`); } } async runCommandInSession(sessionId, command, timeout = 30000) { const session = this.sessions.get(sessionId); if (!session) { throw new Error(`Session ${sessionId} not found`); } try { // Check if session exists await this.checkSessionExists(sessionId); // Create a unique marker to detect command completion const marker = `CMD_COMPLETE_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`; // Send command with completion marker await execAsync(`tmux send-keys -t ${sessionId} "${command.replace(/"/g, '\\"')}; echo '${marker}'" Enter`); // Poll for command completion with timeout const startTime = Date.now(); let output = ''; let exitCode = 0; while (Date.now() - startTime < timeout) { const { stdout } = await execAsync(`tmux capture-pane -t ${sessionId} -p`); output = stdout; if (output.includes(marker)) { // Extract actual command output (everything before the marker) const lines = output.split('\n'); const markerIndex = lines.findIndex(line => line.includes(marker)); if (markerIndex >= 0) { output = lines.slice(0, markerIndex).join('\n'); break; } } // Wait before next poll await new Promise(resolve => setTimeout(resolve, 500)); } // Check for timeout if (Date.now() - startTime >= timeout) { throw new Error(`Command execution timeout after ${timeout}ms`); } return { output: output.trim(), exitCode, timestamp: new Date() }; } catch (error) { throw new Error(`Failed to run command in session: ${error.message}`); } } async attachToSession(sessionId) { const session = this.sessions.get(sessionId); if (!session) { throw new Error(`Session ${sessionId} not found`); } try { await this.checkSessionExists(sessionId); return `tmux attach-session -t ${sessionId}`; } catch (error) { throw new Error(`Session ${sessionId} not found or not running`); } } async killSession(sessionId) { try { await execAsync(`tmux kill-session -t ${sessionId}`); this.sessions.delete(sessionId); return true; } catch (error) { // Session might not exist this.sessions.delete(sessionId); return false; } } listSessions() { return Array.from(this.sessions.values()); } async getSessionStatus(sessionId) { try { await this.checkSessionExists(sessionId); const session = this.sessions.get(sessionId); return session ? session.status : null; } catch (error) { return null; } } async checkSessionExists(sessionId) { try { await execAsync(`tmux has-session -t ${sessionId}`); return true; } catch (error) { throw new Error(`Session ${sessionId} does not exist`); } } async listActiveTmuxSessions() { try { const { stdout } = await execAsync('tmux list-sessions -F "#{session_name}"'); return stdout.trim().split('\n').filter(name => name.startsWith('cortex-')); } catch (error) { // No tmux sessions running return []; } } async startAgentInSession(sessionId, agentCommand, prompt) { const session = this.sessions.get(sessionId); if (!session) { throw new Error(`Session ${sessionId} not found`); } try { // Write prompt to a temporary file with proper escaping const promptFile = `/tmp/prompt-${sessionId}-${Date.now()}.txt`; const escapedPrompt = prompt.replace(/'/g, "'\"'\"'"); // Escape single quotes for shell await execAsync(`echo '${escapedPrompt}' > ${promptFile}`); // Verify the prompt file was created const { stdout: fileCheck } = await execAsync(`test -f ${promptFile} && echo "exists"`); if (!fileCheck.includes('exists')) { throw new Error('Failed to create prompt file'); } // Start the agent with the prompt, ensuring proper command execution const fullCommand = `${agentCommand} -p --dangerously-skip-permissions < ${promptFile}`; await execAsync(`tmux send-keys -t ${sessionId} '${fullCommand}' Enter`); console.log(`Started agent in session ${sessionId} with command: ${agentCommand}`); // Clean up the prompt file after a delay setTimeout(async () => { try { await execAsync(`rm -f ${promptFile}`); } catch (cleanupError) { console.warn(`Failed to cleanup prompt file: ${cleanupError}`); } }, 5000); } catch (error) { throw new Error(`Failed to start agent: ${error.message}`); } } async monitorSession(sessionId, callback) { // This would be used to monitor session output in real-time // Implementation would depend on specific monitoring requirements try { const session = this.sessions.get(sessionId); if (!session) { throw new Error(`Session ${sessionId} not found`); } // Simplified monitoring - real implementation would use proper tmux monitoring console.log(`Monitoring session ${sessionId}...`); // This is a placeholder - real implementation would set up continuous monitoring setInterval(async () => { try { const { stdout } = await execAsync(`tmux capture-pane -t ${sessionId} -p`); callback(stdout); } catch (error) { console.error(`Error monitoring session: ${error}`); } }, 5000); // Check every 5 seconds } catch (error) { throw new Error(`Failed to monitor session: ${error.message}`); } } async getSessionOutput(sessionId, lines = 100) { try { await this.checkSessionExists(sessionId); const { stdout } = await execAsync(`tmux capture-pane -t ${sessionId} -p -S -${lines}`); return stdout; } catch (error) { throw new Error(`Failed to get session output: ${error.message}`); } } async cleanupDeadSessions() { const activeSessions = await this.listActiveTmuxSessions(); for (const [sessionId, sessionInfo] of this.sessions.entries()) { if (!activeSessions.includes(sessionId)) { this.sessions.delete(sessionId); console.log(`Cleaned up dead session: ${sessionId}`); } } } } exports.SessionManager = SessionManager; //# sourceMappingURL=session.js.map