Provides a unified abstraction for tmux, Zellij, Herdr, cmux, and kitty to spawn, manage, and close panes for child OpenCode agent sessions.
types.ts): Defines the contract for terminal multiplexer implementations with methods for pane lifecycle management and layout application.TmuxMultiplexer: tmux-specific implementation using tmux CLI commandsZellijMultiplexer: zellij-specific implementation using zellij plugin APIHerdrMultiplexer: herdr-specific implementation using herdr CLI commandsKittyMultiplexer: kitty-specific implementation using kitten @ CLI commandsCmuxMultiplexer: cmux UUID surface implementation using the cmux CLIshared.ts): quoteShellArg, buildOpencodeAttachCommand, and findBinary — extracted from the three adapters to eliminate copy-paste duplication.session-manager.ts): Tracks child session lifecycle and coordinates pane operations via event-driven architecture.cmux/session-lifecycle.ts): Owns readiness, deferred
spawning, stable-idle polling, activity generations, reliable close retries,
tracked orphan cooldown, cleanup, timers, and ownership. The generic manager
delegates its five public lifecycle entries for cmux.cmux/session-state.ts, cmux/close-policy.ts):
Shared single-record storage and pure close-intent transitions. Pane records
are removed only after confirmed close. The store uses a global symbol so a
same-directory lifecycle can take over tracked orphans after hot reload;
retry budgets remain finite per lifecycle instance.factory.ts): Creates appropriate multiplexer instance based on configuration and environment detection.export interface Multiplexer {
readonly type: 'tmux' | 'zellij' | 'herdr' | 'cmux' | 'kitty';
isAvailable(): Promise<boolean>;
isInsideSession(): boolean;
spawnPane(sessionId: string, description: string, serverUrl: string, directory: string): Promise<PaneResult>;
closePane(paneId: string): Promise<boolean>;
applyLayout(layout: MultiplexerLayout, mainPaneSize: number): Promise<void>;
}
The session manager uses a shared global state pattern to coordinate across plugin instances:
sessions: Map of active tracked sessions (sessionId → pane metadata)knownSessions: Map of sessions that have been created but may not have active panesspawningSessions: Set of sessions currently being spawned (prevents duplicate spawns)closingSessions: Map of ongoing close operations (prevents race conditions)deferredIdleCloses: Set of sessions that should be closed on idle but have running background jobsThe session manager reacts to OpenCode session events:
session.created: Spawns a new pane for the child sessionsession.status: Handles idle/busy state transitionssession.deleted: Cleans up pane when session is deleted1. OpenCode creates child session → emits 'session.created' event
2. MultiplexerSessionManager.onSessionCreated()
├─ Checks if multiplexer is enabled
├─ Validates event properties (sessionId, parentId)
├─ Checks if session is already tracked or spawning
├─ Records session in knownSessions
├─ Spawns pane via multiplexer.spawnPane()
│ ├─ Validates server is running
│ ├─ Creates new pane with:
│ │ ├─ Command: opencode attach --session-id <sessionId>
│ │ ├─ Working directory: project directory
│ │ └─ Title: session description
│ └─ Returns paneId
├─ Validates pane creation succeeded
├─ Records session in sessions map with pane metadata
└─ Starts polling loop if not already running
3. The selected tmux, Zellij, Herdr, or cmux implementation creates the pane
or surface. cmux delegates lifecycle reliability to `CmuxSessionLifecycle`.
1. Child session becomes idle → emits 'session.idle' or 'session.status' event
2. MultiplexerSessionManager.onSessionStatus()
├─ Checks if session is tracked
├─ If idle:
│ ├─ Checks for running background jobs
│ ├─ If background job running: defers close
│ └─ Otherwise: closes pane via multiplexer.closePane()
│ ├─ Removes from sessions map
│ ├─ Calls tmux/zellij kill-pane command
│ └─ Logs completion
└─ If busy: respawns pane (same flow as creation)
3. Session deleted → emits 'session.deleted' event
4. MultiplexerSessionManager.onSessionDeleted()
├─ Removes from knownSessions
└─ Closes pane (same flow as idle)
POLL_INTERVAL_BACKGROUND_MS (default: 5000ms)src/index.ts): Initializes multiplexer session manager during plugin startupsrc/agents/council.ts, src/agents/council-agents.ts): Use session manager for child session pane managementsrc/utils/background-job-board.ts): Coordinates with session manager to defer pane closing when background jobs are runningsrc/config/schema.ts): Provides MultiplexerConfig with type, layout, and size settingssrc/utils/logger.ts): Logs multiplexer operations for debugginginterface MultiplexerConfig {
type: 'tmux' | 'zellij' | 'herdr' | 'cmux' | 'kitty' | 'auto' | 'none';
layout: MultiplexerLayout; // 'tiled' | 'main-horizontal' | 'main-vertical' | 'grid'
main_pane_size?: number; // Percentage for main pane (0-100)
zellij_pane_mode?: string; // Zellij-specific pane mode
}
TMUX), Zellij (ZELLIJ), kitty
(KITTY_PID), Herdr (HERDR_ENV/HERDR_PANE_ID), or cmux
(complete CMUX_SOCKET_PATH, CMUX_WORKSPACE_ID, and CMUX_SURFACE_ID
identity).tmux CLI commands via spawn() utilitytmux select-layout and tmux resize-panezellij CLIfactory.test.ts): Validates multiplexer creation and configurationsession-manager.test.ts): Tests event handling and pane lifecycle| File | Purpose |
|---|---|
index.ts |
Public API exports |
types.ts |
Core interfaces and shared utilities |
shared.ts |
Shared infrastructure (quoteShellArg, buildOpencodeAttachCommand, findBinary) |
factory.ts |
Multiplexer instance creation |
session-manager.ts |
Session lifecycle management |
tmux/index.ts |
tmux-specific implementation |
zellij/index.ts |
zellij-specific implementation |
herdr/index.ts |
herdr-specific implementation |
kitty/index.ts |
kitty-specific implementation |
cmux/index.ts |
cmux adapter and encoded surface handles |
cmux/session-lifecycle.ts |
cmux event, polling, spawn, close, orphan, and cleanup ownership |
cmux/session-state.ts |
process-global cmux session registry |
cmux/close-policy.ts |
pure cmux close-intent transitions and retry budgets |