UNPKG

@sethdouglasford/claude-flow

Version:

Claude Code Flow - Advanced AI-powered development workflows with SPARC methodology

251 lines 9.45 kB
/** * Terminal manager interface and implementation */ import { TerminalError, TerminalSpawnError } from "../utils/errors.js"; import { VSCodeAdapter } from "./adapters/vscode.js"; import { NativeAdapter } from "./adapters/native.js"; import { TerminalPool } from "./pool.js"; import { TerminalSession } from "./session.js"; /** * Terminal manager implementation */ export class TerminalManager { config; eventBus; logger; adapter; pool; sessions = new Map(); initialized = false; constructor(config, eventBus, logger) { this.config = config; this.eventBus = eventBus; this.logger = logger; // Select adapter based on configuration this.adapter = this.createAdapter(); // Create terminal pool this.pool = new TerminalPool(this.config.poolSize, this.config.recycleAfter, this.adapter, this.logger); } async initialize() { if (this.initialized) { return; } this.logger.info("Initializing terminal manager..."); try { // Initialize adapter await this.adapter.initialize(); // Initialize pool await this.pool.initialize(); this.initialized = true; this.logger.info("Terminal manager initialized"); } catch (error) { this.logger.error("Failed to initialize terminal manager", error); throw new TerminalError("Terminal manager initialization failed", { error }); } } async shutdown() { if (!this.initialized) { return; } this.logger.info("Shutting down terminal manager..."); try { // Terminate all sessions const sessionIds = Array.from(this.sessions.keys()); await Promise.all(sessionIds.map((id) => this.terminateTerminal(id))); // Shutdown pool await this.pool.shutdown(); // Shutdown adapter await this.adapter.shutdown(); this.initialized = false; this.logger.info("Terminal manager shutdown complete"); } catch (error) { this.logger.error("Error during terminal manager shutdown", error); throw error; } } async spawnTerminal(profile) { if (!this.initialized) { throw new TerminalError("Terminal manager not initialized"); } this.logger.debug("Spawning terminal", { agentId: profile.id }); try { // Get terminal from pool const terminal = await this.pool.acquire(); // Create session const session = new TerminalSession(terminal, profile, this.config.commandTimeout, this.logger); // Initialize session await session.initialize(); // Store session this.sessions.set(session.id, session); this.logger.info("Terminal spawned", { terminalId: session.id, agentId: profile.id, }); return session.id; } catch (error) { this.logger.error("Failed to spawn terminal", error); throw new TerminalSpawnError("Failed to spawn terminal", { error }); } } async terminateTerminal(terminalId) { const session = this.sessions.get(terminalId); if (!session) { throw new TerminalError(`Terminal not found: ${terminalId}`); } this.logger.debug("Terminating terminal", { terminalId }); try { // Cleanup session await session.cleanup(); // Return terminal to pool await this.pool.release(session.terminal); // Remove session this.sessions.delete(terminalId); this.logger.info("Terminal terminated", { terminalId }); } catch (error) { this.logger.error("Failed to terminate terminal", error); throw error; } } async executeCommand(terminalId, command) { const session = this.sessions.get(terminalId); if (!session) { throw new TerminalError(`Terminal not found: ${terminalId}`); } return await session.executeCommand(command); } async getHealthStatus() { try { const poolHealth = await this.pool.getHealthStatus(); const activeSessions = this.sessions.size; const healthySessions = Array.from(this.sessions.values()) .filter((session) => session.isHealthy()).length; const metrics = { activeSessions, healthySessions, poolSize: poolHealth.size, availableTerminals: poolHealth.available, recycledTerminals: poolHealth.recycled, }; const healthy = poolHealth.healthy && healthySessions === activeSessions; if (healthy) { return { healthy, metrics, }; } else { return { healthy, metrics, error: "Some terminals are unhealthy", }; } } catch (error) { return { healthy: false, error: error instanceof Error ? error.message : "Unknown error", }; } } async performMaintenance() { if (!this.initialized) { return; } this.logger.debug("Performing terminal manager maintenance"); try { // Clean up dead sessions const deadSessions = Array.from(this.sessions.entries()) .filter(([_, session]) => !session.isHealthy()); for (const [terminalId, _] of deadSessions) { this.logger.warn("Cleaning up dead terminal session", { terminalId }); await this.terminateTerminal(terminalId).catch(error => this.logger.error("Failed to clean up terminal", { terminalId, error })); } // Perform pool maintenance await this.pool.performMaintenance(); // Emit maintenance event this.eventBus.emit("terminal:maintenance", { deadSessions: deadSessions.length, activeSessions: this.sessions.size, poolStatus: await this.pool.getHealthStatus(), }); this.logger.debug("Terminal manager maintenance completed"); } catch (error) { this.logger.error("Error during terminal manager maintenance", error); } } /** * Get all active sessions */ getActiveSessions() { return Array.from(this.sessions.values()).map(session => ({ id: session.id, agentId: session.profile.id, terminalId: session.terminal.id, startTime: session.startTime, status: session.isHealthy() ? "active" : "error", lastActivity: session.lastActivity, memoryBankId: "", // TODO: Link to memory bank })); } /** * Get session by ID */ getSession(sessionId) { return this.sessions.get(sessionId); } /** * Stream terminal output */ async streamOutput(terminalId, callback) { const session = this.sessions.get(terminalId); if (!session) { throw new TerminalError(`Terminal not found: ${terminalId}`); } return session.streamOutput(callback); } createAdapter() { switch (this.config.type) { case "vscode": return new VSCodeAdapter(this.logger); case "native": return new NativeAdapter(this.logger); case "auto": // Detect environment and choose appropriate adapter if (this.isVSCodeEnvironment()) { this.logger.info("Detected VSCode environment, using VSCode adapter"); return new VSCodeAdapter(this.logger); } else { this.logger.info("Using native terminal adapter"); return new NativeAdapter(this.logger); } default: throw new TerminalError(`Unknown terminal type: ${this.config.type}`); } } isVSCodeEnvironment() { // Check if we're actually running as a VSCode extension, not just in a VSCode terminal // VSCode extensions have access to the global 'vscode' object and specific APIs try { // Check if we have the VSCode extension API available const hasVSCodeAPI = typeof globalThis.vscode !== "undefined" && typeof globalThis.vscode.window !== "undefined"; // Check if we're in a VSCode extension host process const isExtensionHost = process.env.VSCODE_EXTHOST_WILL_SEND_SOCKET !== undefined || process.env.VSCODE_EXTENSION_HOST_WILL_SEND_SOCKET !== undefined; // Only return true if we have actual VSCode extension capabilities return hasVSCodeAPI && isExtensionHost; } catch (error) { // If any check fails, assume we're not in a VSCode extension return false; } } } //# sourceMappingURL=manager.js.map