codemap.md 8.8 KB

src/multiplexer/

Responsibility

Provides a unified abstraction for tmux, Zellij, Herdr, cmux, and kitty to spawn, manage, and close panes for child OpenCode agent sessions.

Design

Core Abstractions

  • Multiplexer Interface (types.ts): Defines the contract for terminal multiplexer implementations with methods for pane lifecycle management and layout application.
  • Concrete Implementations:
    • TmuxMultiplexer: tmux-specific implementation using tmux CLI commands
    • ZellijMultiplexer: zellij-specific implementation using zellij plugin API
    • HerdrMultiplexer: herdr-specific implementation using herdr CLI commands
    • KittyMultiplexer: kitty-specific implementation using kitten @ CLI commands
    • CmuxMultiplexer: cmux UUID surface implementation using the cmux CLI
  • Shared Utilities (shared.ts): quoteShellArg, buildOpencodeAttachCommand, and findBinary — extracted from the three adapters to eliminate copy-paste duplication.
  • Tmux pane registry (tmux-pane-registry.ts): Session-scoped, expiring TUI pane registrations used to route child panes to the attached root session that created them.
  • Session Manager (session-manager.ts): Tracks child session lifecycle and coordinates pane operations via event-driven architecture.
  • cmux lifecycle (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 state/policy (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 (factory.ts): Creates appropriate multiplexer instance based on configuration and environment detection.

Key Interfaces

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>;
}

State Management

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 panes
  • spawningSessions: 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 jobs

Event-Driven Architecture

The session manager reacts to OpenCode session events:

  • session.created: Spawns a new pane for the child session
  • session.status: Handles idle/busy state transitions
  • session.deleted: Cleans up pane when session is deleted

Flow

Session Creation Flow

1. 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(), forwarding the parent session
   │  ├─ 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`.

Session Completion Flow

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)

Polling Loop

  • Runs every POLL_INTERVAL_BACKGROUND_MS (default: 5000ms)
  • Fetches session statuses from OpenCode server
  • Closes any idle sessions that aren't tracked by this instance
  • Stops when no sessions remain

Integration

Consumers

  • Main Plugin (src/index.ts): Initializes multiplexer session manager during plugin startup
  • Council Agents (src/agents/council.ts, src/agents/council-agents.ts): Use session manager for child session pane management
  • Background Job Board (src/utils/background-job-board.ts): Coordinates with session manager to defer pane closing when background jobs are running

Dependencies

  • Config Schema (src/config/schema.ts): Provides MultiplexerConfig with type, layout, and size settings
  • Logger (src/utils/logger.ts): Logs multiplexer operations for debugging
  • OpenCode Server: Provides session lifecycle events and status API

Configuration

interface 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
}

Environment Detection

  • Auto Mode: Detects tmux (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).
  • Availability Check: Validates multiplexer binary is available before use

Implementation Details

Tmux Implementation

  • Uses tmux CLI commands via spawn() utility
  • Resolves a fresh parent-session pane registration before splitting and falls back to the server process's startup pane if registration is absent or rejected by tmux
  • Debounces layout updates per parent pane so attached tmux sessions remain isolated during concurrent child creation
  • Creates panes with descriptive titles and working directories
  • Applies layouts using tmux select-layout and tmux resize-pane
  • Graceful shutdown: sends Ctrl+C before killing pane to allow clean process termination

Zellij Implementation

  • Uses zellij plugin API via zellij CLI
  • Creates panes with plugin-based OpenCode integration
  • Layout management via zellij's built-in layout system
  • Session attachment via zellij's pane-specific attach mechanism

Error Handling

  • Server health checks before pane creation
  • Graceful degradation when multiplexer is unavailable
  • Logging at each lifecycle stage for observability
  • State consistency maintained via shared global state with proper locking

Testing

  • Factory tests (factory.test.ts): Validates multiplexer creation and configuration
  • Session manager tests (session-manager.test.ts): Tests event handling and pane lifecycle
  • Integration tests verify pane cleanup on session deletion

Files

File Purpose
index.ts Public API exports
types.ts Core interfaces and shared utilities
shared.ts Shared infrastructure (quoteShellArg, buildOpencodeAttachCommand, findBinary)
tmux-pane-registry.ts Attached TUI session-to-pane registration storage
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