PTY Manager Architecture in Munder Difflin: How It Spawns and Manages Agent Processes

The PTY manager in munder-difflin is a centralized process controller that tracks live terminal sessions in a Map, spawns cross-platform agent processes through command resolution and Windows shim decoding, and routes all output and exit events to renderer windows via Electron's WebContents.

The munder-difflin repository implements a robust PTY manager architecture inside src/main/pty.ts to orchestrate the terminal processes that power each agent. This component acts as the single source of truth for creating, monitoring, and tearing down pseudo-terminals across macOS, Linux, and Windows. Understanding its design reveals how the application isolates multi-window agent output while guaranteeing correct command resolution and safe process lifecycle handling.

Core Architecture of the PTY Manager

The PTY manager is built around three structural pillars: session tracking, lifecycle management, and event routing. Each pillar is implemented as concrete methods and data structures inside src/main/pty.ts.

Session Tracking with Map<string, PtySession>

Every live agent process is stored in a Map<string, PtySession> that maps a unique session identifier to a session object. According to the source code at lines 31–36, each PtySession records:

  • The spawned proc instance.
  • The current working directory and resolved command string.
  • The owner BrowserWindow reference.
  • Timestamps for the last output activity.
  • A boolean flag indicating whether any output has been produced yet.

This map enables O(1) lookups for any subsequent operation—write, resize, kill, or redraw—because every public method first resolves the target session by its ID before touching the underlying node-pty instance.

Lifecycle Management Methods

The manager exposes explicit control methods for every stage of a PTY's life: spawn, write, resize, redraw, kill, killByOwner, and killAll. As implemented in chaitanyagiri/munder-difflin, spawn handles the heavy lifting of command resolution and process creation, while all other operations simply look up the session in the map and delegate to the corresponding node-pty API (lines 124–132).

Event Routing to the Renderer

Once a PTY is running, its onData and onExit callbacks forward events to the appropriate Electron renderer via WebContents.send. To prevent crashes during application shutdown, the helper safeSend (lines 55–61) validates that the target window still exists before transmitting any payload. This guarantees that stale callbacks from dying sessions cannot corrupt new sessions or crash the main process.

How the PTY Manager Spawns Agent Processes

Spawning is the most complex operation in the PTY manager architecture. The spawn method (lines 425–437) executes a deterministic pipeline to ensure cross-platform correctness.

Expanding Tilde and Resolving Commands

First, the manager expands ~ in the requested working directory using expandTilde. Next, it resolves the command through resolveCommand, which runs a login-shell which on Unix or where on Windows and caches successful lookups (lines 90–100). Callers can also preemptively check tool availability through isCommandAvailable and commandPath, which expose the same resolution cache.

Windows Shim Decoding

On Windows, the manager attempts to decode npm-style command shims via resolveWindowsShimSpawn and parseNpmCmdShim (lines 484–523). If decoding succeeds, the shim’s interpreter and script are spawned as a raw argv array, preserving multiline arguments and avoiding the truncation bugs that occur when routing through cmd.exe.

Fallback Command Line Construction

If shim decoding fails—or on non-Windows platforms—the pipeline falls back to buildCmdCommandLine (lines 143–150). This helper constructs a properly quoted command line for cmd.exe, ensuring that spaces and special characters are handled safely before node-pty receives the final executable and arguments.

Process Registration and Callback Binding

After resolution, the manager calls pty.spawn with the chosen executable and arguments. It immediately registers onData and onExit callbacks that:

  1. Update the session’s last-output timestamp and ready flag.
  2. Forward the payload to the owner window through safeSend.
  3. Clean up the session entry when the process exits.

Utility and Helper Methods

Beyond spawning and event routing, the PTY manager provides several utilities that support the broader application:

  • attachWebContents – Sets a default WebContents sink for sessions that have no explicit owner window.
  • countByOwner – Returns how many PTYs belong to a specific window, enabling per-floor close confirmations in the UI.
  • isCommandAvailable / commandPath – Reuse the internal resolution cache so the renderer or main process can verify CLI tools before attempting a spawn.

These helpers are consumed downstream by src/preload/index.ts, which bridges the PTY API to the renderer, and by src/renderer/src/hooks/useHive.ts and src/renderer/src/components/terminalPool.ts, which queue writes and render terminal output. Process termination guarantees are reinforced by src/main/procKill.ts, which provides ensureKilled for forcibly cleaning up orphaned child processes.

Practical Example: Spawning and Controlling a PTY

The following TypeScript example demonstrates the full lifecycle of an agent process under the PTY manager:

// 1️⃣ Create a manager and attach the main window
import { PtyManager } from './main/pty';
import { BrowserWindow } from 'electron';

const ptyMgr = new PtyManager();
const mainWin = BrowserWindow.getAllWindows()[0];
ptyMgr.attachWebContents(mainWin.webContents);

// 2️⃣ Spawn an agent process (e.g., the Claude CLI)
const spawnResult = ptyMgr.spawn(
  {
    id: 'pty-1',
    cwd: '~/projects/my-agent',
    command: 'claude',
    args: ['--seed', '"Hello world"'],
    cols: 120,
    rows: 30,
  },
  mainWin.webContents,
);
if (!spawnResult.ok) {
  console.error('Failed to spawn PTY:', spawnResult.error);
}

// 3️⃣ Write data to the PTY (simulate user typing)
ptyMgr.write('pty-1', 'ls -la\n');

// 4️⃣ Resize the terminal when the UI changes
ptyMgr.resize('pty-1', 140, 40);

// 5️⃣ Gracefully kill the PTY (e.g., when the floor window closes)
ptyMgr.kill('pty-1');

This pattern is repeated for every agent floor in the application, with each PTY isolated to its owning window.

Summary

  • The PTY manager architecture centers on a Map<string, PtySession> that tracks every live agent process in src/main/pty.ts.
  • Spawning resolves commands through cached shell lookups, decodes Windows npm shims to preserve arguments, and falls back to quoted cmd.exe command lines when necessary.
  • Lifecycle methods such as write, resize, kill, and killAll perform O(1) map lookups before delegating to node-pty.
  • Event routing uses safeSend to forward onData and onExit events only to valid renderer windows, preventing shutdown crashes.
  • Utility methods like countByOwner, isCommandAvailable, and attachWebContents support multi-window isolation and preemptive command validation.

Frequently Asked Questions

What data structure does the PTY manager use to track active sessions?

The PTY manager uses a Map<string, PtySession> defined at lines 31–36 of src/main/pty.ts. Each entry stores the node-pty process instance, working directory, resolved command, owner window, output timestamps, and a readiness flag.

How does the PTY manager handle Windows npm-style shims?

It invokes resolveWindowsShimSpawn and parseNpmCmdShim (lines 484–523) to decode the shim file. When successful, the manager spawns the interpreter and script as a raw argv array, bypassing cmd.exe and preserving complex arguments such as multiline strings.

How are PTY output and exit events forwarded to the UI?

Each session registers onData and onExit callbacks that call safeSend (lines 55–61). This helper verifies the target WebContents still exists before calling webContents.send, ensuring that output is routed to the correct renderer window without crashing during shutdown.

What utilities does the PTY manager expose for command resolution?

The manager exposes isCommandAvailable and commandPath, both backed by the same cache used inside resolveCommand. These let callers confirm that a CLI tool exists before spawning a PTY, avoiding unnecessary failed sessions.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →