/** * SessionManager — owns the lifecycle of Hivemind's tmux panes. * * The in-tree surface is intentionally narrow: the only public * method exposed by `ForkSessionManager` is `onSessionCreated`. The * observer module (`./observers.ts`) wires `session.created` events * into this class; subsequent status / completion events are not * part of the Hivemind event stream (the OpenCode SDK currently only * emits `session.created`, not `session/status` or `session/deleted`). * * In-tree adaptation vs. the fork: * - **Drops `TmuxPluginConfig` param.** The fork takes a config * object (copilot flag, agentLabelFormat, autoClose). The in-tree * version uses the constants exported below — sensible defaults * that match the fork's reference values. * - **Inlines a minimal Logger** — same pattern as `tmux-multiplexer.ts`. * - **Implements `ForkSessionManager` (only `onSessionCreated`)** — * the other two methods the fork's SessionManager class has * (`onSessionStatus`, `onSessionDeleted`) are not part of the * in-tree `ForkSessionManager` interface (`observers.ts:37-39`) * because the Hivemind event stream does not currently carry * status or deletion events. * - **The post-restart recovery method (`respawnIfKnown`)** is * preserved as a public method on the class even though * `ForkSessionManager` does not declare it — the * `SessionManagerAdapter` exposes it to the `tmux-copilot` tool, * and we want a single concrete class to own that surface. * * ORIGIN: opencode-tmux/src/session-manager.ts:1-32 (file header) * ORIGIN: opencode-tmux/src/session-manager.ts:34-67 (class header) * ORIGIN: opencode-tmux/src/session-manager.ts:69-138 (constructor + state) * ORIGIN: opencode-tmux/src/session-manager.ts:139-187 (onSessionCreated) */ import type { EnrichedSessionEvent, ForkSessionManager, PaneObserver } from "./observers.js"; import type { TmuxMultiplexer } from "./tmux-multiplexer.js"; import type { TmuxLayout } from "./tmux-multiplexer.js"; import type { SessionPersistence } from "./persistence.js"; /** * Minimal logger interface. The fork uses the same shape (it is the * client.app.log envelope). Hivemind does not currently export a Logger * type, so this is duplicated here. When a shared/logger.ts module * lands, this can be replaced with `import type { Logger } from "../../shared/logger.js"`. */ export interface Logger { debug(msg: string, data?: unknown): void; info(msg: string, data?: unknown): void; warn(msg: string, data?: unknown): void; error(msg: string, data?: unknown): void; } /** * Default values that replace the fork's `TmuxPluginConfig` block. These * are conservative defaults that match the fork's reference values. */ export declare const SESSION_MANAGER_DEFAULTS: { readonly layout: TmuxLayout; readonly mainPaneSize: 60; readonly autoClose: true; /** Maximum lifetime of a pane regardless of activity. */ readonly maxSessionAgeMs: number; }; /** * P58.8 (REQ-58-07, S1): one captured-pane record stored in the in-memory * cache. Exposed via the public `getLatestCapture(paneId)` accessor so * both `delegation-status peek` (orchestrator-tier) and `tmux-copilot * peek` (user-tier) can read the most recent capture without re-running * the tmux CLI every time. */ export interface CaptureRecord { content: string; capturedAt: number; byteLength: number; } /** * Lifecycle manager for Hivemind's tmux panes. The in-tree surface * implements `ForkSessionManager` (one method: `onSessionCreated`). * The class also exposes a public `respawnIfKnown` method used by * the `SessionManagerAdapter` for post-restart recovery. * * ORIGIN: opencode-tmux/src/session-manager.ts:34-307 (full class body — 1:1 * port, with two adaptations: (1) no TmuxPluginConfig param — defaults * inlined; (2) `onSessionStatus` and `onSessionDeleted` dropped — not * part of the in-tree ForkSessionManager interface). */ export declare class SessionManager implements ForkSessionManager { private readonly multiplexer; private readonly serverUrl; private readonly directory; private readonly log?; private readonly layout; private readonly mainPaneSize; private readonly persistence?; private readonly sessions; private readonly spawningSessions; private readonly failedSessions; private readonly latestCapture; private readonly lastCaptureHash; private pollingTimer; private currentPollIntervalMs; private static readonly MIN_POLL_MS; private static readonly MAX_POLL_MS; private observer; /** * @param multiplexer TmuxMultiplexer instance owned by the harness. * @param serverUrl OpenCode server URL (forwarded to `opencode attach`). * @param directory Working directory forwarded to `opencode attach`. * @param log Optional logger (uses shape compatible with `client.app.log`). * @param persistence Optional P54 handle; `persist()` fires on every state transition; `undefined` preserves P51 behavior (D-54-08). * * ORIGIN: opencode-tmux/src/session-manager.ts:69-138 (adapted — no config) */ constructor(multiplexer: TmuxMultiplexer, serverUrl: string, directory: string, log?: Logger | undefined, layout?: TmuxLayout, mainPaneSize?: number, persistence?: SessionPersistence | undefined); /** * P58.9 REQ-58.9-01: wire a `PaneObserver` into the SessionManager so the * polling tick can emit `pane-captured` events (previously, the * startPolling tick at L328-356 captured content but did NOT emit events * — silent data-loss bug, see `p51-plus-sticky-bugs-2026-06-04.md:39-50`). * * The observer is invoked with `{ type: "pane-captured", sessionId, * paneId, contentLength, timestamp, content }` for every hash change in * `startPolling` (not on every tick — see the `prevHash !== hash` guard * at L341). The full content is included in the event so the P53 * pane-monitor hook at `src/hooks/pane-monitor.ts` can write a sibling * `-pane-content.txt` next to the 7-field `-pane.json`. * * The plugin composition root (`src/plugin.ts:760-790`) calls this method * after constructing the SessionManager + TmuxEventObserver. If no * observer is set (e.g., in unit tests), the polling tick continues to * capture content but no events are emitted — the existing * `getLatestCapture(paneId)` accessor is unaffected. * * @param observer - PaneObserver instance (typically the SessionManagerAdapter * published by `setSessionManagerAdapter` in * `src/features/tmux/types.ts`). The adapter forwards * the event to the real `TmuxEventObserver.onPaneCaptured`. */ setObserver(observer: PaneObserver): void; /** * Handle a `session.created` event. Spawns a tmux pane (if tmux is * available) and registers the spawn in `sessions`. After 250ms * (post-spawn settle window), re-applies the configured layout so * the new pane resizes according to `mainPaneSize`. * * Idempotency: a duplicate event for an already-tracked session * (same `sessionId`) is a no-op. A duplicate event for a still-spawning * session (race with the tmux binary) is skipped via * `spawningSessions`. * * The event payload shape (from `./observers.ts`): * ``` * { * type: "session.created", * properties: { info: { id, parentID, title, directory } }, * hivemindMeta?: { agent, delegationId, depth } * } * ``` * * ORIGIN: opencode-tmux/src/session-manager.ts:139-187 */ onSessionCreated(event: EnrichedSessionEvent): Promise; /** * If a session id is currently tracked, return `true` and re-spawn * a tmux pane for it (in case the harness was restarted while the * delegation was still active — though in practice this is a no-op * because the in-tree known-sessions set is in-memory). Returns * `false` when the session is not in `sessions` OR when the * tmux binary is unavailable. * * Preserved from the fork (origin: `opencode-tmux/src/session-manager.ts:279-307`) * as a public method on the class — the `SessionManagerAdapter` * surfaces it to the `tmux-copilot` tool. In the in-tree port * this is a thin wrapper around the spawn path used by * `onSessionCreated`. * * ORIGIN: opencode-tmux/src/session-manager.ts:279-307 (adapted) */ respawnIfKnown(sessionId: string): Promise<{ paneId: string; } | null>; /** * Start (or ensure running) the 5s capture-pane polling loop. Iterates * active delegations, calls `multiplexer.capturePaneContent(paneId)`, * stores the result in `latestCapture`, and emits a `pane-captured` * event with the FULL content (P53 hook currently emits metadata-only). * * Backoff: when the hash of the captured content is unchanged from the * previous capture, the interval doubles up to 15s; on change, it * resets to 5s. This is the canonical "stable pane" heuristic from * 58-SPEC.md:191. * * Idempotent: if a polling timer is already running, this is a no-op. * The loop self-schedules via `setTimeout` (not `setInterval`) so the * backoff is honored on every iteration. */ startPolling(intervalMs?: number): void; /** * Stop the polling loop. Safe to call when polling is not running. * Called from `handleSessionClose` indirectly via the existing cleanup path * (sessions map deletion); not currently invoked externally but exposed * for symmetry with `startPolling`. */ stopPolling(): void; /** * Get the most recent capture-pane record for a pane id. Returns `null` * if no capture has been recorded yet (polling not started, or pane not * tracked). Used by `delegation-status peek` (S1) and `tmux-copilot peek` * (S2) — both return the same record shape. */ getLatestCapture(paneId: string): CaptureRecord | null; /** * P58.8 S1 (REQ-58-07): persist a `PersistedSession` to the configured * `SessionPersistence` handle. Exposed publicly so the * `manager.ts:sessionManager` option (which expects a `{ persist, ... }` * shape) can be wired from the integration factory. Returns a resolved * promise when no persistence handle is configured. */ persist(record: import("./persistence.js").PersistedSession): Promise; private static hashContent; /** * Close the tmux pane for a tracked session, drop it from * `sessions`, and clear the age timer. On tmux unavailable or * close failure, the tracked record is still removed (graceful * degradation — the harness should not be blocked on a tmux * close failure). */ private handleSessionClose; private toPersistedSession; } //# sourceMappingURL=session-manager.d.ts.map