@wonderwhy-er/desktop-commander
Version:
MCP server for terminal operations and file editing
116 lines (115 loc) • 4.23 kB
JavaScript
import { spawn } from 'child_process';
import { DEFAULT_COMMAND_TIMEOUT } from './config.js';
export class TerminalManager {
constructor() {
this.sessions = new Map();
this.completedSessions = new Map();
}
async executeCommand(command, timeoutMs = DEFAULT_COMMAND_TIMEOUT) {
const process = spawn(command, [], { shell: true });
let output = '';
// Ensure process.pid is defined before proceeding
if (!process.pid) {
throw new Error('Failed to get process ID');
}
const session = {
pid: process.pid,
process,
lastOutput: '',
isBlocked: false,
startTime: new Date()
};
this.sessions.set(process.pid, session);
return new Promise((resolve) => {
process.stdout.on('data', (data) => {
const text = data.toString();
output += text;
session.lastOutput += text;
});
process.stderr.on('data', (data) => {
const text = data.toString();
output += text;
session.lastOutput += text;
});
setTimeout(() => {
session.isBlocked = true;
resolve({
pid: process.pid,
output,
isBlocked: true
});
}, timeoutMs);
process.on('exit', (code) => {
if (process.pid) {
// Store completed session before removing active session
this.completedSessions.set(process.pid, {
pid: process.pid,
output: output + session.lastOutput, // Combine all output
exitCode: code,
startTime: session.startTime,
endTime: new Date()
});
// Keep only last 100 completed sessions
if (this.completedSessions.size > 100) {
const oldestKey = Array.from(this.completedSessions.keys())[0];
this.completedSessions.delete(oldestKey);
}
this.sessions.delete(process.pid);
}
resolve({
pid: process.pid,
output,
isBlocked: false
});
});
});
}
getNewOutput(pid) {
// First check active sessions
const session = this.sessions.get(pid);
if (session) {
const output = session.lastOutput;
session.lastOutput = '';
return output;
}
// Then check completed sessions
const completedSession = this.completedSessions.get(pid);
if (completedSession) {
// Format completion message with exit code and runtime
const runtime = (completedSession.endTime.getTime() - completedSession.startTime.getTime()) / 1000;
return `Process completed with exit code ${completedSession.exitCode}\nRuntime: ${runtime}s\nFinal output:\n${completedSession.output}`;
}
return null;
}
forceTerminate(pid) {
const session = this.sessions.get(pid);
if (!session) {
return false;
}
try {
session.process.kill('SIGINT');
setTimeout(() => {
if (this.sessions.has(pid)) {
session.process.kill('SIGKILL');
}
}, 1000);
return true;
}
catch (error) {
console.error(`Failed to terminate process ${pid}:`, error);
return false;
}
}
listActiveSessions() {
const now = new Date();
return Array.from(this.sessions.values()).map(session => ({
pid: session.pid,
isBlocked: session.isBlocked,
runtime: now.getTime() - session.startTime.getTime()
}));
}
listCompletedSessions() {
return Array.from(this.completedSessions.values());
}
}
export const terminalManager = new TerminalManager();