@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
JavaScript
/**
* 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=