# How Munder Difflin's PtyManager Handles PTY Processes for Agents

> Discover how Munder Difflin's PtyManager isolates agent processes in PTYs, managing their entire lifecycle and routing terminal output to the owning window for seamless agent operation.

- Repository: [Chaitanya Giri/munder-difflin](https://github.com/chaitanyagiri/munder-difflin)
- Tags: internals
- Published: 2026-08-22

---

**Munder Difflin's PtyManager runs every agent inside an isolated pseudo-terminal (PTY), managing the full lifecycle from cross-platform command resolution to graceful termination while routing all terminal output exclusively to the owning window.**

The `PtyManager` class in the `chaitanyagiri/munder-difflin` repository abstracts the complexity of agent execution by providing a centralized system for spawning, monitoring, and terminating PTY sessions. Located in [`src/main/pty.ts`](https://github.com/chaitanyagiri/munder-difflin/blob/main/src/main/pty.ts), this implementation ensures that each agent operates within its own isolated terminal environment while maintaining strict ownership boundaries between application windows.

## PTY Session Architecture and Owner Isolation

### Session Tracking with Map Storage

The manager maintains active sessions using a private `Map` structure keyed by unique session identifiers. Each entry stores the `node-pty` process instance, current working directory, original command, timestamps, and a reference to the owning Electron `WebContents`.

As implemented at lines 4-5 in [`src/main/pty.ts`](https://github.com/chaitanyagiri/munder-difflin/blob/main/src/main/pty.ts):

```typescript
private sessions = new Map<string, PtySession>();

```

This design allows O(1) lookup for session management operations including data routing, resizing, and termination.

### Secure Output Routing to Window Owners

When a floor (window) spawns a PTY, the manager records the `WebContents` instance as the session's **owner**. All terminal output and exit events are sent exclusively to this owner using the `safeSend` method, preventing data leakage between different agent windows.

The routing mechanism uses namespaced IPC channels:
- `pty:data:<id>` for terminal output streams
- `pty:exit:<id>` for process termination signals

This implementation at lines 41-60 ensures that `owner: WebContents | null` remains the sole recipient of all session events, maintaining strict isolation between concurrent agent operations.

## Cross-Platform Command Resolution and Windows Compatibility

### PATH Resolution and Caching

Before spawning any process, the manager resolves bare commands (e.g., `claude`) against the user's PATH environment. Successful lookups are cached in the `resolvedCommands` map to avoid expensive shell invocations on subsequent spawns.

The `resolveCommand` method at lines 81-88 performs this resolution using the interactive shell environment captured from [`src/main/shellEnv.ts`](https://github.com/chaitanyagiri/munder-difflin/blob/main/src/main/shellEnv.ts), ensuring accurate PATH context even when the application launches from a non-interactive parent process.

### Windows Shim Decoding and Fallback Handling

On Windows systems, the manager handles `.cmd` and `.bat` npm shims that cannot execute directly. The `resolveWindowsShimSpawn` method attempts to decode npm-style shims using `parseNpmCmdShim`; when successful, it spawns the real interpreter with the preserved argument array, maintaining support for multi-line data (the Hive protocol).

If shim decoding fails, the system falls back to `cmd.exe /d /s /c "<command>"` via `buildCmdCommandLine` (lines 13-25), though this warns that multi-line arguments may be truncated. This dual-path approach at lines 68-84 ensures robust cross-platform compatibility while optimizing for the common npm-based tool installation pattern.

## PTY Spawning and Real-Time Data Flow

### Spawning Process with Environment Capture

The `spawn` method at lines 25-70 creates new PTY instances using the resolved executable and appropriate arguments. Environment variables include the captured interactive shell PATH, locale settings, and any agent-specific variables passed via `opts.env`.

```typescript
ptyMgr.spawn(
  {
    id: 'agent-123',
    cwd: '/home/user/project',
    command: 'claude',
    args: ['--interactive'],
    env: { HIVE_ROOT: '/path/to/hive' }
  },
  mainWindow.webContents
);

```

### Output Streaming and Idle Tracking

Once spawned, the PTY's `onData` callback (lines 88-95) forwards terminal output to the owner window via `safeSend`. The manager simultaneously updates the `lastOutputAt` timestamp for each data event, enabling idle detection through the `idleFor` API.

```typescript
ptyMgr.write('agent-123', 'ls -la\n');

```

This real-time streaming architecture ensures low-latency terminal interaction while providing the metadata necessary for handshake and keepalive logic.

## Lifecycle Management and Termination

### Graceful Exit Handling

When a PTY exits naturally, the `onExit` handler at lines 97-105 emits the `pty:exit:<id>` event to the owner, removes the session from the active `Map`, and invokes any registered `exitHandler`. This callback enables higher-level cleanup such as archiving worktrees or removing git worktrees, ensuring consistent resource management regardless of how the process terminated.

### Explicit Kill Operations and Bulk Cleanup

For explicit termination, the `kill` method at lines 50-62 sends SIGKILL to the child process and invokes `ensureKilled` from [`src/main/procKill.ts`](https://github.com/chaitanyagiri/munder-difflin/blob/main/src/main/procKill.ts) to guarantee complete process group removal. The `killAll` method (lines 87-101) disables the exit handler during application shutdown to prevent redundant teardown operations, then iterates through all active sessions.

```typescript
// Clean up when a floor closes
ptyMgr.killByOwner(floorWindow.webContents);

```

### Utility APIs for Session Monitoring

The manager exposes several diagnostic and control methods at lines 64-78:
- `list()` returns all active session metadata
- `lastOutputAt(id)` and `idleFor(id)` provide activity timestamps
- `write(id, data)` sends input to the PTY stdin
- `resize(id, cols, rows)` updates terminal dimensions

```typescript
console.log(ptyMgr.list());

```

## Summary

- **Map-based session tracking** uses unique IDs for O(1) lookup of PTY instances and metadata at lines 4-5.
- **Owner-specific routing** via `WebContents` references and `safeSend` prevents cross-window data leakage.
- **Cross-platform command resolution** includes PATH caching and Windows shim decoding with cmd.exe fallback.
- **Real-time data streaming** captures output through `onData` callbacks while tracking idle states via timestamps.
- **Robust termination** combines graceful exit handlers with `ensureKilled` from [`src/main/procKill.ts`](https://github.com/chaitanyagiri/munder-difflin/blob/main/src/main/procKill.ts) for guaranteed cleanup.

## Frequently Asked Questions

### How does PtyManager ensure PTY output is isolated to specific windows?

The manager stores an `owner: WebContents` reference for each session at lines 41-60 and routes all `pty:data:<id>` and `pty:exit:<id>` events exclusively through the `safeSend` method to that owner. This prevents terminal output from one agent floor appearing in another window.

### What happens when PtyManager encounters a Windows .cmd or .bat shim?

The system attempts to decode npm-style shims using `parseNpmCmdShim` within `resolveWindowsShimSpawn` (lines 68-84). If successful, it spawns the real interpreter with preserved arguments; otherwise, it falls back to `cmd.exe /d /s /c` (lines 13-25) with a warning that multi-line arguments may be truncated.

### How does the manager handle PTY cleanup when the application shuts down?

`killAll()` disables the exit handler to prevent redundant archiving work, then terminates all active sessions. Individual `kill()` operations use `ensureKilled` from [`src/main/procKill.ts`](https://github.com/chaitanyagiri/munder-difflin/blob/main/src/main/procKill.ts) to guarantee process group removal, ensuring no zombie agents persist after application closure.

### Can PTY sessions be monitored for idle state?

Yes. The manager updates `lastOutputAt` timestamps during every `onData` event (lines 88-95) and exposes `idleFor(id)` and `lastOutputAt(id)` methods at lines 64-78, allowing the application to detect inactive agents and trigger keepalive handshakes or timeouts accordingly.