UNPKG

@dollhousemcp/mcp-server

Version:

DollhouseMCP - A Model Context Protocol (MCP) server that enables dynamic AI persona management from markdown files, allowing Claude and other compatible AI assistants to activate and switch between different behavioral personas.

86 lines 12.7 kB
/** * Follower-to-leader promotion manager (#1850). * * Handles the lifecycle of promoting a follower process to leader when the * current leader becomes unreachable. Extracted from UnifiedConsole.ts to * reduce complexity and allow per-instance state tracking. * * Each PromotionManager instance tracks its own attempt counter, so multiple * followers in the same process (unlikely but possible) don't interfere * with each other's promotion budgets. */ import { logger } from '../../utils/logger.js'; import { deleteLeaderLock, claimLeadership, readLeaderLock, createLeaderInfo, } from './LeaderElection.js'; const MAX_PROMOTION_ATTEMPTS = 3; export class PromotionManager { options; consolePort; startAsLeader; startAsFollower; inProgress = false; attempts = 0; constructor(options, consolePort, startAsLeader, startAsFollower) { this.options = options; this.consolePort = consolePort; this.startAsLeader = startAsLeader; this.startAsFollower = startAsFollower; } /** * Attempt promotion. Safe to call from the ForwardingSink onLeaderDeath * callback — guards against concurrent and excessive attempts. */ async promote(forwardingSink, sessionHeartbeat) { if (this.inProgress) { logger.info('[PromotionManager] Promotion already in progress — skipping'); return; } this.attempts++; if (this.attempts > MAX_PROMOTION_ATTEMPTS) { logger.error(`[PromotionManager] Attempt ${this.attempts} exceeds max (${MAX_PROMOTION_ATTEMPTS}) — giving up`); return; } this.inProgress = true; const startMs = Date.now(); try { logger.warn(`[PromotionManager] Leader death detected — promotion attempt ${this.attempts}/${MAX_PROMOTION_ATTEMPTS}`, { sessionId: this.options.sessionId, pid: process.pid, port: this.consolePort, attempt: this.attempts, }); await sessionHeartbeat.stop(); await forwardingSink.close(); await deleteLeaderLock(); const myInfo = createLeaderInfo(this.options.sessionId, this.consolePort); const claimed = await claimLeadership(myInfo); const durationMs = Date.now() - startMs; if (claimed) { logger.info('[PromotionManager] Promotion succeeded — starting as leader', { sessionId: this.options.sessionId, port: this.consolePort, durationMs, attempt: this.attempts, }); const election = { role: 'leader', leaderInfo: myInfo }; await this.startAsLeader(this.options, election, this.consolePort); } else { logger.info('[PromotionManager] Lost promotion race — following new leader', { sessionId: this.options.sessionId, durationMs, attempt: this.attempts, }); const newLeader = await readLeaderLock(); if (newLeader) { const election = { role: 'follower', leaderInfo: newLeader }; await this.startAsFollower(this.options, election, this.consolePort); } else { logger.error('[PromotionManager] No leader available after lost race'); } } } catch (err) { logger.error('[PromotionManager] Promotion failed', { error: err instanceof Error ? err.message : String(err), durationMs: Date.now() - startMs, attempt: this.attempts, }); } finally { this.inProgress = false; } } } //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiUHJvbW90aW9uTWFuYWdlci5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uLy4uL3NyYy93ZWIvY29uc29sZS9Qcm9tb3Rpb25NYW5hZ2VyLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBOzs7Ozs7Ozs7O0dBVUc7QUFFSCxPQUFPLEVBQUUsTUFBTSxFQUFFLE1BQU0sdUJBQXVCLENBQUM7QUFDL0MsT0FBTyxFQUNMLGdCQUFnQixFQUNoQixlQUFlLEVBQ2YsY0FBYyxFQUNkLGdCQUFnQixHQUVqQixNQUFNLHFCQUFxQixDQUFDO0FBSTdCLE1BQU0sc0JBQXNCLEdBQUcsQ0FBQyxDQUFDO0FBRWpDLE1BQU0sT0FBTyxnQkFBZ0I7SUFLUjtJQUNBO0lBQ0E7SUFLQTtJQVhYLFVBQVUsR0FBRyxLQUFLLENBQUM7SUFDbkIsUUFBUSxHQUFHLENBQUMsQ0FBQztJQUVyQixZQUNtQixPQUE4QixFQUM5QixXQUFtQixFQUNuQixhQUlJLEVBQ0osZUFJSTtRQVhKLFlBQU8sR0FBUCxPQUFPLENBQXVCO1FBQzlCLGdCQUFXLEdBQVgsV0FBVyxDQUFRO1FBQ25CLGtCQUFhLEdBQWIsYUFBYSxDQUlUO1FBQ0osb0JBQWUsR0FBZixlQUFlLENBSVg7SUFDcEIsQ0FBQztJQUVKOzs7T0FHRztJQUNILEtBQUssQ0FBQyxPQUFPLENBQ1gsY0FBdUMsRUFDdkMsZ0JBQWtDO1FBRWxDLElBQUksSUFBSSxDQUFDLFVBQVUsRUFBRSxDQUFDO1lBQ3BCLE1BQU0sQ0FBQyxJQUFJLENBQUMsNkRBQTZELENBQUMsQ0FBQztZQUMzRSxPQUFPO1FBQ1QsQ0FBQztRQUVELElBQUksQ0FBQyxRQUFRLEVBQUUsQ0FBQztRQUNoQixJQUFJLElBQUksQ0FBQyxRQUFRLEdBQUcsc0JBQXNCLEVBQUUsQ0FBQztZQUMzQyxNQUFNLENBQUMsS0FBSyxDQUFDLDhCQUE4QixJQUFJLENBQUMsUUFBUSxpQkFBaUIsc0JBQXNCLGVBQWUsQ0FBQyxDQUFDO1lBQ2hILE9BQU87UUFD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ZW50IjpbIi8qKlxuICogRm9sbG93ZXItdG8tbGVhZGVyIHByb21vdGlvbiBtYW5hZ2VyICgjMTg1MCkuXG4gKlxuICogSGFuZGxlcyB0aGUgbGlmZWN5Y2xlIG9mIHByb21vdGluZyBhIGZvbGxvd2VyIHByb2Nlc3MgdG8gbGVhZGVyIHdoZW4gdGhlXG4gKiBjdXJyZW50IGxlYWRlciBiZWNvbWVzIHVucmVhY2hhYmxlLiBFeHRyYWN0ZWQgZnJvbSBVbmlmaWVkQ29uc29sZS50cyB0b1xuICogcmVkdWNlIGNvbXBsZXhpdHkgYW5kIGFsbG93IHBlci1pbnN0YW5jZSBzdGF0ZSB0cmFja2luZy5cbiAqXG4gKiBFYWNoIFByb21vdGlvbk1hbmFnZXIgaW5zdGFuY2UgdHJhY2tzIGl0cyBvd24gYXR0ZW1wdCBjb3VudGVyLCBzbyBtdWx0aXBsZVxuICogZm9sbG93ZXJzIGluIHRoZSBzYW1lIHByb2Nlc3MgKHVubGlrZWx5IGJ1dCBwb3NzaWJsZSkgZG9uJ3QgaW50ZXJmZXJlXG4gKiB3aXRoIGVhY2ggb3RoZXIncyBwcm9tb3Rpb24gYnVkZ2V0cy5cbiAqL1xuXG5pbXBvcnQgeyBsb2dnZXIgfSBmcm9tICcuLi8uLi91dGlscy9sb2dnZXIuanMnO1xuaW1wb3J0IHtcbiAgZGVsZXRlTGVhZGVyTG9jayxcbiAgY2xhaW1MZWFkZXJzaGlwLFxuICByZWFkTGVhZGVyTG9jayxcbiAgY3JlYXRlTGVhZGVySW5mbyxcbiAgdHlwZSBFbGVjdGlvblJlc3VsdCxcbn0gZnJvbSAnLi9MZWFkZXJFbGVjdGlvbi5qcyc7XG5pbXBvcnQgdHlwZSB7IExlYWRlckZvcndhcmRpbmdMb2dTaW5rLCBTZXNzaW9uSGVhcnRiZWF0IH0gZnJvbSAnLi9MZWFkZXJGb3J3YXJkaW5nU2luay5qcyc7XG5pbXBvcnQgdHlwZSB7IFVuaWZpZWRDb25zb2xlT3B0aW9ucyB9IGZyb20gJy4vVW5pZmllZENvbnNvbGUuanMnO1xuXG5jb25zdCBNQVhfUFJPTU9USU9OX0FUVEVNUFRTID0gMztcblxuZXhwb3J0IGNsYXNzIFByb21vdGlvbk1hbmFnZXIge1xuICBwcml2YXRlIGluUHJvZ3Jlc3MgPSBmYWxzZTtcbiAgcHJpdmF0ZSBhdHRlbXB0cyA9IDA7XG5cbiAgY29uc3RydWN0b3IoXG4gICAgcHJpdmF0ZSByZWFkb25seSBvcHRpb25zOiBVbmlmaWVkQ29uc29sZU9wdGlvbnMsXG4gICAgcHJpdmF0ZSByZWFkb25seSBjb25zb2xlUG9ydDogbnVtYmVyLFxuICAgIHByaXZhdGUgcmVhZG9ubHkgc3RhcnRBc0xlYWRlcjogKFxuICAgICAgb3B0aW9uczogVW5pZmllZENvbnNvbGVPcHRpb25zLFxuICAgICAgZWxlY3Rpb246IEVsZWN0aW9uUmVzdWx0LFxuICAgICAgY29uc29sZVBvcnQ6IG51bWJlcixcbiAgICApID0+IFByb21pc2U8dW5rbm93bj4sXG4gICAgcHJpdmF0ZSByZWFkb25seSBzdGFydEFzRm9sbG93ZXI6IChcbiAgICAgIG9wdGlvbnM6IFVuaWZpZWRDb25zb2xlT3B0aW9ucyxcbiAgICAgIGVsZWN0aW9uOiBFbGVjdGlvblJlc3VsdCxcbiAgICAgIGNvbnNvbGVQb3J0OiBudW1iZXIsXG4gICAgKSA9PiBQcm9taXNlPHVua25vd24+LFxuICApIHt9XG5cbiAgLyoqXG4gICAqIEF0dGVtcHQgcHJvbW90aW9uLiBTYWZlIHRvIGNhbGwgZnJvbSB0aGUgRm9yd2FyZGluZ1Npbmsgb25MZWFkZXJEZWF0aFxuICAgKiBjYWxsYmFjayDigJQgZ3VhcmRzIGFnYWluc3QgY29uY3VycmVudCBhbmQgZXhjZXNzaXZlIGF0dGVtcHRzLlxuICAgKi9cbiAgYXN5bmMgcHJvbW90ZShcbiAgICBmb3J3YXJkaW5nU2luazogTGVhZGVyRm9yd2FyZGluZ0xvZ1NpbmssXG4gICAgc2Vzc2lvbkhlYXJ0YmVhdDogU2Vzc2lvbkhlYXJ0YmVhdCxcbiAgKTogUHJvbWlzZTx2b2lkPiB7XG4gICAgaWYgKHRoaXMuaW5Qcm9ncmVzcykge1xuICAgICAgbG9nZ2VyLmluZm8oJ1tQcm9tb3Rpb25NYW5hZ2VyXSBQcm9tb3Rpb24gYWxyZWFkeSBpbiBwcm9ncmVzcyDigJQgc2tpcHBpbmcnKTtcbiAgICAgIHJldHVybjtcbiAgICB9XG5cbiAgICB0aGlzLmF0dGVtcHRzKys7XG4gICAgaWYgKHRoaXMuYXR0ZW1wdHMgPiBNQVhfUFJPTU9USU9OX0FUVEVNUFRTKSB7XG4gICAgICBsb2dnZXIuZXJyb3IoYFtQcm9tb3Rpb25NYW5hZ2VyXSBBdHRlbXB0ICR7dGhpcy5hdHRlbXB0c30gZXhjZWVkcyBtYXggKCR7TUFYX1BST01PVElPTl9BVFRFTVBUU30pIOKAlCBnaXZpbmcgdXBgKTtcbiAgICAgIHJldHVybjtcbiAgICB9XG5cbiAgICB0aGlzLmluUHJvZ3Jlc3MgPSB0cnVlO1xuICAgIGNvbnN0IHN0YXJ0TXMgPSBEYXRlLm5vdygpO1xuXG4gICAgdHJ5IHtcbiAgICAgIGxvZ2dlci53YXJuKGBbUHJvbW90aW9uTWFuYWdlcl0gTGVhZGVyIGRlYXRoIGRldGVjdGVkIOKAlCBwcm9tb3Rpb24gYXR0ZW1wdCAke3RoaXMuYXR0ZW1wdHN9LyR7TUFYX1BST01PVElPTl9BVFRFTVBUU31gLCB7XG4gICAgICAgIHNlc3Npb25JZDogdGhpcy5vcHRpb25zLnNlc3Npb25JZCwgcGlkOiBwcm9jZXNzLnBpZCwgcG9ydDogdGhpcy5jb25zb2xlUG9ydCwgYXR0ZW1wdDogdGhpcy5hdHRlbXB0cyxcbiAgICAgIH0pO1xuXG4gICAgICBhd2FpdCBzZXNzaW9uSGVhcnRiZWF0LnN0b3AoKTtcbiAgICAgIGF3YWl0IGZvcndhcmRpbmdTaW5rLmNsb3NlKCk7XG4gICAgICBhd2FpdCBkZWxldGVMZWFkZXJMb2NrKCk7XG5cbiAgICAgIGNvbnN0IG15SW5mbyA9IGNyZWF0ZUxlYWRlckluZm8odGhpcy5vcHRpb25zLnNlc3Npb25JZCwgdGhpcy5jb25zb2xlUG9ydCk7XG5cbiAgICAgIGNvbnN0IGNsYWltZWQgPSBhd2FpdCBjbGFpbUxlYWRlcnNoaXAobXlJbmZvKTtcbiAgICAgIGNvbnN0IGR1cmF0aW9uTXMgPSBEYXRlLm5vdygpIC0gc3RhcnRNcztcblxuICAgICAgaWYgKGNsYWltZWQpIHtcbiAgICAgICAgbG9nZ2VyLmluZm8oJ1tQcm9tb3Rpb25NYW5hZ2VyXSBQcm9tb3Rpb24gc3VjY2VlZGVkIOKAlCBzdGFydGluZyBhcyBsZWFkZXInLCB7XG4gICAgICAgICAgc2Vzc2lvbklkOiB0aGlzLm9wdGlvbnMuc2Vzc2lvbklkLCBwb3J0OiB0aGlzLmNvbnNvbGVQb3J0LCBkdXJhdGlvbk1zLCBhdHRlbXB0OiB0aGlzLmF0dGVtcHRzLFxuICAgICAgICB9KTtcbiAgICAgICAgY29uc3QgZWxlY3Rpb246IEVsZWN0aW9uUmVzdWx0ID0geyByb2xlOiAnbGVhZGVyJywgbGVhZGVySW5mbzogbXlJbmZvIH07XG4gICAgICAgIGF3YWl0IHRoaXMuc3RhcnRBc0xlYWRlcih0aGlzLm9wdGlvbnMsIGVsZWN0aW9uLCB0aGlzLmNvbnNvbGVQb3J0KTtcbiAgICAgIH0gZWxzZSB7XG4gICAgICAgIGxvZ2dlci5pbmZvKCdbUHJvbW90aW9uTWFuYWdlcl0gTG9zdCBwcm9tb3Rpb24gcmFjZSDigJQgZm9sbG93aW5nIG5ldyBsZWFkZXInLCB7XG4gICAgICAgICAgc2Vzc2lvbklkOiB0aGlzLm9wdGlvbnMuc2Vzc2lvbklkLCBkdXJhdGlvbk1zLCBhdHRlbXB0OiB0aGlzLmF0dGVtcHRzLFxuICAgICAgICB9KTtcbiAgICAgICAgY29uc3QgbmV3TGVhZGVyID0gYXdhaXQgcmVhZExlYWRlckxvY2soKTtcbiAgICAgICAgaWYgKG5ld0xlYWRlcikge1xuICAgICAgICAgIGNvbnN0IGVsZWN0aW9uOiBFbGVjdGlvblJlc3VsdCA9IHsgcm9sZTogJ2ZvbGxvd2VyJywgbGVhZGVySW5mbzogbmV3TGVhZGVyIH07XG4gICAgICAgICAgYXdhaXQgdGhpcy5zdGFydEFzRm9sbG93ZXIodGhpcy5vcHRpb25zLCBlbGVjdGlvbiwgdGhpcy5jb25zb2xlUG9ydCk7XG4gICAgICAgIH0gZWxzZSB7XG4gICAgICAgICAgbG9nZ2VyLmVycm9yKCdbUHJvbW90aW9uTWFuYWdlcl0gTm8gbGVhZGVyIGF2YWlsYWJsZSBhZnRlciBsb3N0IHJhY2UnKTtcbiAgICAgICAgfVxuICAgICAgfVxuICAgIH0gY2F0Y2ggKGVycikge1xuICAgICAgbG9nZ2VyLmVycm9yKCdbUHJvbW90aW9uTWFuYWdlcl0gUHJvbW90aW9uIGZhaWxlZCcsIHtcbiAgICAgICAgZXJyb3I6IGVyciBpbnN0YW5jZW9mIEVycm9yID8gZXJyLm1lc3NhZ2UgOiBTdHJpbmcoZXJyKSxcbiAgICAgICAgZHVyYXRpb25NczogRGF0ZS5ub3coKSAtIHN0YXJ0TXMsIGF0dGVtcHQ6IHRoaXMuYXR0ZW1wdHMsXG4gICAgICB9KTtcbiAgICB9IGZpbmFsbHkge1xuICAgICAgdGhpcy5pblByb2dyZXNzID0gZmFsc2U7XG4gICAgfVxuICB9XG59XG4iXX0=