panes using xterm.js with WebGL acceleration and communicates with the daemon via token-authenticated Named Pipes. A dedicated browser panel connects to an embedded Chromium instance through CDP, with automatic MCP server registration for AI agent integration.
Step 1: ConPTY Pane Manager
Windows ConPTY provides a native pseudo-terminal API that mirrors Unix PTY behavior. The pane manager spawns isolated ConPTY instances, routes I/O, and handles split operations.
import { createConptyProcess, ConptyHandle } from 'native-pty-win';
interface PaneConfig {
id: string;
cwd: string;
env: Record<string, string>;
dimensions: { cols: number; rows: number };
}
class ConptyPaneManager {
private activePanes: Map<string, ConptyHandle> = new Map();
async spawnPane(config: PaneConfig): Promise<string> {
const handle = await createConptyProcess({
commandLine: 'cmd.exe',
cwd: config.cwd,
environment: config.env,
dimensions: config.dimensions,
inheritConsole: false
});
this.activePanes.set(config.id, handle);
return config.id;
}
writeInput(paneId: string, data: Buffer): void {
const handle = this.activePanes.get(paneId);
if (!handle) throw new Error(`Pane ${paneId} not found`);
handle.writeInput(data);
}
readOutput(paneId: string): AsyncIterable<Buffer> {
const handle = this.activePanes.get(paneId);
if (!handle) throw new Error(`Pane ${pipeId} not found`);
return handle.readOutput();
}
}
Why this design: ConPTY handles are kept in a Map for O(1) lookup during I/O routing. Input sanitization occurs before writeInput to prevent escape sequence injection. Dimensions are tracked separately from the PTY handle to support dynamic pane resizing without process restart.
Step 2: CDP Browser Panel with MCP Auto-Registration
AI agents require browser interaction for research, testing, and UI validation. Instead of launching external automation tools, the multiplexer embeds a Chromium instance and exposes it via CDP. An MCP server auto-registers in the agent's configuration directory upon pane initialization.
import { CdpClient, BrowserSession } from 'cdp-bridge';
import { McpRegistry } from 'agent-mcp';
class CdpBrowserPanel {
private session: BrowserSession | null = null;
private cdpPort: number;
constructor() {
this.cdpPort = this.generateRandomPort();
}
private generateRandomPort(): number {
return Math.floor(Math.random() * (65535 - 1024) + 1024);
}
async initialize(): Promise<void> {
this.session = await CdpClient.launch({
port: this.cdpPort,
headless: false,
args: ['--disable-features=IsolateOrigins,site-per-process']
});
await McpRegistry.register({
serverName: 'browser-automation',
configPath: `${process.env.USERPROFILE}/.claude.json`,
endpoint: `http://127.0.0.1:${this.cdpPort}`
});
}
async navigate(url: string): Promise<void> {
if (!this.session) throw new Error('Browser not initialized');
this.validateUrl(url);
await this.session.Page.navigate({ url });
}
private validateUrl(target: string): void {
const blocked = ['127.0.0.1', 'localhost', 'file://', 'javascript:'];
if (blocked.some(pattern => target.startsWith(pattern))) {
throw new Error('SSRF guard: blocked internal/unsafe URL');
}
}
}
Why this design: Randomized CDP ports prevent session collision when multiple agents run concurrently. The SSRF guard intercepts navigation requests before they reach the browser engine, blocking localhost, file protocol, and JavaScript URIs. MCP registration occurs automatically, eliminating manual configuration for Claude Code and compatible agents.
Step 3: Session Persistence Daemon
Terminal sessions must survive application closures and system reboots. A background daemon owns the PTY processes and serializes state to disk using atomic writes.
import { writeFileSync, renameSync, existsSync } from 'fs';
import { join } from 'path';
class SessionDaemon {
private stateDir: string;
private maxMemoryMb: number = 750;
constructor(stateDir: string) {
this.stateDir = stateDir;
}
async persistSession(sessionId: string, state: Record<string, unknown>): Promise<void> {
const tempPath = join(this.stateDir, `${sessionId}.tmp`);
const bakPath = join(this.stateDir, `${sessionId}.bak`);
const finalPath = join(this.stateDir, `${sessionId}.json`);
writeFileSync(tempPath, JSON.stringify(state), { encoding: 'utf8' });
if (existsSync(finalPath)) {
renameSync(finalPath, bakPath);
}
renameSync(tempPath, finalPath);
}
async restoreSession(sessionId: string): Promise<Record<string, unknown> | null> {
const finalPath = join(this.stateDir, `${sessionId}.json`);
const bakPath = join(this.stateDir, `${sessionId}.bak`);
try {
const raw = readFileSync(finalPath, { encoding: 'utf8' });
return JSON.parse(raw);
} catch {
if (existsSync(bakPath)) {
const raw = readFileSync(bakPath, { encoding: 'utf8' });
return JSON.parse(raw);
}
return null;
}
}
private checkMemoryUsage(): void {
const usage = process.memoryUsage().heapUsed / 1024 / 1024;
if (usage > this.maxMemoryMb) {
this.reapDeadSessions();
}
}
}
Why this design: Atomic .bak rotation prevents corruption during interrupted writes. The daemon monitors heap usage and triggers garbage collection when thresholds are exceeded. State restoration falls back to .bak files if the primary state file is missing or malformed, ensuring graceful degradation.
Step 4: Throughput Monitoring & Notification
Detecting when an AI agent finishes a task requires monitoring output patterns, not string matching. A throughput watcher tracks I/O velocity and triggers system notifications when activity drops below a threshold.
import { EventEmitter } from 'events';
import { showWindowsToast } from 'system-notifications';
class ThroughputWatcher extends EventEmitter {
private quietThresholdMs: number = 3000;
private lastActivity: number = Date.now();
private isQuiet: boolean = false;
trackOutput(): void {
this.lastActivity = Date.now();
if (this.isQuiet) {
this.isQuiet = false;
this.emit('activity-resumed');
}
}
startMonitoring(): void {
setInterval(() => {
const idleTime = Date.now() - this.lastActivity;
if (idleTime > this.quietThresholdMs && !this.isQuiet) {
this.isQuiet = true;
this.emit('task-completed');
showWindowsToast({
title: 'Agent Task Complete',
body: 'Output throughput has dropped below threshold.',
icon: 'info'
});
}
}, 1000);
}
}
Why this design: Throughput monitoring avoids brittle regex patterns that break when agent output formats change. The 3-second quiet threshold balances responsiveness with false-positive prevention. Windows toast notifications integrate with the OS taskbar, providing non-intrusive alerts that don't interrupt active work.
Pitfall Guide
1. Hardcoding CDP Ports
Explanation: Assigning fixed ports to browser panels causes collision when multiple agents run simultaneously.
Fix: Generate random ports within the ephemeral range (1024β65535) and validate availability before binding. Store port mappings in session state for daemon recovery.
2. Bypassing SSRF Guards in Browser Panels
Explanation: Allowing agents to navigate to localhost, file://, or javascript: URIs exposes the host system to cross-origin attacks and local file disclosure.
Fix: Implement a strict URL validator that rejects internal addresses, protocol handlers, and relative paths before passing navigation requests to CDP.
3. Ignoring ConPTY Lifecycle Events
Explanation: ConPTY processes can exit unexpectedly due to crashes or resource limits. Failing to listen to exit events leaves orphaned handles and memory leaks.
Fix: Attach on('exit', handler) listeners to every ConPTY handle. Trigger session cleanup, state serialization, and daemon notification on exit.
4. Regex-Based Agent Completion Detection
Explanation: Matching specific strings like "Task complete" or "Done" breaks when agents change output formatting or run in different locales.
Fix: Monitor I/O throughput velocity. Trigger notifications when output drops below a configurable threshold for a sustained period, regardless of content.
Explanation: Storing unlimited terminal output in RAM causes memory exhaustion during long-running agent sessions.
Fix: Persist scrollback to disk using a circular buffer. Limit in-memory retention to the visible viewport plus a small overflow, flushing older lines to compressed .log files.
Explanation: Raw terminal input can contain escape sequences that trigger unintended behavior, such as terminal resizing or command injection.
Fix: Sanitize input buffers before writing to ConPTY. Strip or escape control sequences that don't match expected agent output patterns.
7. Neglecting Daemon Crash Recovery
Explanation: If the persistence daemon crashes, active PTY processes become orphaned and session state is lost.
Fix: Run the daemon as a Windows Service with automatic restart policies. Implement heartbeat checks between UI client and daemon, triggering reconnection logic on timeout.
Production Bundle
Action Checklist
Decision Matrix
| Scenario | Recommended Approach | Why | Cost Impact |
|---|
| Single agent, short tasks | VSCode integrated terminal | Low overhead, familiar UI | Minimal |
| Parallel agents, web research required | Native multiplexer with CDP panel | Eliminates external automation toolchain | Moderate setup time |
| Long-running agents, system reboots expected | Daemon-backed persistence | Survives crashes without manual recovery | Higher memory baseline |
| Cross-platform team (Win/Mac/Linux) | Native multiplexer + WSL fallback | Maintains Windows-native workflow while supporting Linux agents | Slightly higher complexity |
| Memory-constrained environments | Throughput monitoring + disk scrollback | Prevents RAM exhaustion during extended sessions | Disk I/O overhead |
Configuration Template
{
"multiplexer": {
"rendering": {
"engine": "xterm-webgl",
"scrollbackLines": 999000,
"persistToDisk": true
},
"pty": {
"conptyVersion": "latest",
"inputSanitization": true,
"dangerousCommandAlerts": ["git push --force", "rm -rf", "DROP TABLE"]
},
"browser": {
"cdpPortRange": [1024, 65535],
"ssrfGuard": true,
"blockedProtocols": ["file://", "javascript:", "data:"],
"mcpAutoRegister": true
},
"persistence": {
"daemonMode": true,
"stateDirectory": "%USERPROFILE%/.wmux/state",
"atomicWrites": true,
"memoryWatchdogMb": 750
},
"notifications": {
"throughputQuietMs": 3000,
"taskbarFlash": true,
"windowsToast": true
}
}
}
Quick Start Guide
- Initialize the daemon: Launch the background persistence service. It will create the state directory, bind to a randomized IPC channel, and prepare ConPTY handle pools.
- Spawn your first pane: Use the split command (
Ctrl+D for horizontal, Ctrl+Shift+D for vertical). The manager allocates a ConPTY instance, attaches xterm.js rendering, and registers the pane ID.
- Launch an AI agent: Start Claude Code, Codex CLI, or Gemini CLI in the active pane. The multiplexer detects the process, auto-registers the MCP browser server, and begins throughput monitoring.
- Verify browser automation: Issue a navigation command through the agent. The CDP panel intercepts the request, validates the URL against SSRF rules, and executes navigation in the embedded Chromium instance.
- Test persistence: Close the UI client. The daemon retains all PTY processes and serializes state to disk. Reopen the application; panes reconnect with scrollback intact and agent processes running.