/** * Unified web console orchestrator. * * Ties together leader election, console startup, follower wiring, * and session lifecycle management. This is the main entry point * called by the DI container during deferred setup. * * Flow: * 1. Run leader election (read lock file, claim or follow) * 2. If leader: start web server on fixed port, mount ingest routes, start heartbeat * 3. If follower: register forwarding sinks with LogManager, start session heartbeat * * @since v2.1.0 — Issue #1700 */ import type { UnifiedLogEntry } from '../../logging/types.js'; import type { MetricSnapshot } from '../../metrics/types.js'; import type { MemoryLogSink } from '../../logging/sinks/MemoryLogSink.js'; import type { MemoryMetricsSink } from '../../metrics/sinks/MemoryMetricsSink.js'; import { logger } from '../../utils/logger.js'; import { isLeaderWebConsoleReachable, forceClaimLeadership, detectLegacyLeader, readLeaderLock, deleteLeaderLock, type ElectionResult, type ConsoleLeaderInfo, type LeaderPreferenceDecision } from './LeaderElection.js'; import { findPidOnPort } from './StaleProcessRecovery.js'; /** * Options for starting the unified console. */ export interface UnifiedConsoleOptions { /** This process's unique session ID */ sessionId: string; /** Stable Dollhouse session identity shown to humans and used for persistence. */ stableSessionId: string; /** Portfolio base directory (for startWebServer) */ portfolioDir: string; /** Log memory sink (for console history) */ memorySink: MemoryLogSink; /** Metrics memory sink */ metricsSink?: MemoryMetricsSink; /** MCP-AQL handler for permission routes (typed as any to avoid circular imports) */ mcpAqlHandler?: any; /** Callback to register a log sink with the LogManager */ registerLogSink: (sink: { write(entry: UnifiedLogEntry): void; flush(): Promise; close(): Promise; }) => void; /** Callback to wire SSE broadcasts after web server starts */ wireSSEBroadcasts: (webResult: { logBroadcast?: (entry: UnifiedLogEntry) => void; metricsOnSnapshot?: (snapshot: MetricSnapshot) => void; }, metricsSink?: MemoryMetricsSink) => void; /** Console port override from config file. Falls back to env var if not provided. */ port?: number; } /** * Result of starting the unified console. */ export interface UnifiedConsoleResult { role: 'leader' | 'follower'; election: ElectionResult; /** Port the console is running on (leader only) */ port?: number; /** Cleanup function to call on shutdown */ cleanup: () => Promise; } /** * Check for a running legacy (pre-authentication) DollhouseMCP console and * log a WARN-level message if one is found (#1794). * * Extracted from `startUnifiedConsole` so the wiring can be integration- * tested in isolation without spinning up a full web server and leader * election. The implementation is fire-and-forget: detection failures * are logged at DEBUG and never propagate, because a failure here must * not block leader election of the authenticated console. * * @param currentPort - The port the authenticated console intends to * bind to. Used in the warning message to help the * user tell the two consoles apart. * @param detect - Optional injection point for the detection * function. Defaults to `detectLegacyLeader`. Tests * pass a stub. * @param log - Optional injection point for the logger. Defaults * to the module logger. Tests pass a spy. * @returns The legacy leader info from `detect()`, or null if detection * threw. Exposed so tests can assert the full result shape. */ export declare function warnIfLegacyConsolePresent(currentPort: number, detect?: typeof detectLegacyLeader, log?: typeof logger): Promise> | null>; export interface PortLeaderDiscovery { leaderInfo: ConsoleLeaderInfo | null; ownerPid: number | null; source: 'api' | 'lock' | 'synthetic' | 'none'; } export interface BindFailureRecoveryResult extends PortLeaderDiscovery { lockCleanupAttempted: boolean; lockCleanupPerformed: boolean; } export interface PortOwnerReplacementDecision { shouldEvict: boolean; ownerPid: number | null; preference: LeaderPreferenceDecision | null; } interface FollowerAuthorityResolution { election: ElectionResult; discovery: PortLeaderDiscovery | null; replacement: PortOwnerReplacementDecision | null; forcedClaim: boolean; } interface FollowerAuthorityDependencies { isLeaderWebConsoleReachableImpl?: typeof isLeaderWebConsoleReachable; discoverLeaderServingPortImpl?: typeof discoverLeaderServingPort; forceClaimLeadershipImpl?: typeof forceClaimLeadership; deleteLeaderLockImpl?: typeof deleteLeaderLock; } interface DiscoveryDependencies { fetchImpl?: typeof fetch; findPidOnPortImpl?: typeof findPidOnPort; readLeaderLockImpl?: typeof readLeaderLock; } export declare function discoverLeaderServingPort(port: number, authToken: string | null, deps?: DiscoveryDependencies): Promise; interface BindFailureRecoveryDependencies extends DiscoveryDependencies { deleteLeaderLockImpl?: typeof deleteLeaderLock; } export declare function recoverLeaderBindFailure(provisionalLeader: ConsoleLeaderInfo, port: number, authToken: string | null, deps?: BindFailureRecoveryDependencies): Promise; export declare function evaluatePortOwnerReplacement(candidateLeader: ConsoleLeaderInfo, fallback: PortLeaderDiscovery): PortOwnerReplacementDecision; export declare function resolveFollowerAuthority(sessionId: string, consolePort: number, election: ElectionResult, deps?: FollowerAuthorityDependencies): Promise; /** * Start the unified web console. * * Runs leader election, then either starts the full console (leader) * or sets up event forwarding (follower). */ export declare function startUnifiedConsole(options: UnifiedConsoleOptions): Promise; export {}; //# sourceMappingURL=UnifiedConsole.d.ts.map