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
JavaScript
"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