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.

504 lines 82.7 kB
/** * Event ingestion routes for the unified web console. * * The console leader mounts these routes so follower MCP servers can * forward their logs, metrics, and session lifecycle events. All ingested * entries are stamped with `_sessionId` in their data field and then * broadcast to SSE clients via the existing log/metrics broadcast hooks. * * Routes: * - POST /api/ingest/logs — Batched log entries from a follower * - POST /api/ingest/metrics — Metric snapshots from a follower * - POST /api/ingest/session — Session lifecycle events (started/stopped/heartbeat) * - GET /api/sessions — Active session list for the UI * * @since v2.1.0 — Issue #1700 */ import express, { Router } from 'express'; import { SlidingWindowRateLimiter } from '../../utils/SlidingWindowRateLimiter.js'; import { UnicodeValidator } from '../../security/validators/unicodeValidator.js'; import { SessionNamePool } from './SessionNames.js'; import { logger } from '../../utils/logger.js'; import { env } from '../../config/env.js'; import { PACKAGE_VERSION } from '../../generated/version.js'; import { CONSOLE_PROTOCOL_VERSION, LEGACY_CONSOLE_PROTOCOL_VERSION, } from './LeaderElection.js'; import { getSessionClientPlatformLabel, normalizeSessionClientPlatformId, } from './sessionClientPlatform.js'; /** Maximum payload size for ingestion requests */ const MAX_PAYLOAD_SIZE = '1mb'; /** Rate limit: max requests per window per source */ const RATE_LIMIT_MAX = 1000; const RATE_LIMIT_WINDOW_MS = 60_000; /** How often to check for stale sessions (ms) */ const REAPER_INTERVAL_MS = 5_000; /** How long since last heartbeat before a session is considered dead (ms) */ const SESSION_STALE_MS = 15_000; /** Timeout for legacy port federation/proxy requests (ms) */ const LEGACY_FETCH_TIMEOUT_MS = 2_000; /** How long before ended sessions are purged from the Map (ms) */ const ENDED_PURGE_MS = 5 * 60_000; // 5 minutes /** Normalize a string via UnicodeValidator (DMCP-SEC-004) */ function normalizeInput(s) { return UnicodeValidator.normalize(s).normalizedContent; } function normalizeServerVersion(version) { if (typeof version === 'string' && version.trim().length > 0) { return version.trim(); } return 'unknown'; } function normalizeConsoleProtocolVersion(version) { if (typeof version === 'number' && Number.isInteger(version) && version >= 0) { return version; } return LEGACY_CONSOLE_PROTOCOL_VERSION; } function normalizeClientPlatform(platform) { return normalizeSessionClientPlatformId(platform); } /** * Create the ingestion routes and session registry. * * @param broadcasts - Callbacks to forward ingested events to SSE clients * @returns Router and session management functions */ export function createIngestRoutes(broadcasts) { const router = Router(); const sessions = new Map(); const namePool = new SessionNamePool(); const rateLimiter = new SlidingWindowRateLimiter(RATE_LIMIT_MAX, RATE_LIMIT_WINDOW_MS); // Sessions the user explicitly killed — never come back (#1870). // Cleared only on server restart, which is appropriate since that's a new context. const killedSessions = new Set(); // Sessions waiting for a PID so we can SIGTERM them (#1870). // When the user dismisses a pid=0 orphan, we add it here. The next heartbeat // (every 10s) carries the PID — we SIGTERM immediately and move to killedSessions. const pendingKills = new Set(); /** Execute a deferred kill if we now have a PID. */ function tryExecutePendingKill(sessionId, pid) { const killPid = pid || sessions.get(sessionId)?.pid; if (!killPid) return; try { process.kill(killPid, 'SIGTERM'); } catch { /* already dead */ } const existing = sessions.get(sessionId); if (existing) existing.status = 'ended'; logger.info('[IngestRoutes] Deferred kill executed — PID arrived', { displayName: existing?.displayName, sessionId, pid: killPid, }); } /** Promote a pending kill to permanent. */ function finalizePendingKill(sessionId, pid) { tryExecutePendingKill(sessionId, pid); pendingKills.delete(sessionId); killedSessions.add(sessionId); } /** Create a new session entry for an orphan. Returns null on failure. */ function autoRegister(sessionId, pid, authenticated = false, serverVersion, consoleProtocolVersion, clientPlatform) { try { const displayName = namePool.assign(sessionId); const color = namePool.getColor(sessionId) ?? '#3b82f6'; const now = new Date().toISOString(); const normalizedClientPlatform = normalizeClientPlatform(clientPlatform); const info = { sessionId, displayName, color, pid: pid || 0, startedAt: now, lastHeartbeat: now, status: 'active', isLeader: false, authenticated, kind: 'mcp', serverVersion: normalizeServerVersion(serverVersion), consoleProtocolVersion: normalizeConsoleProtocolVersion(consoleProtocolVersion), clientPlatform: normalizedClientPlatform, clientPlatformLabel: getSessionClientPlatformLabel(normalizedClientPlatform), }; sessions.set(sessionId, info); logger.info('[IngestRoutes] Auto-registered orphaned session', { displayName, sessionId, source: pid ? 'heartbeat' : 'ingestion', }); broadcasts.sessionBroadcast?.(info); return info; } catch (err) { logger.debug('[IngestRoutes] Failed to auto-register orphaned session', { sessionId, error: err.message, }); return null; } } /** * Auto-register or update an orphaned session from ingestion data. * Returns the session (existing or newly created), or null if killed/pending. */ function ensureSession(sessionId, pid, authenticated = false, serverVersion, consoleProtocolVersion, clientPlatform) { if (killedSessions.has(sessionId)) return null; if (pendingKills.has(sessionId)) { finalizePendingKill(sessionId, pid); return null; } const existing = sessions.get(sessionId); if (!existing) { return autoRegister(sessionId, pid, authenticated, serverVersion, consoleProtocolVersion, clientPlatform); } if (existing.status === 'ended') { existing.status = 'active'; logger.info('[IngestRoutes] Revived ended session still sending data', { displayName: existing.displayName, sessionId, }); } existing.lastHeartbeat = new Date().toISOString(); if (pid && !existing.pid) { existing.pid = pid; logger.info('[IngestRoutes] Recovered PID for orphaned session', { displayName: existing.displayName, sessionId, pid, }); } if (serverVersion) { existing.serverVersion = normalizeServerVersion(serverVersion); } if (consoleProtocolVersion !== undefined) { existing.consoleProtocolVersion = normalizeConsoleProtocolVersion(consoleProtocolVersion); } if (clientPlatform !== undefined) { existing.clientPlatform = normalizeClientPlatform(clientPlatform); existing.clientPlatformLabel = getSessionClientPlatformLabel(existing.clientPlatform); } return existing; } // JSON body parsing with size limit router.use(express.json({ limit: MAX_PAYLOAD_SIZE })); /** * POST /api/ingest/logs — Receive batched log entries from a follower. */ router.post('/api/ingest/logs', (req, res) => { if (!rateLimiter.tryAcquire()) { res.status(429).json({ error: 'Rate limit exceeded' }); return; } const payload = req.body; if (!payload?.sessionId || !Array.isArray(payload.entries)) { const received = payload ? Object.keys(payload) : []; logger.warn('[IngestRoutes] Invalid log payload', { received, hasSessionId: !!payload?.sessionId, hasEntries: Array.isArray(payload?.entries) }); res.status(400).json({ error: 'Invalid payload', required: ['sessionId', 'entries'], received }); return; } payload.sessionId = normalizeInput(payload.sessionId); let count = 0; let skipped = 0; for (const entry of payload.entries) { if (!entry || typeof entry.message !== 'string') { skipped++; continue; } const stamped = { ...entry, data: { ...entry.data, _sessionId: payload.sessionId }, }; broadcasts.logBroadcast(stamped); count++; } // Update heartbeat, revive ended sessions, or auto-register orphans (#1870) const session = ensureSession(payload.sessionId); if (skipped > 0) { logger.debug(`[IngestRoutes] Log ingest from ${session?.displayName ?? payload.sessionId}: accepted=${count}, skipped=${skipped}`); } res.status(200).json({ accepted: count, skipped }); }); /** * POST /api/ingest/metrics — Receive metric snapshots from a follower. */ router.post('/api/ingest/metrics', (req, res) => { if (!rateLimiter.tryAcquire()) { res.status(429).json({ error: 'Rate limit exceeded' }); return; } const payload = req.body; if (!payload?.sessionId || !payload.snapshot) { const received = payload ? Object.keys(payload) : []; logger.warn('[IngestRoutes] Invalid metrics payload', { received }); res.status(400).json({ error: 'Invalid payload', required: ['sessionId', 'snapshot'], received }); return; } payload.sessionId = normalizeInput(payload.sessionId); if (broadcasts.metricsOnSnapshot) { broadcasts.metricsOnSnapshot(payload.snapshot); } if (broadcasts.storeMetricsSnapshot) { broadcasts.storeMetricsSnapshot(payload.snapshot, payload.sessionId); } // Update heartbeat, revive ended sessions, or auto-register orphans (#1870) const session = ensureSession(payload.sessionId); logger.debug(`[IngestRoutes] Metrics ingested from ${session?.displayName ?? payload.sessionId}`); res.status(200).json({ accepted: true }); }); /** * POST /api/ingest/session — Session lifecycle events. */ router.post('/api/ingest/session', (req, res) => { const payload = req.body; if (!payload?.sessionId || !payload.event) { const received = payload ? Object.keys(payload) : []; logger.warn('[IngestRoutes] Invalid session event payload', { received }); res.status(400).json({ error: 'Invalid payload', required: ['sessionId', 'event'], received }); return; } payload.sessionId = normalizeInput(payload.sessionId); const now = new Date().toISOString(); switch (payload.event) { case 'started': { // Killed sessions stay dead; pending kills get finalized (#1870) if (killedSessions.has(payload.sessionId)) break; if (pendingKills.has(payload.sessionId)) { finalizePendingKill(payload.sessionId, payload.pid); break; } const displayName = namePool.assign(payload.sessionId); const color = namePool.getColor(payload.sessionId) ?? '#3b82f6'; const isAuthenticated = Boolean(res.locals?.tokenEntry); const normalizedClientPlatform = normalizeClientPlatform(payload.clientPlatform); sessions.set(payload.sessionId, { sessionId: payload.sessionId, displayName, color, pid: payload.pid, startedAt: payload.startedAt || now, lastHeartbeat: now, status: 'active', isLeader: false, authenticated: isAuthenticated, kind: 'mcp', serverVersion: normalizeServerVersion(payload.serverVersion), consoleProtocolVersion: normalizeConsoleProtocolVersion(payload.consoleProtocolVersion), clientPlatform: normalizedClientPlatform, clientPlatformLabel: getSessionClientPlatformLabel(normalizedClientPlatform), }); logger.info('[IngestRoutes] Session registered', { displayName, sessionId: payload.sessionId, pid: payload.pid, color, activeSessions: Array.from(sessions.values()).filter(s => s.status === 'active').length, }); broadcasts.sessionBroadcast?.(sessions.get(payload.sessionId)); break; } case 'stopped': { const existing = sessions.get(payload.sessionId); if (existing) { existing.status = 'ended'; existing.lastHeartbeat = now; namePool.release(payload.sessionId); logger.info('[IngestRoutes] Session stopped', { displayName: existing.displayName, sessionId: payload.sessionId, pid: existing.pid, activeSessions: Array.from(sessions.values()).filter(s => s.status === 'active').length - 1, }); broadcasts.sessionBroadcast?.(existing); } break; } case 'heartbeat': { // Auto-register or update — heartbeat includes PID for recovery (#1870) ensureSession(payload.sessionId, payload.pid, false, payload.serverVersion, payload.consoleProtocolVersion, payload.clientPlatform); break; } } res.status(200).json({ ok: true }); }); /** * GET /api/sessions — List all tracked sessions. */ router.get('/api/sessions', async (_req, res) => { // Server-side active filter — the frontend also filters, but ended sessions // should never leave the API to prevent stale UI (#1870). const localSessions = Array.from(sessions.values()).filter(s => s.status === 'active'); const currentPort = env.DOLLHOUSE_WEB_CONSOLE_PORT ?? 41715; // Federate with the legacy port (3939) to show all sessions on the // machine, including unauthenticated ones from pre-auth installs. // Server-to-server avoids CORS restrictions (#1805). if (currentPort !== 3939) { try { const controller = new AbortController(); const timeout = setTimeout(() => controller.abort(), LEGACY_FETCH_TIMEOUT_MS); const legacyRes = await fetch('http://127.0.0.1:3939/api/sessions', { signal: controller.signal, }); clearTimeout(timeout); if (legacyRes.ok) { const legacyData = await legacyRes.json(); const localIds = new Set(localSessions.map(s => s.sessionId)); for (const ls of (legacyData.sessions || [])) { if (!localIds.has(ls.sessionId) && ls.status === 'active') { localSessions.push({ ...ls, authenticated: false, kind: ls.kind || 'mcp', serverVersion: normalizeServerVersion(ls.serverVersion), consoleProtocolVersion: normalizeConsoleProtocolVersion(ls.consoleProtocolVersion), clientPlatform: normalizeClientPlatform(ls.clientPlatform), clientPlatformLabel: getSessionClientPlatformLabel(normalizeClientPlatform(ls.clientPlatform)), }); } } } } catch { // Legacy instance not running or unreachable — that's fine } } res.json({ sessions: localSessions }); }); /** * POST /api/sessions/:sessionId/kill — Terminate a session's server process. */ router.post('/api/sessions/:sessionId/kill', async (req, res) => { const sessionId = req.params['sessionId']; const session = sessions.get(sessionId); if (!session) { // Session not in local Map — try proxying kill to legacy port (#1870) const currentPort = env.DOLLHOUSE_WEB_CONSOLE_PORT ?? 41715; if (currentPort !== 3939) { try { const controller = new AbortController(); const timeout = setTimeout(() => controller.abort(), LEGACY_FETCH_TIMEOUT_MS); const proxyRes = await fetch(`http://127.0.0.1:3939/api/sessions/${encodeURIComponent(sessionId)}/kill`, { method: 'POST', signal: controller.signal, }); clearTimeout(timeout); if (proxyRes.ok) { const data = await proxyRes.json(); res.json(data); return; } } catch { // Legacy instance not running — fall through to 404 } } logger.warn('[IngestRoutes] Kill requested for unknown session', { sessionId }); res.status(404).json({ error: 'Session not found', sessionId }); return; } if (!session.pid) { // Auto-registered orphan with unknown PID — queue for deferred kill (#1870). // The next heartbeat (every ~10s) carries the PID. ensureSession() will // SIGTERM the process as soon as the PID arrives. Session is gone for good. session.status = 'ended'; namePool.release(sessionId); pendingKills.add(sessionId); logger.info('[IngestRoutes] Queued deferred kill — waiting for PID via heartbeat', { displayName: session.displayName, sessionId, }); res.json({ ok: true, dismissed: session.displayName, reason: 'pending-kill' }); return; } // SIGTERM the process. Even if it fails (ESRCH = already dead, EPERM = not ours), // mark the session as permanently killed so it never reappears (#1870). try { process.kill(session.pid, 'SIGTERM'); } catch (err) { const code = err.code; if (code === 'ESRCH') { // Process already dead — treat as successful kill. } else { logger.error('[IngestRoutes] Failed to kill session', { displayName: session.displayName, sessionId, pid: session.pid, error: err.message, }); res.status(500).json({ error: 'Failed to kill session', sessionId, displayName: session.displayName, pid: session.pid, detail: err.message }); return; } } session.status = 'ended'; namePool.release(sessionId); killedSessions.add(sessionId); logger.info('[IngestRoutes] Session killed', { displayName: session.displayName, sessionId, pid: session.pid, activeSessions: Array.from(sessions.values()).filter(s => s.status === 'active').length - 1, }); res.json({ ok: true, killed: session.displayName, pid: session.pid }); }); /** Mark stale active sessions as ended. */ function reapStaleSessions(now) { for (const [id, session] of sessions) { if (session.status !== 'active') continue; if (session.isLeader || session.kind === 'console') continue; const age = now - new Date(session.lastHeartbeat).getTime(); if (age <= SESSION_STALE_MS) continue; session.status = 'ended'; namePool.release(id); logger.info('[IngestRoutes] Reaped stale session', { displayName: session.displayName, sessionId: id, pid: session.pid, lastHeartbeatAgo: `${Math.round(age / 1000)}s`, activeSessions: Array.from(sessions.values()).filter(s => s.status === 'active').length - 1, }); broadcasts.sessionBroadcast?.(session); } } /** Delete ended sessions to bound memory (#1870). */ function purgeStaleEntries(now) { for (const [id, session] of sessions) { if (session.status === 'ended' && now - new Date(session.lastHeartbeat).getTime() > ENDED_PURGE_MS) { sessions.delete(id); } } } const reaperInterval = setInterval(() => { const now = Date.now(); reapStaleSessions(now); purgeStaleEntries(now); }, REAPER_INTERVAL_MS); reaperInterval.unref(); function getSessions() { return Array.from(sessions.values()).filter(s => s.status === 'active'); } function registerLeaderSession(sessionId, pid, clientPlatform) { const displayName = namePool.assign(sessionId, true); const color = namePool.getColor(sessionId) ?? '#3b82f6'; const normalizedClientPlatform = normalizeClientPlatform(clientPlatform ?? undefined); sessions.set(sessionId, { sessionId, displayName, color, pid, startedAt: new Date().toISOString(), lastHeartbeat: new Date().toISOString(), status: 'active', isLeader: true, authenticated: true, kind: 'mcp', serverVersion: PACKAGE_VERSION, consoleProtocolVersion: CONSOLE_PROTOCOL_VERSION, clientPlatform: normalizedClientPlatform, clientPlatformLabel: getSessionClientPlatformLabel(normalizedClientPlatform), }); logger.info('[IngestRoutes] Leader session registered', { displayName, sessionId, pid, color }); } /** * Register the web console itself as a session (#1805). Ensures the * session indicator always shows at least one entry — the console the * user is currently looking at. */ function registerConsoleSession() { const consoleId = `console-${process.pid}`; if (sessions.has(consoleId)) return; const displayName = 'Web Console'; const normalizedClientPlatform = 'web-console'; sessions.set(consoleId, { sessionId: consoleId, displayName, color: '#6366f1', // indigo — distinct from puppet greens/blues pid: process.pid, startedAt: new Date().toISOString(), lastHeartbeat: new Date().toISOString(), status: 'active', isLeader: false, authenticated: true, kind: 'console', serverVersion: PACKAGE_VERSION, consoleProtocolVersion: CONSOLE_PROTOCOL_VERSION, clientPlatform: normalizedClientPlatform, clientPlatformLabel: getSessionClientPlatformLabel(normalizedClientPlatform), }); logger.info('[IngestRoutes] Console session registered', { sessionId: consoleId, pid: process.pid }); } return { router, getSessions, registerLeaderSession, registerConsoleSession }; } //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiSW5nZXN0Um91dGVzLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vLi4vc3JjL3dlYi9jb25zb2xlL0luZ2VzdFJvdXRlcy50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQTs7Ozs7Ozs7Ozs7Ozs7O0dBZUc7QUFFSCxPQUFPLE9BQU8sRUFBRSxFQUFFLE1BQU0sRUFBRSxNQUFNLFNBQVMsQ0FBQztBQUkxQyxPQUFPLEVBQUUsd0JBQXdCLEVBQUUsTUFBTSx5Q0FBeUMsQ0FBQztBQUNuRixPQUFPLEVBQUUsZ0JBQWdCLEVBQUUsTUFBTSwrQ0FBK0MsQ0FBQztBQUNqRixPQUFPLEVBQUUsZUFBZSxFQUFFLE1BQU0sbUJBQW1CLENBQUM7QUFDcEQsT0FBTyxFQUFFLE1BQU0sRUFBRSxNQUFNLHVCQUF1QixDQUFDO0FBQy9DLE9BQU8sRUFBRSxHQUFHLEVBQUUsTUFBTSxxQkFBcUIsQ0FBQztBQUMxQyxPQUFPLEVBQUUsZUFBZSxFQUFFLE1BQU0sNEJBQTRCLENBQUM7QUFDN0QsT0FBTyxFQUNMLHdCQUF3QixFQUN4QiwrQkFBK0IsR0FDaEMsTUFBTSxxQkFBcUIsQ0FBQztBQUM3QixPQUFPLEVBQ0wsNkJBQTZCLEVBQzdCLGdDQUFnQyxHQUVqQyxNQUFNLDRCQUE0QixDQUFDO0FBRXBDLGtEQUFrRDtBQUNsRCxNQUFNLGdCQUFnQixHQUFHLEtBQUssQ0FBQztBQUUvQixxREFBcUQ7QUFDckQsTUFBTSxjQUFjLEdBQUcsSUFBSSxDQUFDO0FBQzVCLE1BQU0sb0JBQW9CLEdBQUcsTUFBTSxDQUFDO0FBRXBDLGlEQUFpRDtBQUNqRCxNQUFNLGtCQUFrQixHQUFHLEtBQUssQ0FBQztBQUVqQyw2RUFBNkU7QUFDN0UsTUFBTSxnQkFBZ0IsR0FBRyxNQUFNLENBQUM7QUFFaEMsNkRBQTZEO0FBQzdELE1BQU0sdUJBQXVCLEdBQUcsS0FBSyxDQUFDO0FBRXRDLGtFQUFrRTtBQUNsRSxNQUFNLGNBQWMsR0FBRyxDQUFDLEdBQUcsTUFBTSxDQUFD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LEVBQUUscUJBQXFCLEVBQUUsc0JBQXNCLEVBQUUsQ0FBQztBQUNoRixDQUFDIiwic291cmNlc0NvbnRlbnQiOlsiLyoqXG4gKiBFdmVudCBpbmdlc3Rpb24gcm91dGVzIGZvciB0aGUgdW5pZmllZCB3ZWIgY29uc29sZS5cbiAqXG4gKiBUaGUgY29uc29sZSBsZWFkZXIgbW91bnRzIHRoZXNlIHJvdXRlcyBzbyBmb2xsb3dlciBNQ1Agc2VydmVycyBjYW5cbiAqIGZvcndhcmQgdGhlaXIgbG9ncywgbWV0cmljcywgYW5kIHNlc3Npb24gbGlmZWN5Y2xlIGV2ZW50cy4gQWxsIGluZ2VzdGVkXG4gKiBlbnRyaWVzIGFyZSBzdGFtcGVkIHdpdGggYF9zZXNzaW9uSWRgIGluIHRoZWlyIGRhdGEgZmllbGQgYW5kIHRoZW5cbiAqIGJyb2FkY2FzdCB0byBTU0UgY2xpZW50cyB2aWEgdGhlIGV4aXN0aW5nIGxvZy9tZXRyaWNzIGJyb2FkY2FzdCBob29rcy5cbiAqXG4gKiBSb3V0ZXM6XG4gKiAtIFBPU1QgL2FwaS9pbmdlc3QvbG9ncyAgICAg4oCUIEJhdGNoZWQgbG9nIGVudHJpZXMgZnJvbSBhIGZvbGxvd2VyXG4gKiAtIFBPU1QgL2FwaS9pbmdlc3QvbWV0cmljcyAg4oCUIE1ldHJpYyBzbmFwc2hvdHMgZnJvbSBhIGZvbGxvd2VyXG4gKiAtIFBPU1QgL2FwaS9pbmdlc3Qvc2Vzc2lvbiAg4oCUIFNlc3Npb24gbGlmZWN5Y2xlIGV2ZW50cyAoc3RhcnRlZC9zdG9wcGVkL2hlYXJ0YmVhdClcbiAqIC0gR0VUICAvYXBpL3Nlc3Npb25zICAgICAgICDigJQgQWN0aXZlIHNlc3Npb24gbGlzdCBmb3IgdGhlIFVJXG4gKlxuICogQHNpbmNlIHYyLjEuMCDigJQgSXNzdWUgIzE3MDBcbiAqL1xuXG5pbXBvcnQgZXhwcmVzcywgeyBSb3V0ZXIgfSBmcm9tICdleHByZXNzJztcbmltcG9ydCB0eXBlIHsgUmVxdWVzdCwgUmVzcG9uc2UgfSBmcm9tICdleHByZXNzJztcbmltcG9ydCB0eXBlIHsgVW5pZmllZExvZ0VudHJ5IH0gZnJvbSAnLi4vLi4vbG9nZ2luZy90eXBlcy5qcyc7XG5pbXBvcnQgdHlwZSB7IE1ldHJpY1NuYXBzaG90IH0gZnJvbSAnLi4vLi4vbWV0cmljcy90eXBlcy5qcyc7XG5pbXBvcnQgeyBTbGlkaW5nV2luZG93UmF0ZUxpbWl0ZXIgfSBmcm9tICcuLi8uLi91dGlscy9TbGlkaW5nV2luZG93UmF0ZUxpbWl0ZXIuanMnO1xuaW1wb3J0IHsgVW5pY29kZVZhbGlkYXRvciB9IGZyb20gJy4uLy4uL3NlY3VyaXR5L3ZhbGlkYXRvcnMvdW5pY29kZVZhbGlkYXRvci5qcyc7XG5pbXBvcnQgeyBTZXNzaW9uTmFtZVBvb2wgfSBmcm9tICcuL1Nlc3Npb25OYW1lcy5qcyc7XG5pbXBvcnQgeyBsb2dnZXIgfSBmcm9tICcuLi8uLi91dGlscy9sb2dnZXIuanMnO1xuaW1wb3J0IHsgZW52IH0gZnJvbSAnLi4vLi4vY29uZmlnL2Vudi5qcyc7XG5pbXBvcnQgeyBQQUNLQUdFX1ZFUlNJT04gfSBmcm9tICcuLi8uLi9nZW5lcmF0ZWQvdmVyc2lvbi5qcyc7XG5pbXBvcnQge1xuICBDT05TT0xFX1BST1RPQ09MX1ZFUlNJT04sXG4gIExFR0FDWV9DT05TT0xFX1BST1RPQ09MX1ZFUlNJT04sXG59IGZyb20gJy4vTGVhZGVyRWxlY3Rpb24uanMnO1xuaW1wb3J0IHtcbiAgZ2V0U2Vzc2lvbkNsaWVudFBsYXRmb3JtTGFiZWwsXG4gIG5vcm1hbGl6ZVNlc