@sethdouglasford/claude-flow
Version:
Claude Code Flow - Advanced AI-powered development workflows with SPARC methodology
251 lines • 9.45 kB
JavaScript
/**
* 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