# How Desktop Commander Manages Sessions for Long-Running Terminal Commands Across Requests

> Desktop Commander manages long-running terminal sessions using memory-mapped objects and a singleton TerminalManager. Poll for incremental output across HTTP requests.

- Repository: [Eduard Ruzga/DesktopCommanderMCP](https://github.com/wonderwhy-er/DesktopCommanderMCP)
- Tags: internals
- Published: 2026-07-31

---

**Desktop Commander maintains long-running terminal sessions by spawning child processes in a singleton `TerminalManager` that stores session state in memory-mapped objects, enabling clients to poll for incremental output across discrete HTTP requests.**

Desktop Commander, an MCP (Model Context Protocol) server by wonderwhy-er, solves the challenge of persistent terminal execution through a sophisticated session management system. The implementation in [`src/terminal-manager.ts`](https://github.com/wonderwhy-er/DesktopCommanderMCP/blob/main/src/terminal-manager.ts) creates isolated **TerminalSession** objects for each process, allowing commands to outlive individual request cycles. This architecture ensures that long-running tasks like log monitoring or build processes remain accessible even after the initial connection closes.

## Session Lifecycle and Process Management

### Creating a New Terminal Session

When a client invokes `executeCommand`, the `TerminalManager` instantiates a fresh `TerminalSession` and registers it in the private `sessions` Map (lines 69-78 in [`src/terminal-manager.ts`](https://github.com/wonderwhy-er/DesktopCommanderMCP/blob/main/src/terminal-manager.ts)). Each session receives a unique PID that serves as the lookup key for future interactions.

```typescript
// Start a long-running command and obtain the session identifier
const result = await terminalManager.executeCommand('tail -f /var/log/syslog');
const pid = result.pid;   // e.g., 12345

```

### Singleton Persistence Across Requests

The manager exports a singleton instance (`export const terminalManager = new TerminalManager();` at line 753) that persists for the server's lifetime. Because the `sessions` Map (declared at lines 48-49 as `private sessions: Map<number, TerminalSession>`) lives in this singleton, session data survives across separate HTTP requests, enabling asynchronous polling patterns.

## Buffered Output Handling

### Line-Based Buffer with Automatic Eviction

To prevent memory exhaustion during extended executions, Desktop Commander implements a capped line buffer. The `appendToLineBuffer` method enforces `MAX_BUFFERED_OUTPUT_CHARS` (defined at line 56), automatically evicting the oldest lines when the threshold is exceeded (lines 97-103). This circular buffer approach ensures that commands running for hours or days do not crash the server.

### Reading Output with Pagination and Offsets

Clients retrieve output through `readOutputPaginated(pid, offset, length)` (lines 514-530). This method supports three read modes:

- **Zero offset**: Returns new output since the last read using the internal `lastReadIndex`
- **Positive offset**: Reads from an absolute line number
- **Negative offset**: Performs a "tail" read from the buffer end

The response includes `evictedLines`, allowing clients to adjust their line number calculations when content has been dropped from the buffer.

```typescript
// Later, in a different request, read only new output
const newOutput = terminalManager.readOutputPaginated(pid, 0, 200);
console.log(newOutput.lines.join('\n'));
console.log(`Lines evicted since last read: ${newOutput.evictedLines}`);

```

## REPL-Style Interactions with Snapshots

### Capturing Output Snapshots

For interactive REPL workflows, Desktop Commander provides `captureOutputSnapshot(pid)` (lines 558-569). This records the current character count and line count into a snapshot object before sending new input to the process.

### Retrieving Delta Output

After injecting input via `sendInputToProcess`, clients call `getOutputSinceSnapshot(pid, snapshot)` (lines 578-595) to receive only the output generated after the snapshot point. This eliminates the need to re-scan or re-transfer the entire buffer, optimizing network efficiency for chat-style interactions with Python, Node.js, or database shells.

```typescript
// Take a snapshot before sending REPL input
const snap = terminalManager.captureOutputSnapshot(pid);
terminalManager.sendInputToProcess(pid, 'status');

// After a short wait, get only the output generated since the snapshot
const delta = terminalManager.getOutputSinceSnapshot(pid, snap);
console.log('New REPL output:', delta);

```

## Session Cleanup and History

### Active vs. Completed Sessions

When a child process exits, its session transitions from the active `sessions` Map to the `completedSessions` Map (lines 22-34). The system maintains a rolling history of the last 100 completed sessions (lines 34-38), allowing clients to retrieve final output and exit codes even after process termination.

### Accessing Historical Results

The API exposes `listActiveSessions()` and `listCompletedSessions()` (lines 390-446) to enumerate available sessions. These methods return metadata including `pid`, `isBlocked` status, and runtime duration, enabling clients to implement intelligent polling strategies and resource cleanup.

```typescript
// When the process finishes, fetch the completed buffer
const completed = terminalManager.readOutputPaginated(pid, 0, 1000);
if (completed.isComplete) {
  console.log('Process exited with code', completed.exitCode);
}

```

## Summary

- Desktop Commander uses a **singleton `TerminalManager`** to persist session state across HTTP requests
- Each command runs in a **dedicated `TerminalSession`** stored in a PID-keyed Map in [`src/terminal-manager.ts`](https://github.com/wonderwhy-er/DesktopCommanderMCP/blob/main/src/terminal-manager.ts)
- **Circular line buffering** with automatic eviction prevents memory leaks during long executions
- **`readOutputPaginated`** supports incremental, absolute, and tail-based reading with eviction tracking
- **Snapshot APIs** enable efficient REPL interactions by isolating new output since the last interaction
- **Completed session history** retains the last 100 finished processes for post-mortem analysis

## Frequently Asked Questions

### How does Desktop Commander keep terminal sessions alive between HTTP requests?

The server maintains a singleton `TerminalManager` instance that stores active sessions in a JavaScript Map indexed by process ID. Because this object persists in the Node.js runtime for the server's lifetime, the underlying child processes continue executing even after the initial HTTP response completes, allowing subsequent requests to query the same PID through the `sessions` Map (lines 48-49).

### What happens when the output buffer reaches its size limit?

When the buffer exceeds `MAX_BUFFERED_OUTPUT_CHARS` (line 56), the `appendToLineBuffer` method automatically removes the oldest lines (lines 97-103). The `readOutputPaginated` response includes an `evictedLines` count so clients can detect when content has been dropped and adjust their line offset calculations accordingly, ensuring the system remains stable during multi-day executions.

### Can I interact with a REPL process incrementally?

Yes. Use `captureOutputSnapshot(pid)` to mark the current buffer position, then send input via `sendInputToProcess`. After a brief delay, call `getOutputSinceSnapshot(pid, snapshot)` (lines 578-595) to retrieve only the output generated by that specific input, making it ideal for interactive shells without retransmitting the entire session history.

### How long are completed session results available?

Desktop Commander retains completed sessions in a separate Map with a hard limit of 100 entries (lines 34-38). Once this limit is reached or the server restarts, the historical data is purged, so clients should retrieve final results promptly after detecting process completion via `listCompletedSessions()` or `readOutputPaginated`.