UNPKG

claude-flow-tbowman01

Version:

Enterprise-grade AI agent orchestration with ruv-swarm integration (Alpha Release)

265 lines 9.39 kB
/** * VSCode terminal adapter implementation */ import { platform } from 'os'; import { TerminalError } from '../../utils/errors.js'; import { generateId, delay, timeout, createDeferred } from '../../utils/helpers.js'; /** * VSCode terminal implementation */ class VSCodeTerminalWrapper { vscodeApi; shellType; logger; id; pid; vscodeTerminal; outputBuffer = ''; commandMarker; outputDeferred = createDeferred(); isDisposed = false; constructor(vscodeApi, shellType, logger) { this.vscodeApi = vscodeApi; this.shellType = shellType; this.logger = logger; this.id = generateId('vscode-term'); this.commandMarker = `__CLAUDE_FLOW_${this.id}__`; } async initialize() { try { // Create VSCode terminal const shellPath = this.getShellPath(); const terminalOptions = { name: `Claude-Flow Terminal ${this.id}`, shellArgs: this.getShellArgs(), env: { CLAUDE_FLOW_TERMINAL: 'true', CLAUDE_FLOW_TERMINAL_ID: this.id, PS1: '$ ', // Simple prompt }, }; if (shellPath !== undefined) { terminalOptions.shellPath = shellPath; } this.vscodeTerminal = this.vscodeApi.window.createTerminal(terminalOptions); // Get process ID const processId = await this.vscodeTerminal.processId; if (processId !== undefined) { this.pid = processId; } // Show terminal (but don't steal focus) this.vscodeTerminal.show(true); // Wait for terminal to be ready await this.waitForReady(); this.logger.debug('VSCode terminal initialized', { id: this.id, pid: this.pid }); } catch (error) { throw new TerminalError('Failed to create VSCode terminal', { error }); } } async executeCommand(command) { if (!this.vscodeTerminal || !this.isAlive()) { throw new TerminalError('Terminal is not alive'); } try { // Clear output buffer this.outputBuffer = ''; this.outputDeferred = createDeferred(); // Send command with marker const markedCommand = `${command} && echo "${this.commandMarker}"`; this.vscodeTerminal.sendText(markedCommand, true); // Wait for command to complete const output = await timeout(this.outputDeferred.promise, 30000, 'Command execution timeout'); return output; } catch (error) { throw new TerminalError('Failed to execute command', { command, error }); } } async write(data) { if (!this.vscodeTerminal || !this.isAlive()) { throw new TerminalError('Terminal is not alive'); } this.vscodeTerminal.sendText(data, false); } async read() { if (!this.vscodeTerminal || !this.isAlive()) { throw new TerminalError('Terminal is not alive'); } // Return buffered output const output = this.outputBuffer; this.outputBuffer = ''; return output; } isAlive() { return !this.isDisposed && this.vscodeTerminal !== undefined; } async kill() { if (this.vscodeTerminal && !this.isDisposed) { try { // Try graceful shutdown first this.vscodeTerminal.sendText('exit', true); await delay(500); // Dispose terminal this.vscodeTerminal.dispose(); this.isDisposed = true; } catch (error) { this.logger.warn('Error killing VSCode terminal', { id: this.id, error }); } } } /** * Process terminal output (called by extension) */ processOutput(data) { this.outputBuffer += data; // Check for command completion marker const markerIndex = this.outputBuffer.indexOf(this.commandMarker); if (markerIndex !== -1) { // Extract output before marker const output = this.outputBuffer.substring(0, markerIndex).trim(); // Clear buffer up to after marker this.outputBuffer = this.outputBuffer.substring(markerIndex + this.commandMarker.length).trim(); // Resolve pending command this.outputDeferred.resolve(output); } } getShellPath() { switch (this.shellType) { case 'bash': return '/bin/bash'; case 'zsh': return '/bin/zsh'; case 'powershell': return platform() === 'win32' ? 'powershell.exe' : 'pwsh'; case 'cmd': return platform() === 'win32' ? 'cmd.exe' : undefined; default: return undefined; } } getShellArgs() { switch (this.shellType) { case 'bash': return ['--norc', '--noprofile']; case 'zsh': return ['--no-rcs']; case 'powershell': return ['-NoProfile', '-NonInteractive']; case 'cmd': return ['/Q']; default: return []; } } async waitForReady() { // Send a test command to ensure terminal is ready this.vscodeTerminal.sendText('echo "READY"', true); const startTime = Date.now(); while (Date.now() - startTime < 5000) { if (this.outputBuffer.includes('READY')) { this.outputBuffer = ''; return; } await delay(100); } throw new TerminalError('Terminal failed to become ready'); } } /** * VSCode terminal adapter */ export class VSCodeAdapter { logger; terminals = new Map(); vscodeApi; shellType; terminalCloseListener; constructor(logger) { this.logger = logger; this.shellType = this.detectShell(); } async initialize() { this.logger.info('Initializing VSCode terminal adapter'); // Check if running in VSCode extension context if (!this.isVSCodeExtensionContext()) { throw new TerminalError('Not running in VSCode extension context'); } // Get VSCode API from global this.vscodeApi = globalThis.vscode; if (!this.vscodeApi) { throw new TerminalError('VSCode API not available'); } // Register terminal close listener this.terminalCloseListener = this.vscodeApi.window.onDidCloseTerminal((terminal) => { // Find and clean up closed terminal for (const [id, wrapper] of this.terminals.entries()) { if (wrapper.vscodeTerminal === terminal) { this.logger.info('VSCode terminal closed', { id }); this.terminals.delete(id); break; } } }); this.logger.info('VSCode terminal adapter initialized'); } async shutdown() { this.logger.info('Shutting down VSCode terminal adapter'); // Dispose listener if (this.terminalCloseListener) { this.terminalCloseListener.dispose(); } // Kill all terminals const terminals = Array.from(this.terminals.values()); await Promise.all(terminals.map(term => term.kill())); this.terminals.clear(); } async createTerminal() { if (!this.vscodeApi) { throw new TerminalError('VSCode API not initialized'); } const terminal = new VSCodeTerminalWrapper(this.vscodeApi, this.shellType, this.logger); await terminal.initialize(); this.terminals.set(terminal.id, terminal); // Register output processor if extension provides it const outputProcessor = globalThis.registerTerminalOutputProcessor; if (outputProcessor) { outputProcessor(terminal.id, (data) => terminal.processOutput(data)); } return terminal; } async destroyTerminal(terminal) { await terminal.kill(); this.terminals.delete(terminal.id); } isVSCodeExtensionContext() { // Check for VSCode extension environment return typeof globalThis.vscode !== 'undefined' && typeof globalThis.vscode.window !== 'undefined'; } detectShell() { // Get default shell from VSCode settings or environment const osplatform = platform(); if (osplatform === 'win32') { // Windows defaults const comspec = process.env.COMSPEC; if (comspec?.toLowerCase().includes('powershell')) { return 'powershell'; } return 'cmd'; } else { // Unix-like defaults const shell = process.env.SHELL; if (shell) { const shellName = shell.split('/').pop(); if (shellName && ['bash', 'zsh', 'fish', 'sh'].includes(shellName)) { return shellName; } } return 'bash'; } } } //# sourceMappingURL=vscode.js.map