codemap.md 8.3 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.
  • 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()
   │  ├─ 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
  • 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)
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