@sethdouglasford/claude-flow
Version:
Claude Code Flow - Advanced AI-powered development workflows with SPARC methodology
209 lines • 7.1 kB
JavaScript
/**
* Terminal session management
*/
import { TerminalCommandError } from "../utils/errors.js";
import { generateId, timeout } from "../utils/helpers.js";
/**
* Terminal session wrapper
*/
export class TerminalSession {
terminal;
profile;
commandTimeout;
logger;
id;
startTime;
initialized = false;
commandHistory = [];
lastCommandTime;
outputListeners = new Set();
constructor(terminal, profile, commandTimeout, logger) {
this.terminal = terminal;
this.profile = profile;
this.commandTimeout = commandTimeout;
this.logger = logger;
this.id = generateId("session");
this.startTime = new Date();
}
get lastActivity() {
return this.lastCommandTime ?? this.startTime;
}
async initialize() {
if (this.initialized) {
return;
}
this.logger.debug("Initializing terminal session", {
sessionId: this.id,
agentId: this.profile.id,
});
try {
// Set up environment
await this.setupEnvironment();
// Run initialization commands
await this.runInitializationCommands();
this.initialized = true;
this.logger.info("Terminal session initialized", {
sessionId: this.id,
agentId: this.profile.id,
});
}
catch (error) {
this.logger.error("Failed to initialize terminal session", error);
throw error;
}
}
async executeCommand(command) {
if (!this.initialized) {
throw new TerminalCommandError("Session not initialized");
}
if (!this.terminal.isAlive()) {
throw new TerminalCommandError("Terminal is not alive");
}
this.logger.debug("Executing command", {
sessionId: this.id,
command: command.substring(0, 100),
});
try {
// Notify listeners of command
this.notifyOutputListeners(`$ ${command}\n`);
// Execute with timeout
const result = await timeout(this.terminal.executeCommand(command), this.commandTimeout, `Command timeout after ${this.commandTimeout}ms`);
// Notify listeners of output
this.notifyOutputListeners(result);
// Update history
this.commandHistory.push(command);
this.lastCommandTime = new Date();
this.logger.debug("Command executed successfully", {
sessionId: this.id,
outputLength: result.length,
});
return result;
}
catch (error) {
this.logger.error("Command execution failed", {
sessionId: this.id,
command,
error,
});
throw new TerminalCommandError("Command execution failed", {
command,
error,
});
}
}
async cleanup() {
this.logger.debug("Cleaning up terminal session", { sessionId: this.id });
try {
// Run cleanup commands
await this.runCleanupCommands();
}
catch (error) {
this.logger.warn("Error during session cleanup", {
sessionId: this.id,
error,
});
}
}
isHealthy() {
if (!this.terminal.isAlive()) {
return false;
}
// Check if terminal is responsive
if (this.lastCommandTime) {
const timeSinceLastCommand = Date.now() - this.lastCommandTime.getTime();
if (timeSinceLastCommand > 300000) { // 5 minutes
// Terminal might be stale, do a health check
this.performHealthCheck().catch((error) => {
this.logger.warn("Health check failed", { sessionId: this.id, error });
});
}
}
return true;
}
getCommandHistory() {
return [...this.commandHistory];
}
async setupEnvironment() {
// Set environment variables
const envVars = {
CLAUDE_FLOW_SESSION: this.id,
CLAUDE_FLOW_AGENT: this.profile.id,
CLAUDE_FLOW_AGENT_TYPE: this.profile.type,
};
for (const [key, value] of Object.entries(envVars)) {
await this.terminal.executeCommand(`export ${key}="${value}"`);
}
// Set working directory if specified
if (this.profile.metadata?.workingDirectory) {
await this.terminal.executeCommand(`cd "${String(this.profile.metadata.workingDirectory)}"`);
}
}
async runInitializationCommands() {
// Run any profile-specific initialization commands
if (this.profile.metadata?.initCommands) {
const commands = this.profile.metadata.initCommands;
for (const command of commands) {
await this.terminal.executeCommand(command);
}
}
// Set up command prompt
await this.terminal.executeCommand("export PS1=\"[claude-flow]$ \"");
}
async runCleanupCommands() {
// Run any profile-specific cleanup commands
if (this.profile.metadata?.cleanupCommands) {
const commands = this.profile.metadata.cleanupCommands;
for (const command of commands) {
try {
await this.terminal.executeCommand(command);
}
catch {
// Ignore cleanup errors
}
}
}
}
async performHealthCheck() {
try {
const result = await timeout(this.terminal.executeCommand("echo \"HEALTH_CHECK_OK\""), 5000, "Health check timeout");
if (!result.includes("HEALTH_CHECK_OK")) {
throw new Error("Invalid health check response");
}
this.lastCommandTime = new Date();
}
catch (error) {
throw new Error(`Health check failed: ${error instanceof Error ? error.message : String(error)}`);
}
}
/**
* Stream terminal output
*/
streamOutput(callback) {
this.outputListeners.add(callback);
// Set up terminal output listener if supported
if (this.terminal.addOutputListener) {
this.terminal.addOutputListener(callback);
}
// Return unsubscribe function
return () => {
this.outputListeners.delete(callback);
if (this.terminal.removeOutputListener) {
this.terminal.removeOutputListener(callback);
}
};
}
/**
* Notify output listeners
*/
notifyOutputListeners(output) {
this.outputListeners.forEach(listener => {
try {
listener(output);
}
catch (error) {
this.logger.error("Error in output listener", { sessionId: this.id, error });
}
});
}
}
//# sourceMappingURL=session.js.map