capsule-ai-cli
Version:
The AI Model Orchestrator - Intelligent multi-model workflows with device-locked licensing
270 lines • 9.51 kB
JavaScript
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