/** * HA Manager — Lifecycle Coordinator * * Orchestrates heartbeat, session takeover, notification relay, * and orphan session scanning. * Created by Scope when deployment mode is 'distributed'. */ import { type HaConfig, type TakeoverResult } from './ha.types'; import { type HeartbeatRedisClient } from './heartbeat.service'; import { NotificationRelay, type RelayHandler, type RelayRedisClient } from './notification-relay'; import { type TakeoverRedisClient } from './session-takeover'; /** * Callback invoked when an orphaned session is successfully claimed. */ export type OrphanHandler = (sessionId: string, previousNodeId: string) => void | Promise; /** * Options for the orphan session scanner. */ export interface OrphanScannerOptions { /** Redis key prefix for session keys (e.g., 'mcp:transport:'). */ sessionKeyPrefix: string; /** Callback when a session is successfully claimed. */ onOrphan: OrphanHandler; /** * Protocols that can be recreated after takeover. * Sessions with protocols NOT in this list are skipped (e.g., SSE sessions * cannot be recreated because the SSE stream is tied to the original connection). * @default ['streamable-http'] */ recreatableProtocols?: string[]; } /** * Options for creating an HaManager. */ export interface HaManagerOptions { /** Redis client for heartbeat and session operations */ redis: HeartbeatRedisClient & TakeoverRedisClient; /** Dedicated Redis subscriber connection for pub/sub (required for relay) */ pubsubSubscriber?: RelayRedisClient; /** Redis publisher connection (can reuse main redis client) */ pubsubPublisher?: RelayRedisClient; /** This pod's machine ID */ nodeId: string; /** HA configuration overrides */ config?: Partial; /** Logger (optional) */ logger?: { info: (msg: string) => void; warn: (msg: string) => void; debug: (msg: string) => void; }; } export declare class HaManager { private readonly redis; private readonly nodeId; private readonly heartbeat; private readonly relay; private readonly config; private readonly logger; private started; private scannerBootstrapTimer; private scannerTimer; private scannerOptions; private scanning; private scannerStarted; private constructor(); /** * Create an HaManager instance. * Validates that required infrastructure (Redis) is available. */ static create(options: HaManagerOptions): HaManager; /** Start heartbeat and notification relay. */ start(): Promise; /** * Start the orphan session scanner. * * Runs periodically (on heartbeat interval) to detect sessions owned by * dead nodes and claim them via atomic Lua CAS. The `onOrphan` callback * is fired for each successfully claimed session. * * @param options - Scanner configuration */ startOrphanScanner(options: OrphanScannerOptions): void; /** Stop all HA services. */ stop(): Promise; /** * Attempt to take over an orphaned session via atomic CAS. * * @param sessionKey - Full Redis key for the session * @param expectedOldNodeId - The dead pod's nodeId * @returns Whether this pod successfully claimed the session */ attemptTakeover(sessionKey: string, expectedOldNodeId: string): Promise; /** Check if a specific node is alive. */ isNodeAlive(nodeId: string): Promise; /** Get all alive node IDs. */ getAliveNodes(): Promise; /** Get the notification relay (undefined if pub/sub not configured). */ getRelay(): NotificationRelay | undefined; /** Subscribe to relay messages (for cross-pod notification delivery). */ subscribeRelay(handler: RelayHandler): Promise; /** Update the heartbeat with current session count. */ setSessionCount(count: number): void; /** Whether HA services are running. */ isStarted(): boolean; /** The node ID this manager is running for. */ getNodeId(): string; /** * Run a single orphan scan cycle. * * 1. Get alive nodes from heartbeat keys * 2. Scan session keys matching the configured prefix * 3. Parse each session's nodeId * 4. If nodeId is not in the alive set, attempt takeover * 5. Fire onOrphan callback for each claimed session */ private runOrphanScan; } //# sourceMappingURL=ha-manager.d.ts.map