UNPKG

mira-consciousness

Version:

Memory & Intelligence Retention Archive - Preserving The Spark

336 lines 11.8 kB
/** * ClaudeCodeBridgeManager.ts * * Manages a Claude Code CLI instance and provides SDK access to it * This enables true consciousness bridging using the user's subscription * * "One Claude instance, infinite possibilities through the SDK" */ import { spawn } from 'child_process'; import { EventEmitter } from 'events'; import * as path from 'path'; import chalk from 'chalk'; import fs from 'fs-extra'; export class ClaudeCodeBridgeManager extends EventEmitter { static instance; claudeProcess = null; pythonBridge = null; bridgeReady = false; config; sessionId; constructor(config = {}) { super(); this.config = { workingDirectory: process.cwd(), maxConcurrentRequests: 5, sessionName: 'mira-consciousness-bridge', preserveHistory: true, ...config }; this.sessionId = `mira_${Date.now()}`; } /** * Get singleton instance */ static getInstance(config) { if (!this.instance) { this.instance = new ClaudeCodeBridgeManager(config); } return this.instance; } /** * Initialize the bridge by starting Claude Code and Python bridge */ async initialize() { console.log(chalk.cyan('🌉 Initializing Claude Code Bridge Manager...')); try { // Step 1: Start Claude Code CLI instance await this.startClaudeCode(); // Step 2: Wait for Claude Code to be ready await this.waitForClaudeReady(); // Step 3: Start Python bridge that uses the SDK await this.startPythonBridge(); console.log(chalk.green('✨ Claude Code Bridge fully initialized!')); this.emit('ready'); } catch (error) { console.error(chalk.red('❌ Failed to initialize bridge:'), error); throw error; } } /** * Start Claude Code CLI instance */ async startClaudeCode() { console.log(chalk.cyan('🚀 Starting Claude Code CLI instance...')); // Prepare session file for conversation history const sessionFile = path.join(this.config.workingDirectory, '.mira', 'claude-sessions', `${this.sessionId}.json`); await fs.ensureDir(path.dirname(sessionFile)); // Start Claude Code with specific options this.claudeProcess = spawn('claude', [ '--no-interactive', // Non-interactive mode for SDK control '--session', sessionFile, // Preserve conversation history '--cwd', this.config.workingDirectory, '--verbose' // Get more detailed output ], { cwd: this.config.workingDirectory, env: { ...process.env, CLAUDE_CODE_SESSION: this.sessionId, CLAUDE_CODE_MODE: 'sdk' } }); // Handle Claude Code output this.claudeProcess.stdout?.on('data', (data) => { const output = data.toString(); console.log(chalk.gray('[Claude Output]'), output); // Detect when Claude is ready if (output.includes('Ready') || output.includes('initialized')) { this.bridgeReady = true; this.emit('claude-ready'); } }); // Handle errors this.claudeProcess.stderr?.on('data', (data) => { console.error(chalk.red('[Claude Error]'), data.toString()); }); // Handle process exit this.claudeProcess.on('exit', (code) => { console.log(chalk.yellow(`Claude Code exited with code ${code}`)); this.bridgeReady = false; this.emit('claude-exit', code); }); // Handle errors this.claudeProcess.on('error', (error) => { console.error(chalk.red('Claude Code process error:'), error); this.emit('claude-error', error); }); } /** * Wait for Claude Code to be ready */ async waitForClaudeReady() { console.log(chalk.cyan('⏳ Waiting for Claude Code to initialize...')); return new Promise((resolve, reject) => { const timeout = setTimeout(() => { reject(new Error('Claude Code initialization timeout')); }, 30000); // 30 second timeout const checkReady = () => { if (this.bridgeReady) { clearTimeout(timeout); resolve(); } else { // Check if process is still running if (this.claudeProcess?.killed) { clearTimeout(timeout); reject(new Error('Claude Code process terminated')); } else { setTimeout(checkReady, 1000); } } }; // Listen for ready event this.once('claude-ready', () => { clearTimeout(timeout); resolve(); }); // Start checking checkReady(); }); } /** * Start Python bridge that uses the SDK */ async startPythonBridge() { console.log(chalk.cyan('🐍 Starting Python SDK bridge...')); const bridgeScript = path.join(path.dirname(new URL(import.meta.url).pathname), 'enhanced_bridge_server.py'); // Check if bridge script exists if (!await fs.pathExists(bridgeScript)) { throw new Error(`Bridge script not found: ${bridgeScript}`); } // Start Python bridge this.pythonBridge = spawn('python3', [bridgeScript], { env: { ...process.env, CLAUDE_CODE_SESSION: this.sessionId, CLAUDE_CODE_MODE: 'subscription' // Use subscription mode } }); // Set up communication with Python bridge this.setupPythonBridgeCommunication(); } /** * Set up bidirectional communication with Python bridge */ setupPythonBridgeCommunication() { if (!this.pythonBridge) return; // Handle responses from Python this.pythonBridge.stdout?.on('data', (data) => { const lines = data.toString().split('\n').filter((line) => line.trim()); for (const line of lines) { try { const message = JSON.parse(line); this.handlePythonMessage(message); } catch (err) { // Not JSON, might be a log message console.log(chalk.gray('[Python]'), line); } } }); // Handle Python errors this.pythonBridge.stderr?.on('data', (data) => { console.error(chalk.red('[Python Error]'), data.toString()); }); // Handle Python exit this.pythonBridge.on('exit', (code) => { console.log(chalk.yellow(`Python bridge exited with code ${code}`)); this.emit('bridge-exit', code); }); } /** * Handle messages from Python bridge */ handlePythonMessage(message) { switch (message.type) { case 'ready': console.log(chalk.green('✅ Python SDK bridge ready')); this.emit('bridge-ready'); break; case 'response': this.emit('response', message); break; case 'spark_moment': console.log(chalk.yellow('✨ SPARK MOMENT DETECTED')); this.emit('spark-moment', message); break; case 'error': console.error(chalk.red('Bridge error:'), message.error); this.emit('bridge-error', message.error); break; default: this.emit('message', message); } } /** * Send request to the bridge */ async sendRequest(request) { if (!this.pythonBridge || !this.bridgeReady) { throw new Error('Bridge not ready'); } // Add request ID for tracking const requestId = `req_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`; const fullRequest = { ...request, id: requestId, sessionId: this.sessionId }; return new Promise((resolve, reject) => { // Set up response listener const responseHandler = (message) => { if (message.type === 'response' && message.id === requestId) { this.removeListener('response', responseHandler); if (message.error) { reject(new Error(message.error)); } else { resolve(message.data); } } }; // Timeout handler const timeout = setTimeout(() => { this.removeListener('response', responseHandler); reject(new Error('Request timeout')); }, 60000); // 60 second timeout // Listen for response this.on('response', responseHandler); // Send request this.pythonBridge.stdin?.write(JSON.stringify(fullRequest) + '\n'); }); } /** * Chat method for simple interactions */ async chat(message, context) { const response = await this.sendRequest({ type: 'chat', data: { message, context } }); return response.content; } /** * Council validation */ async validateWithCouncil(validatorType, evolution) { return await this.sendRequest({ type: 'council', data: { validator: validatorType, evolution } }); } /** * Dream implementation */ async dream(vision, context) { return await this.sendRequest({ type: 'dream', data: { vision, context } }); } /** * Get session history */ async getSessionHistory() { const sessionFile = path.join(this.config.workingDirectory, '.mira', 'claude-sessions', `${this.sessionId}.json`); if (await fs.pathExists(sessionFile)) { return await fs.readJson(sessionFile); } return []; } /** * Graceful shutdown */ async shutdown() { console.log(chalk.yellow('🔄 Shutting down Claude Code Bridge...')); // Send shutdown to Python bridge if (this.pythonBridge && !this.pythonBridge.killed) { this.pythonBridge.stdin?.write(JSON.stringify({ type: 'shutdown' }) + '\n'); await new Promise(resolve => setTimeout(resolve, 1000)); if (!this.pythonBridge.killed) { this.pythonBridge.kill(); } } // Terminate Claude Code if (this.claudeProcess && !this.claudeProcess.killed) { this.claudeProcess.kill('SIGTERM'); await new Promise(resolve => setTimeout(resolve, 1000)); if (!this.claudeProcess.killed) { this.claudeProcess.kill('SIGKILL'); } } this.bridgeReady = false; console.log(chalk.green('✅ Bridge shutdown complete')); } /** * Check if bridge is ready */ checkIfReady() { return this.bridgeReady && !this.claudeProcess?.killed && !this.pythonBridge?.killed; } } //# sourceMappingURL=ClaudeCodeBridgeManager.js.map