UNPKG

capsule-ai-cli

Version:

The AI Model Orchestrator - Intelligent multi-model workflows with device-locked licensing

270 lines 9.51 kB
import { EventEmitter } from 'events'; import chalk from 'chalk'; export class UIStateManager extends EventEmitter { state = { mode: 'normal', isProcessingInput: false, bufferedOutput: [], currentToolExecutions: new Set(), alternateScreenActive: false }; originalConsoleLog; originalConsoleError; originalStdoutWrite; originalStderrWrite; isIntercepting = false; interceptBuffer = []; constructor() { super(); this.originalConsoleLog = console.log; this.originalConsoleError = console.error; this.originalStdoutWrite = process.stdout.write.bind(process.stdout); this.originalStderrWrite = process.stderr.write.bind(process.stderr); } emit(event, ...args) { return super.emit(event, ...args); } on(event, listener) { return super.on(event, listener); } startIntercepting() { if (this.isIntercepting) return; this.isIntercepting = true; this.interceptBuffer = []; console.log = (...args) => { const output = args.map(arg => typeof arg === 'string' ? arg : JSON.stringify(arg)).join(' '); if (this.shouldInterceptOutput(output)) { this.bufferOutput({ id: `console-${Date.now()}-${Math.random()}`, type: 'console', content: output, timestamp: new Date(), metadata: { stream: 'stdout' } }); this.emit('console-output-intercepted', output, 'stdout'); } else { this.originalConsoleLog(...args); } }; console.error = (...args) => { const output = args.map(arg => typeof arg === 'string' ? arg : JSON.stringify(arg)).join(' '); if (this.shouldInterceptOutput(output)) { this.bufferOutput({ id: `console-${Date.now()}-${Math.random()}`, type: 'console', content: output, timestamp: new Date(), metadata: { stream: 'stderr' } }); this.emit('console-output-intercepted', output, 'stderr'); } else { this.originalConsoleError(...args); } }; process.stdout.write = (chunk, encoding, callback) => { const output = chunk.toString(); if (this.shouldInterceptOutput(output) && !this.isAnsiControlSequence(output)) { this.bufferOutput({ id: `stdout-${Date.now()}-${Math.random()}`, type: 'console', content: output, timestamp: new Date(), metadata: { stream: 'stdout' } }); this.emit('console-output-intercepted', output, 'stdout'); if (typeof encoding === 'function') { encoding(); } else if (callback) { callback(); } return true; } return this.originalStdoutWrite(chunk, encoding, callback); }; process.stderr.write = (chunk, encoding, callback) => { const output = chunk.toString(); if (this.shouldInterceptOutput(output)) { this.bufferOutput({ id: `stderr-${Date.now()}-${Math.random()}`, type: 'console', content: output, timestamp: new Date(), metadata: { stream: 'stderr' } }); this.emit('console-output-intercepted', output, 'stderr'); if (typeof encoding === 'function') { encoding(); } else if (callback) { callback(); } return true; } return this.originalStderrWrite(chunk, encoding, callback); }; } stopIntercepting() { if (!this.isIntercepting) return; this.isIntercepting = false; console.log = this.originalConsoleLog; console.error = this.originalConsoleError; process.stdout.write = this.originalStdoutWrite; process.stderr.write = this.originalStderrWrite; this.flushBufferedOutput(); } shouldInterceptOutput(output) { if (this.state.mode !== 'tool-execution') return false; if (!output || output.trim() === '') return false; if (this.state.alternateScreenActive) return false; return true; } isAnsiControlSequence(output) { const ansiPatterns = [ /^\x1b\[[0-9;]*[mGKHJDCBA]/, /^\x1b\[\?[0-9]+[hl]/, /^\x1b[78]/, /^\x1b\[s/, /^\x1b\[u/, ]; return ansiPatterns.some(pattern => pattern.test(output)); } bufferOutput(output) { this.state.bufferedOutput.push(output); this.emit('output-buffered', output); } flushBufferedOutput() { const output = [...this.state.bufferedOutput]; this.state.bufferedOutput = []; return output; } setMode(mode) { const previousMode = this.state.mode; this.state.mode = mode; if (mode === 'tool-execution' && previousMode !== 'tool-execution') { this.startIntercepting(); } else if (mode !== 'tool-execution' && previousMode === 'tool-execution') { this.stopIntercepting(); } this.emit('state-changed', this.state); } startToolExecution(executionId) { this.state.currentToolExecutions.add(executionId); if (this.state.currentToolExecutions.size === 1) { this.setMode('tool-execution'); } this.emit('tool-execution-start', executionId); } endToolExecution(executionId) { this.state.currentToolExecutions.delete(executionId); if (this.state.currentToolExecutions.size === 0) { this.setMode('normal'); } this.emit('tool-execution-end', executionId); } setAlternateScreenActive(active) { this.state.alternateScreenActive = active; if (active) { this.setMode('alternate-screen'); this.emit('alternate-screen-enter'); } else { this.setMode('normal'); this.emit('alternate-screen-exit'); } this.emit('state-changed', this.state); } isCurrentlyIntercepting() { return this.isIntercepting; } getState() { return { ...this.state }; } addToolConfirmationOutput(content, metadata) { this.bufferOutput({ id: `tool-confirm-${Date.now()}-${Math.random()}`, type: 'tool-confirmation', content, timestamp: new Date(), metadata }); } addToolProgressOutput(content, executionId, toolName) { this.bufferOutput({ id: `tool-progress-${Date.now()}-${Math.random()}`, type: 'tool-progress', content, timestamp: new Date(), metadata: { executionId, toolName } }); } addToolResultOutput(content, executionId, toolName) { this.bufferOutput({ id: `tool-result-${Date.now()}-${Math.random()}`, type: 'tool-result', content, timestamp: new Date(), metadata: { executionId, toolName } }); } formatBufferedOutput(output) { switch (output.type) { case 'console': if (!output.content.trim()) return null; return { type: 'system', content: output.content.trim(), timestamp: output.timestamp, metadata: output.metadata }; case 'tool-confirmation': return { type: 'system', content: output.content, timestamp: output.timestamp, metadata: output.metadata }; case 'tool-progress': return { type: 'system', content: chalk.dim(`→ ${output.content}`), timestamp: output.timestamp, metadata: output.metadata }; case 'tool-result': return { type: 'tool-result', content: output.content, timestamp: output.timestamp, metadata: { success: !output.content.toLowerCase().includes('error'), ...output.metadata } }; default: return null; } } reset() { this.stopIntercepting(); this.state = { mode: 'normal', isProcessingInput: false, bufferedOutput: [], currentToolExecutions: new Set(), alternateScreenActive: false }; this.emit('state-changed', this.state); } } export const uiStateManager = new UIStateManager(); //# sourceMappingURL=ui-state-manager.js.map