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.

141 lines 19 kB
/** * CacheMemoryBudget - Global memory coordinator for LRU cache instances * * Tracks aggregate memory usage across all registered LRU caches and enforces * a configurable global ceiling. When total memory exceeds the budget, evicts * entries from the least-active (coldest) cache first. * * Phase 4 of the Cache Consolidation RFC (CACHE-DESIGN.md). */ import { logger } from '../utils/logger.js'; export class CacheMemoryBudget { registeredCaches = new Set(); globalLimitBytes; maxEvictionsPerEnforce; enforcing = false; constructor(options) { this.globalLimitBytes = options.globalLimitBytes; this.maxEvictionsPerEnforce = options.maxEvictionsPerEnforce ?? 50; } /** * Register a cache instance with this budget. Idempotent. */ register(cache) { this.registeredCaches.add(cache); } /** * Unregister a cache instance (e.g., during dispose). Idempotent. */ unregister(cache) { this.registeredCaches.delete(cache); } /** * Get the number of registered caches. */ getRegisteredCacheCount() { return this.registeredCaches.size; } /** * Get the sum of memory usage across all registered caches. */ getTotalMemoryBytes() { let total = 0; for (const cache of this.registeredCaches) { total += cache.getMemoryUsageBytes(); } return total; } /** * Enforce the global memory budget by evicting entries from the coldest * (least recently active) cache until total usage is under the limit. * * Called automatically via onSet callbacks registered on each cache. * Includes a reentrancy guard to prevent cascading enforcement. */ enforce() { if (this.enforcing) { return; } const totalBytes = this.getTotalMemoryBytes(); if (totalBytes <= this.globalLimitBytes) { return; } this.enforcing = true; try { // Sort caches by last activity time ascending (coldest first) const sorted = [...this.registeredCaches].sort((a, b) => a.getLastActivityTimestamp() - b.getLastActivityTimestamp()); let evictions = 0; let currentTotal = totalBytes; for (const cache of sorted) { while (currentTotal > this.globalLimitBytes && evictions < this.maxEvictionsPerEnforce) { const memBefore = cache.getMemoryUsageBytes(); const evicted = cache.evictOne(); if (!evicted) { break; // Cache is empty, move to next } const memAfter = cache.getMemoryUsageBytes(); currentTotal -= (memBefore - memAfter); evictions++; } if (currentTotal <= this.globalLimitBytes) { break; } } if (evictions > 0) { const cacheNames = sorted .filter(c => c.getStats().evictionCount > 0) .map(c => c.getName()) .join(', '); logger.info(`[CacheMemoryBudget] Budget enforced: evicted ${evictions} entries from [${cacheNames}], ` + `memory ${(totalBytes / (1024 * 1024)).toFixed(1)}MB → ${(currentTotal / (1024 * 1024)).toFixed(1)}MB ` + `(limit: ${(this.globalLimitBytes / (1024 * 1024)).toFixed(1)}MB)`); } } finally { this.enforcing = false; } } /** * Get a diagnostic report of all registered caches. */ getReport() { const caches = []; let totalBytes = 0; for (const cache of this.registeredCaches) { const stats = cache.getStats(); const memBytes = cache.getMemoryUsageBytes(); totalBytes += memBytes; caches.push({ name: cache.getName(), entries: stats.size, memoryMB: Number((memBytes / (1024 * 1024)).toFixed(2)), hitRate: stats.hitRate, lastActivityMs: cache.getLastActivityTimestamp(), }); } const budgetMB = Number((this.globalLimitBytes / (1024 * 1024)).toFixed(2)); const totalMemoryMB = Number((totalBytes / (1024 * 1024)).toFixed(2)); const report = { caches, totalMemoryMB, budgetMB, utilizationPercent: budgetMB > 0 ? Number(((totalMemoryMB / budgetMB) * 100).toFixed(1)) : 0, }; // Log summary at info level for operational visibility if (caches.length > 0) { const lowHitCaches = caches.filter(c => { const stats = [...this.registeredCaches].find(rc => rc.getName() === c.name)?.getStats(); const totalOps = (stats?.hitCount ?? 0) + (stats?.missCount ?? 0); return totalOps > 100 && c.hitRate < 0.5; }); if (lowHitCaches.length > 0) { logger.warn(`[CacheMemoryBudget] Low hit rate caches: ${lowHitCaches.map(c => `${c.name} (${(c.hitRate * 100).toFixed(0)}%)`).join(', ')}`); } } return report; } } //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiQ2FjaGVNZW1vcnlCdWRnZXQuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi9zcmMvY2FjaGUvQ2FjaGVNZW1vcnlCdWRnZXQudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUE7Ozs7Ozs7O0dBUUc7QUFHSCxPQUFPLEVBQUUsTUFBTSxFQUFFLE1BQU0sb0JBQW9CLENBQUM7QUF3QjVDLE1BQU0sT0FBTyxpQkFBaUI7SUFDWCxnQkFBZ0IsR0FBRyxJQUFJLEdBQUcsRUFBaUIsQ0FBQztJQUM1QyxnQkFBZ0IsQ0FBUztJQUN6QixzQkFBc0IsQ0FBUztJQUN4QyxTQUFTLEdBQUcsS0FBSyxDQUFDO0lBRTFCLFlBQVksT0FBaUM7UUFDM0MsSUFBSSxDQUFDLGdCQUFnQixHQUFHLE9BQU8sQ0FBQyxnQkFBZ0IsQ0FBQztRQUNqRCxJQUFJLENBQUMsc0JBQXNCLEdBQUcsT0FBTyxDQUFDLHNCQUFzQixJQUFJLEVBQUUsQ0FBQztJQUNyRSxDQUFDO0lBRUQ7O09BRUc7SUFDSCxRQUFRLENBQUMsS0FBb0I7UUFDM0IsSUFBSSxDQUFDLGdCQUFnQixDQUFDLEdBQUcsQ0FBQyxLQUFLLENBQUMsQ0FBQztJQUNuQyxDQUFDO0lBRUQ7O09BRUc7SUFDSCxVQUFVLENBQUMsS0FBb0I7UUFDN0IsSUFBSSxDQUFDLGdCQUFnQixDQUFDLE1BQU0sQ0FBQyxLQUFLLENBQUMsQ0FBQztJQUN0QyxDQUFDO0lBRUQ7O09BRUc7SUFDSCx1QkFBdUI7UUFDckIsT0FBTyxJQUFJLENBQUMsZ0JBQWdCLENBQUMsSUFBSSxDQUFDO0lBQ3BDLENBQUM7SUFFRDs7T0FFRztJQUNILG1CQUFtQjtRQUNqQixJQUFJLEtBQUssR0FBRyxDQUFDLENBQUM7UUFDZCxLQUFLLE1BQU0sS0FBSyxJQUFJLElBQUksQ0FBQyxnQkFBZ0IsRUFBRSxDQUFDO1lBQzFDLEtBQUssSUFBSSxLQUFLLENBQUMsbUJBQW1CLEVBQUUsQ0FBQztRQUN2QyxDQUFDO1FBQ0QsT0FBTyxLQUFLLENB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Q0FBQztZQUN0QixNQUFNLFlBQVksR0FBRyxNQUFNLENBQUMsTUFBTSxDQUFDLENBQUMsQ0FBQyxFQUFFO2dCQUNyQyxNQUFNLEtBQUssR0FBRyxDQUFDLEdBQUcsSUFBSSxDQUFDLGdCQUFnQixDQUFDLENBQUMsSUFBSSxDQUFDLEVBQUUsQ0FBQyxFQUFFLENBQUMsRUFBRSxDQUFDLE9BQU8sRUFBRSxLQUFLLENBQUMsQ0FBQyxJQUFJLENBQUMsRUFBRSxRQUFRLEVBQUUsQ0FBQztnQkFDekYsTUFBTSxRQUFRLEdBQUcsQ0FBQyxLQUFLLEVBQUUsUUFBUSxJQUFJLENBQUMsQ0FBQyxHQUFHLENBQUMsS0FBSyxFQUFFLFNBQVMsSUFBSSxDQUFDLENBQUMsQ0FBQztnQkFDbEUsT0FBTyxRQUFRLEdBQUcsR0FBRyxJQUFJLENBQUMsQ0FBQyxPQUFPLEdBQUcsR0FBRyxDQUFDO1lBQzNDLENBQUMsQ0FBQyxDQUFDO1lBQ0gsSUFBSSxZQUFZLENBQUMsTUFBTSxHQUFHLENBQUMsRUFBRSxDQUFDO2dCQUM1QixNQUFNLENBQUMsSUFBSSxDQUNULDRDQUE0QyxZQUFZLENBQUMsR0FBRyxDQUFDLENBQUMsQ0FBQyxFQUFFLENBQUMsR0FBRyxDQUFDLENBQUMsSUFBSSxLQUFLLENBQUMsQ0FBQyxDQUFDLE9BQU8sR0FBRyxHQUFHLENBQUMsQ0FBQyxPQUFPLENBQUMsQ0FBQyxDQUFDLElBQUksQ0FBQyxDQUFDLElBQUksQ0FBQyxJQUFJLENBQUMsRUFBRSxDQUMvSCxDQUFDO1lBQ0osQ0FBQztRQUNILENBQUM7UUFFRCxPQUFPLE1BQU0sQ0FBQztJQUNoQixDQUFDO0NBQ0YiLCJzb3VyY2VzQ29udGVudCI6WyIvKipcbiAqIENhY2hlTWVtb3J5QnVkZ2V0IC0gR2xvYmFsIG1lbW9yeSBjb29yZGluYXRvciBmb3IgTFJVIGNhY2hlIGluc3RhbmNlc1xuICpcbiAqIFRyYWNrcyBhZ2dyZWdhdGUgbWVtb3J5IHVzYWdlIGFjcm9zcyBhbGwgcmVnaXN0ZXJlZCBMUlUgY2FjaGVzIGFuZCBlbmZvcmNlc1xuICogYSBjb25maWd1cmFibGUgZ2xvYmFsIGNlaWxpbmcuIFdoZW4gdG90YWwgbWVtb3J5IGV4Y2VlZHMgdGhlIGJ1ZGdldCwgZXZpY3RzXG4gKiBlbnRyaWVzIGZyb20gdGhlIGxlYXN0LWFjdGl2ZSAoY29sZGVzdCkgY2FjaGUgZmlyc3QuXG4gKlxuICogUGhhc2UgNCBvZiB0aGUgQ2FjaGUgQ29uc29saWRhdGlvbiBSRkMgKENBQ0hFLURFU0lHTi5tZCkuXG4gKi9cblxuaW1wb3J0IHsgTFJVQ2FjaGUgfSBmcm9tICcuL0xSVUNhY2hlLmpzJztcbmltcG9ydCB7IGxvZ2dlciB9IGZyb20gJy4uL3V0aWxzL2xvZ2dlci5qcyc7XG5cbmV4cG9ydCBpbnRlcmZhY2UgQ2FjaGVNZW1vcnlCdWRnZXRPcHRpb25zIHtcbiAgLyoqIEdsb2JhbCBtZW1vcnkgbGltaXQgaW4gYnl0ZXMgKGRlZmF1bHQ6IDE1MCBNQikgKi9cbiAgZ2xvYmFsTGltaXRCeXRlczogbnVtYmVyO1xuICAvKiogTWF4aW11bSBldmljdGlvbnMgcGVyIGVuZm9yY2UoKSBjYWxsIHRvIHByZXZlbnQgcnVuYXdheSBsb29wcyAoZGVmYXVsdDogNTApICovXG4gIG1heEV2aWN0aW9uc1BlckVuZm9yY2U/OiBudW1iZXI7XG59XG5cbmV4cG9ydCBpbnRlcmZhY2UgQnVkZ2V0Q2FjaGVSZXBvcnQge1xuICBuYW1lOiBzdHJpbmc7XG4gIGVudHJpZXM6IG51bWJlcjtcbiAgbWVtb3J5TUI6IG51bWJlcjtcbiAgaGl0UmF0ZTogbnVtYmVyO1xuICBsYXN0QWN0aXZpdHlNczogbnVtYmVyO1xufVxuXG5leHBvcnQgaW50ZXJmYWNlIEJ1ZGdldFJlcG9ydCB7XG4gIGNhY2hlczogQnVkZ2V0Q2FjaGVSZXBvcnRbXTtcbiAgdG90YWxNZW1vcnlNQjogbnVtYmVyO1xuICBidWRnZXRNQjogbnVtYmVyO1xuICB1dGlsaXphdGlvblBlcmNlbnQ6IG51bWJlcjtcbn1cblxuZXhwb3J0IGNsYXNzIENhY2hlTWVtb3J5QnVkZ2V0IHtcbiAgcHJpdmF0ZSByZWFkb25seSByZWdpc3RlcmVkQ2FjaGVzID0gbmV3IFNldDxMUlVDYWNoZTxhbnk+PigpO1xuICBwcml2YXRlIHJlYWRvbmx5IGdsb2JhbExpbWl0Qnl0ZXM6IG51bWJlcjtcbiAgcHJpdmF0ZSByZWFkb25seSBtYXhFdmljdGlvbnNQZXJFbmZvcmNlOiBudW1iZXI7XG4gIHByaXZhdGUgZW5mb3JjaW5nID0gZmFsc2U7XG5cbiAgY29uc3RydWN0b3Iob3B0aW9uczogQ2FjaGVNZW1vcnlCdWRnZXRPcHRpb25zKSB7XG4gICAgdGhpcy5nbG9iYWxMaW1pdEJ5dGVzID0gb3B0aW9ucy5nbG9iYWxMaW1pdEJ5dGVzO1xuICAgIHRoaXMubWF4RXZpY3Rpb25zUGVyRW5mb3JjZSA9IG9wdGlvbnMubWF4RXZpY3Rpb25zUGVyRW5mb3JjZSA/PyA1MDtcbiAgfVxuXG4gIC8qKlxuICAgKiBSZWdpc3RlciBhIGNhY2hlIGluc3RhbmNlIHdpdGggdGhpcyBidWRnZXQuIElkZW1wb3RlbnQuXG4gICAqL1xuICByZWdpc3RlcihjYWNoZTogTFJVQ2FjaGU8YW55Pik6IHZvaWQge1xuICAgIHRoaXMucmVnaXN0ZXJlZENhY2hlcy5hZGQoY2FjaGUpO1xuICB9XG5cbiAgLyoqXG4gICAqIFVucmVnaXN0ZXIgYSBjYWNoZSBpbnN0YW5jZSAoZS5nLiwgZHVyaW5nIGRpc3Bvc2UpLiBJZGVtcG90ZW50LlxuICAgKi9cbiAgdW5yZWdpc3RlcihjYWNoZTogTFJVQ2FjaGU8YW55Pik6IHZvaWQge1xuICAgIHRoaXMucmVnaXN0ZXJlZENhY2hlcy5kZWxldGUoY2FjaGUpO1xuICB9XG5cbiAgLyoqXG4gICAqIEdldCB0aGUgbnVtYmVyIG9mIHJlZ2lzdGVyZWQgY2FjaGVzLlxuICAgKi9cbiAgZ2V0UmVnaXN0ZXJlZENhY2hlQ291bnQoKTogbnVtYmVyIHtcbiAgICByZXR1cm4gdGhpcy5yZWdpc3RlcmVkQ2FjaGVzLnNpemU7XG4gIH1cblxuICAvKipcbiAgICogR2V0IHRoZSBzdW0gb2YgbWVtb3J5IHVzYWdlIGFjcm9zcyBhbGwgcmVnaXN0ZXJlZCBjYWNoZXMuXG4gICAqL1xuICBnZXRUb3RhbE1lbW9yeUJ5dGVzKCk6IG51bWJlciB7XG4gICAgbGV0IHRvdGFsID0gMDtcbiAgICBmb3IgKGNvbnN0IGNhY2hlIG9mIHRoaXMucmVnaXN0ZXJlZENhY2hlcykge1xuICAgICAgdG90YWwgKz0gY2FjaGUuZ2V0TWVtb3J5VXNhZ2VCeXRlcygpO1xuICAgIH1cbiAgICByZXR1cm4gdG90YWw7XG4gIH1cblxuICAvKipcbiAgICogRW5mb3JjZSB0aGUgZ2xvYmFsIG1lbW9yeSBidWRnZXQgYnkgZXZpY3RpbmcgZW50cmllcyBmcm9tIHRoZSBjb2xkZXN0XG4gICAqIChsZWFzdCByZWNlbnRseSBhY3RpdmUpIGNhY2hlIHVudGlsIHRvdGFsIHVzYWdlIGlzIHVuZGVyIHRoZSBsaW1pdC5cbiAgICpcbiAgICogQ2FsbGVkIGF1dG9tYXRpY2FsbHkgdmlhIG9uU2V0IGNhbGxiYWNrcyByZWdpc3RlcmVkIG9uIGVhY2ggY2FjaGUuXG4gICAqIEluY2x1ZGVzIGEgcmVlbnRyYW5jeSBndWFyZCB0byBwcmV2ZW50IGNhc2NhZGluZyBlbmZvcmNlbWVudC5cbiAgICovXG4gIGVuZm9yY2UoKTogdm9pZCB7XG4gICAgaWYgKHRoaXMuZW5mb3JjaW5nKSB7XG4gICAgICByZXR1cm47XG4gICAgfVxuXG4gICAgY29uc3QgdG90YWxCeXRlcyA9IHRoaXMuZ2V0VG90YWxNZW1vcnlCeXRlcygpO1xuICAgIGlmICh0b3RhbEJ5dGVzIDw9IHRoaXMuZ2xvYmFsTGltaXRCeXRlcykge1xuICAgICAgcmV0dXJuO1xuICAgIH1cblxuICAgIHRoaXMuZW5mb3JjaW5nID0gdHJ1ZTtcbiAgICB0cnkge1xuICAgICAgLy8gU29ydCBjYWNoZXMgYnkgbGFzdCBhY3Rpdml0eSB0aW1lIGFzY2VuZGluZyAoY29sZGVzdCBmaXJzdClcbiAgICAgIGNvbnN0IHNvcnRlZCA9IFsuLi50aGlzLnJlZ2lzdGVyZWRDYWNoZXNdLnNvcnQoXG4gICAgICAgIChhLCBiKSA9PiBhLmdldExhc3RBY3Rpdml0eVRpbWVzdGFtcCgpIC0gYi5nZXRMYXN0QWN0aXZpdHlUaW1lc3RhbXAoKVxuICAgICAgKTtcblxuICAgICAgbGV0IGV2aWN0aW9ucyA9IDA7XG4gICAgICBsZXQgY3VycmVudFRvdGFsID0gdG90YWxCeXRlcztcblxuICAgICAgZm9yIChjb25zdCBjYWNoZSBvZiBzb3J0ZWQpIHtcbiAgICAgICAgd2hpbGUgKFxuICAgICAgICAgIGN1cnJlbnRUb3RhbCA+IHRoaXMuZ2xvYmFsTGltaXRCeXRlcyAmJlxuICAgICAgICAgIGV2aWN0aW9ucyA8IHRoaXMubWF4RXZpY3Rpb25zUGVyRW5mb3JjZVxuICAgICAgICApIHtcbiAgICAgICAgICBjb25zdCBtZW1CZWZvcmUgPSBjYWNoZS5nZXRNZW1vcnlVc2FnZUJ5dGVzKCk7XG4gICAgICAgICAgY29uc3QgZXZpY3RlZCA9IGNhY2hlLmV2aWN0T25lKCk7XG4gICAgICAgICAgaWYgKCFldmljdGVkKSB7XG4gICAgICAgICAgICBicmVhazsgLy8gQ2FjaGUgaXMgZW1wdHksIG1vdmUgdG8gbmV4dFxuICAgICAgICAgIH1cbiAgICAgICAgICBjb25zdCBtZW1BZnRlciA9IGNhY2hlLmdldE1lbW9yeVVzYWdlQnl0ZXMoKTtcbiAgICAgICAgICBjdXJyZW50VG90YWwgLT0gKG1lbUJlZm9yZSAtIG1lbUFmdGVyKTtcbiAgICAgICAgICBldmljdGlvbnMrKztcbiAgICAgICAgfVxuXG4gICAgICAgIGlmIChjdXJyZW50VG90YWwgPD0gdGhpcy5nbG9iYWxMaW1pdEJ5dGVzKSB7XG4gICAgICAgICAgYnJlYWs7XG4gICAgICAgIH1cbiAgICAgIH1cblxuICAgICAgaWYgKGV2aWN0aW9ucyA+IDApIHtcbiAgICAgICAgY29uc3QgY2FjaGVOYW1lcyA9IHNvcnRlZFxuICAgICAgICAgIC5maWx0ZXIoYyA9PiBjLmdldFN0YXRzKCkuZXZpY3Rpb25Db3VudCA+IDApXG4gICAgICAgICAgLm1hcChjID0+IGMuZ2V0TmFtZSgpKVxuICAgICAgICAgIC5qb2luKCcsICcpO1xuICAgICAgICBsb2dnZXIuaW5mbyhcbiAgICAgICAgICBgW0NhY2hlTWVtb3J5QnVkZ2V0XSBCdWRnZXQgZW5mb3JjZWQ6IGV2aWN0ZWQgJHtldmljdGlvbnN9IGVudHJpZXMgZnJvbSBbJHtjYWNoZU5hbWVzfV0sIGAgK1xuICAgICAgICAgIGBtZW1vcnkgJHsodG90YWxCeXRlcyAvICgxMDI0ICogMTAyNCkpLnRvRml4ZWQoMSl9TUIg4oaSICR7KGN1cnJlbnRUb3RhbCAvICgxMDI0ICogMTAyNCkpLnRvRml4ZWQoMSl9TUIgYCArXG4gICAgICAgICAgYChsaW1pdDogJHsodGhpcy5nbG9iYWxMaW1pdEJ5dGVzIC8gKDEwMjQgKiAxMDI0KSkudG9GaXhlZCgxKX1NQilgXG4gICAgICAgICk7XG4gICAgICB9XG4gICAgfSBmaW5hbGx5IHtcbiAgICAgIHRoaXMuZW5mb3JjaW5nID0gZmFsc2U7XG4gICAgfVxuICB9XG5cbiAgLyoqXG4gICAqIEdldCBhIGRpYWdub3N0aWMgcmVwb3J0IG9mIGFsbCByZWdpc3RlcmVkIGNhY2hlcy5cbiAgICovXG4gIGdldFJlcG9ydCgpOiBCdWRnZXRSZXBvcnQge1xuICAgIGNvbnN0IGNhY2hlczogQnVkZ2V0Q2FjaGVSZXBvcnRbXSA9IFtdO1xuICAgIGxldCB0b3RhbEJ5dGVzID0gMDtcblxuICAgIGZvciAoY29uc3QgY2FjaGUgb2YgdGhpcy5yZWdpc3RlcmVkQ2FjaGVzKSB7XG4gICAgICBjb25zdCBzdGF0cyA9IGNhY2hlLmdldFN0YXRzKCk7XG4gICAgICBjb25zdCBtZW1CeXRlcyA9IGNhY2hlLmdldE1lbW9yeVVzYWdlQnl0ZXMoKTtcbiAgICAgIHRvdGFsQnl0ZXMgKz0gbWVtQnl0ZXM7XG5cbiAgICAgIGNhY2hlcy5wdXNoKHtcbiAgICAgICAgbmFtZTogY2FjaGUuZ2V0TmFtZSgpLFxuICAgICAgICBlbnRyaWVzOiBzdGF0cy5zaXplLFxuICAgICAgICBtZW1vcnlNQjogTnVtYmVyKChtZW1CeXRlcyAvICgxMDI0ICogMTAyNCkpLnRvRml4ZWQoMikpLFxuICAgICAgICBoaXRSYXRlOiBzdGF0cy5oaXRSYXRlLFxuICAgICAgICBsYXN0QWN0aXZpdHlNczogY2FjaGUuZ2V0TGFzdEFjdGl2aXR5VGltZXN0YW1wKCksXG4gICAgICB9KTtcbiAgICB9XG5cbiAgICBjb25zdCBidWRnZXRNQiA9IE51bWJlcigodGhpcy5nbG9iYWxMaW1pdEJ5dGVzIC8gKDEwMjQgKiAxMDI0KSkudG9GaXhlZCgyKSk7XG4gICAgY29uc3QgdG90YWxNZW1vcnlNQiA9IE51bWJlcigodG90YWxCeXRlcyAvICgxMDI0ICogMTAyNCkpLnRvRml4ZWQoMikpO1xuXG4gICAgY29uc3QgcmVwb3J0OiBCdWRnZXRSZXBvcnQgPSB7XG4gICAgICBjYWNoZXMsXG4gICAgICB0b3RhbE1lbW9yeU1CLFxuICAgICAgYnVkZ2V0TUIsXG4gICAgICB1dGlsaXphdGlvblBlcmNlbnQ6IGJ1ZGdldE1CID4gMFxuICAgICAgICA/IE51bWJlcigoKHRvdGFsTWVtb3J5TUIgLyBidWRnZXRNQikgKiAxMDApLnRvRml4ZWQoMSkpXG4gICAgICAgIDogMCxcbiAgICB9O1xuXG4gICAgLy8gTG9nIHN1bW1hcnkgYXQgaW5mbyBsZXZlbCBmb3Igb3BlcmF0aW9uYWwgdmlzaWJpbGl0eVxuICAgIGlmIChjYWNoZXMubGVuZ3RoID4gMCkge1xuICAgICAgY29uc3QgbG93SGl0Q2FjaGVzID0gY2FjaGVzLmZpbHRlcihjID0+IHtcbiAgICAgICAgY29uc3Qgc3RhdHMgPSBbLi4udGhpcy5yZWdpc3RlcmVkQ2FjaGVzXS5maW5kKHJjID0+IHJjLmdldE5hbWUoKSA9PT0gYy5uYW1lKT8uZ2V0U3RhdHMoKTtcbiAgICAgICAgY29uc3QgdG90YWxPcHMgPSAoc3RhdHM/LmhpdENvdW50ID8/IDApICsgKHN0YXRzPy5taXNzQ291bnQgPz8gMCk7XG4gICAgICAgIHJldHVybiB0b3RhbE9wcyA+IDEwMCAmJiBjLmhpdFJhdGUgPCAwLjU7XG4gICAgICB9KTtcbiAgICAgIGlmIChsb3dIaXRDYWNoZXMubGVuZ3RoID4gMCkge1xuICAgICAgICBsb2dnZXIud2FybihcbiAgICAgICAgICBgW0NhY2hlTWVtb3J5QnVkZ2V0XSBMb3cgaGl0IHJhdGUgY2FjaGVzOiAke2xvd0hpdENhY2hlcy5tYXAoYyA9PiBgJHtjLm5hbWV9ICgkeyhjLmhpdFJhdGUgKiAxMDApLnRvRml4ZWQoMCl9JSlgKS5qb2luKCcsICcpfWBcbiAgICAgICAgKTtcbiAgICAgIH1cbiAgICB9XG5cbiAgICByZXR1cm4gcmVwb3J0O1xuICB9XG59XG4iXX0=