@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.
194 lines • 21.3 kB
JavaScript
/**
* MemoryRetentionStrategy - Retention strategy for Memory element entries
*
* Implements IRetentionStrategy for Memory entries, which have:
* - Individual entries with expiresAt dates
* - TTL-based expiration
* - Capacity limits (max entries)
*
* @module MemoryRetentionStrategy
*/
import { ElementType } from '../../../portfolio/types.js';
import { UnicodeValidator } from '../../../security/validators/unicodeValidator.js';
import { RETENTION_DEFAULTS, } from '../types.js';
/**
* Retention strategy for Memory element entries
*/
export class MemoryRetentionStrategy {
elementType = ElementType.MEMORY;
/**
* Extract retainable items from a Memory element
* Converts Memory entries to IRetainableItem interface for retention processing
*
* @param element - The Memory instance to extract entries from
* @returns Map of entry IDs to retainable item wrappers
* @throws {TypeError} If element is not a valid Memory instance
*/
getRetainableItems(element) {
const memory = element;
const items = new Map();
// Memory exposes entries through its public interface
// We need to access the entries - check if memory has a method or property
const entries = this.getMemoryEntries(memory);
for (const [id, entry] of entries) {
items.set(id, this.toRetainableItem(entry));
}
return items;
}
/**
* Check if a memory entry should be retained based on retention policy
* Evaluates expiration, pinning status, and policy configuration
*
* @param item - The retainable memory entry to check
* @param config - Retention configuration for this element type
* @returns Result indicating whether to retain and the reason
*
* Retention reasons:
* - 'pinned': Entry is marked as permanent/pinned
* - 'policy_disabled': Retention is disabled in config
* - 'no_expiry': Entry has no expiration date
* - 'valid': Entry has not expired yet
* - 'within_warning': Entry expires within warning threshold
* - 'expired': Entry has passed its expiration date
*/
checkItem(item, config) {
const now = new Date();
// Check if item is pinned
if (this.isPinned(item)) {
return {
item,
shouldRetain: true,
reason: 'pinned',
};
}
// Check if retention is disabled
if (!config.enabled) {
return {
item,
shouldRetain: true,
reason: 'policy_disabled',
};
}
// Check if item has no expiry (permanent)
if (!item.expiresAt) {
return {
item,
shouldRetain: true,
reason: 'no_expiry_set',
};
}
const expiresAt = new Date(item.expiresAt);
const msUntilExpiry = expiresAt.getTime() - now.getTime();
const daysUntilExpiry = Math.ceil(msUntilExpiry / (1000 * 60 * 60 * 24));
if (daysUntilExpiry < 0) {
// Item has expired
return {
item,
shouldRetain: false,
reason: 'expired',
daysUntilExpiry,
};
}
// Item is still valid
return {
item,
shouldRetain: true,
reason: daysUntilExpiry <= RETENTION_DEFAULTS.WARNING_THRESHOLD_DAYS
? 'within_warning'
: 'not_expired',
daysUntilExpiry,
};
}
/**
* Remove a memory entry by ID
* Uses Memory.removeEntry() public API (no reflection)
*
* @param element - The Memory instance
* @param itemId - The entry ID to remove
* @throws {Error} If element is not a valid Memory instance
*
* SECURITY: Normalizes itemId to prevent Unicode bypass attacks
*/
removeItem(element, itemId) {
// SECURITY: Normalize itemId user input
const sanitizedItemId = UnicodeValidator.normalize(itemId).normalizedContent;
const memory = element;
// Use public API - removeEntry() added in Issue #51
memory.removeEntry(sanitizedItemId);
}
/**
* Calculate expiry date for a new memory entry based on TTL config
*
* @param config - Retention configuration with defaultTtlDays
* @returns Date when entry expires, or undefined if permanent (negative TTL)
*/
calculateExpiryDate(config) {
if (config.defaultTtlDays < 0) {
return undefined; // Permanent
}
const expiry = new Date();
expiry.setDate(expiry.getDate() + config.defaultTtlDays);
return expiry;
}
/**
* Check if a memory entry is pinned (protected from deletion)
* Pinned entries are never deleted by retention policy
*
* @param item - The retainable memory entry to check
* @returns true if entry has pinned or permanent flag in metadata
*/
isPinned(item) {
// Check metadata for pinned flag
const metadata = item.originalEntry.metadata;
if (metadata && typeof metadata === 'object') {
return metadata.pinned === true || metadata.permanent === true;
}
return false;
}
/**
* Get truncated content preview for display in retention reports
*
* @param item - The retainable memory entry
* @param maxLength - Maximum preview length (default: RETENTION_DEFAULTS.MAX_PREVIEW_LENGTH)
* @returns Truncated content with ellipsis if needed
*/
getContentPreview(item, maxLength = RETENTION_DEFAULTS.MAX_PREVIEW_LENGTH) {
const content = item.originalEntry.content || '';
if (content.length <= maxLength) {
return content;
}
return content.substring(0, maxLength) + '...';
}
/**
* Convert MemoryEntry to IRetainableItem
*/
toRetainableItem(entry) {
return {
id: entry.id,
createdAt: entry.timestamp,
expiresAt: entry.expiresAt,
contentPreview: this.truncateContent(entry.content),
metadata: entry.metadata,
originalEntry: entry,
};
}
/**
* Get entries from a Memory element
* Uses the public getEntries() API (Issue #51)
*/
getMemoryEntries(memory) {
// Memory class now exposes a public getEntries() method
return memory.getEntries();
}
/**
* Truncate content for preview
*/
truncateContent(content, maxLength = RETENTION_DEFAULTS.MAX_PREVIEW_LENGTH) {
if (!content)
return '';
if (content.length <= maxLength)
return content;
return content.substring(0, maxLength) + '...';
}
}
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiTWVtb3J5UmV0ZW50aW9uU3RyYXRlZ3kuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi8uLi8uLi9zcmMvc2VydmljZXMvcmV0ZW50aW9uL3N0cmF0ZWdpZXMvTWVtb3J5UmV0ZW50aW9uU3RyYXRlZ3kudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUE7Ozs7Ozs7OztHQVNHO0FBRUgsT0FBTyxFQUFFLFdBQVcsRUFBRSxNQUFNLDZCQUE2QixDQUFDO0FBRzFELE9BQU8sRUFBRSxnQkFBZ0IsRUFBRSxNQUFNLGtEQUFrRCxDQUFDO0FBQ3BGLE9BQU8sRUFLTCxrQkFBa0IsR0FDbkIsTUFBTSxhQUFhLENBQUM7QUFTckI7O0dBRUc7QUFDSCxNQUFNLE9BQU8sdUJBQXVCO0lBQ3pCLFdBQVcsR0FBRyxXQUFXLENBQUMsTUFBTSxDQUFDO0lBRTFDOzs7Ozs7O09BT0c7SUFDSCxrQkFBa0IsQ0FBQyxPQUFnQjtRQUNqQyxNQUFNLE1BQU0sR0FBRyxPQUFpQixDQUFDO1FBQ2pDLE1BQU0sS0FBSyxHQUFHLElBQUksR0FBRyxFQUFpQyxDQUFDO1FBRXZELHNEQUFzRDtRQUN0RCwyRUFBMkU7UUFDM0UsTUFBTSxPQUFPLEdBQUcsSUFBSSxDQUFDLGdCQUFnQixDQUFDLE1BQU0sQ0FBQyxDQUFDO1FBRTlDLEtBQUssTUFBTSxDQUFDLEVBQUUsRUFBRSxLQUFLLENBQUMsSUFBSSxPQUFPLEVBQUUsQ0FBQztZQUNsQyxLQUFLLENBQUMsR0FBRyxDQUFDLEVBQUUsRUFBRSxJQUFJLENBQUMsZ0JBQWdCLENBQUMsS0FBSyxDQUFDLENBQUMsQ0FBQztRQUM5QyxDQUFDO1FBRUQsT0FBTyxLQUFLLENBQUM7SUFDZixDQUFDO0lBRUQ7Ozs7Ozs7Ozs7Ozs7OztPQWVHO0lBQ0gsU0FBUyxDQUFDLElBQTJCLEVBQUUsTUFBOEI7UUFDbkUsTUFBTSxHQUFHLEdBQUcsSUFBSSxJQUFJLEVBQUUsQ0FBQztRQUV2QiwwQkFBMEI7UUFDMUIsSUFBSSxJQUFJLENB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QU0sT0FBTyxHQUFHLElBQUksQ0FBQyxhQUFhLENBQUMsT0FBTyxJQUFJLEVBQUUsQ0FBQztRQUNqRCxJQUFJLE9BQU8sQ0FBQyxNQUFNLElBQUksU0FBUyxFQUFFLENBQUM7WUFDaEMsT0FBTyxPQUFPLENBQUM7UUFDakIsQ0FBQztRQUNELE9BQU8sT0FBTyxDQUFDLFNBQVMsQ0FBQyxDQUFDLEVBQUUsU0FBUyxDQUFDLEdBQUcsS0FBSyxDQUFDO0lBQ2pELENBQUM7SUFFRDs7T0FFRztJQUNLLGdCQUFnQixDQUFDLEtBQWtCO1FBQ3pDLE9BQU87WUFDTCxFQUFFLEVBQUUsS0FBSyxDQUFDLEVBQUU7WUFDWixTQUFTLEVBQUUsS0FBSyxDQUFDLFNBQVM7WUFDMUIsU0FBUyxFQUFFLEtBQUssQ0FBQyxTQUFTO1lBQzFCLGNBQWMsRUFBRSxJQUFJLENBQUMsZUFBZSxDQUFDLEtBQUssQ0FBQyxPQUFPLENBQUM7WUFDbkQsUUFBUSxFQUFFLEtBQUssQ0FBQyxRQUFRO1lBQ3hCLGFBQWEsRUFBRSxLQUFLO1NBQ3JCLENBQUM7SUFDSixDQUFDO0lBRUQ7OztPQUdHO0lBQ0ssZ0JBQWdCLENBQUMsTUFBYztRQUNyQyx3REFBd0Q7UUFDeEQsT0FBTyxNQUFNLENBQUMsVUFBVSxFQUFFLENBQUM7SUFDN0IsQ0FBQztJQUVEOztPQUVHO0lBQ0ssZUFBZSxDQUFDLE9BQWUsRUFBRSxZQUFvQixrQkFBa0IsQ0FBQyxrQkFBa0I7UUFDaEcsSUFBSSxDQUFDLE9BQU87WUFBRSxPQUFPLEVBQUUsQ0FBQztRQUN4QixJQUFJLE9BQU8sQ0FBQyxNQUFNLElBQUksU0FBUztZQUFFLE9BQU8sT0FBTyxDQUFDO1FBQ2hELE9BQU8sT0FBTyxDQUFDLFNBQVMsQ0FBQyxDQUFDLEVBQUUsU0FBUyxDQUFDLEdBQUcsS0FBSyxDQUFDO0lBQ2pELENBQUM7Q0FDRiIsInNvdXJjZXNDb250ZW50IjpbIi8qKlxuICogTWVtb3J5UmV0ZW50aW9uU3RyYXRlZ3kgLSBSZXRlbnRpb24gc3RyYXRlZ3kgZm9yIE1lbW9yeSBlbGVtZW50IGVudHJpZXNcbiAqXG4gKiBJbXBsZW1lbnRzIElSZXRlbnRpb25TdHJhdGVneSBmb3IgTWVtb3J5IGVudHJpZXMsIHdoaWNoIGhhdmU6XG4gKiAtIEluZGl2aWR1YWwgZW50cmllcyB3aXRoIGV4cGlyZXNBdCBkYXRlc1xuICogLSBUVEwtYmFzZWQgZXhwaXJhdGlvblxuICogLSBDYXBhY2l0eSBsaW1pdHMgKG1heCBlbnRyaWVzKVxuICpcbiAqIEBtb2R1bGUgTWVtb3J5UmV0ZW50aW9uU3RyYXRlZ3lcbiAqL1xuXG5pbXBvcnQgeyBFbGVtZW50VHlwZSB9IGZyb20gJy4uLy4uLy4uL3BvcnRmb2xpby90eXBlcy5qcyc7XG5pbXBvcnQgdHlwZSB7IE1lbW9yeUVudHJ5IH0gZnJvbSAnLi4vLi4vLi4vZWxlbWVudHMvbWVtb3JpZXMvdHlwZXMuanMnO1xuaW1wb3J0IHR5cGUgeyBNZW1vcnkgfSBmcm9tICcuLi8uLi8uLi9lbGVtZW50cy9tZW1vcmllcy9NZW1vcnkuanMnO1xuaW1wb3J0IHsgVW5pY29kZVZhbGlkYXRvciB9IGZyb20gJy4uLy4uLy4uL3NlY3VyaXR5L3ZhbGlkYXRvcnMvdW5pY29kZVZhbGlkYXRvci5qcyc7XG5pbXBvcnQge1xuICBJUmV0ZW50aW9uU3RyYXRlZ3ksXG4gIElSZXRhaW5hYmxlSXRlbSxcbiAgRWxlbWVudFJldGVudGlvbkNvbmZpZyxcbiAgUmV0ZW50aW9uQ2hlY2tSZXN1bHQsXG4gIFJFVEVOVElPTl9ERUZBVUxUUyxcbn0gZnJvbSAnLi4vdHlwZXMuanMnO1xuXG4vKipcbiAqIEFkYXB0ZXIgdG8gbWFrZSBNZW1vcnlFbnRyeSBjb25mb3JtIHRvIElSZXRhaW5hYmxlSXRlbVxuICovXG5pbnRlcmZhY2UgUmV0YWluYWJsZU1lbW9yeUVudHJ5IGV4dGVuZHMgSVJldGFpbmFibGVJdGVtIHtcbiAgb3JpZ2luYWxFbnRyeTogTWVtb3J5RW50cnk7XG59XG5cbi8qKlxuICogUmV0ZW50aW9uIHN0cmF0ZWd5IGZvciBNZW1vcnkgZWxlbWVudCBlbnRyaWVzXG4gKi9cbmV4cG9ydCBjbGFzcyBNZW1vcnlSZXRlbnRpb25TdHJhdGVneSBpbXBsZW1lbnRzIElSZXRlbnRpb25TdHJhdGVneTxSZXRhaW5hYmxlTWVtb3J5RW50cnk+IHtcbiAgcmVhZG9ubHkgZWxlbWVudFR5cGUgPSBFbGVtZW50VHlwZS5NRU1PUlk7XG5cbiAgLyoqXG4gICAqIEV4dHJhY3QgcmV0YWluYWJsZSBpdGVtcyBmcm9tIGEgTWVtb3J5IGVsZW1lbnRcbiAgICogQ29udmVydHMgTWVtb3J5IGVudHJpZXMgdG8gSVJldGFpbmFibGVJdGVtIGludGVyZmFjZSBmb3IgcmV0ZW50aW9uIHByb2Nlc3NpbmdcbiAgICpcbiAgICogQHBhcmFtIGVsZW1lbnQgLSBUaGUgTWVtb3J5IGluc3RhbmNlIHRvIGV4dHJhY3QgZW50cmllcyBmcm9tXG4gICAqIEByZXR1cm5zIE1hcCBvZiBlbnRyeSBJRHMgdG8gcmV0YWluYWJsZSBpdGVtIHdyYXBwZXJzXG4gICAqIEB0aHJvd3Mge1R5cGVFcnJvcn0gSWYgZWxlbWVudCBpcyBub3QgYSB2YWxpZCBNZW1vcnkgaW5zdGFuY2VcbiAgICovXG4gIGdldFJldGFpbmFibGVJdGVtcyhlbGVtZW50OiB1bmtub3duKTogTWFwPHN0cmluZywgUmV0YWluYWJsZU1lbW9yeUVudHJ5PiB7XG4gICAgY29uc3QgbWVtb3J5ID0gZWxlbWVudCBhcyBNZW1vcnk7XG4gICAgY29uc3QgaXRlbXMgPSBuZXcgTWFwPHN0cmluZywgUmV0YWluYWJsZU1lbW9yeUVudHJ5PigpO1xuXG4gICAgLy8gTWVtb3J5IGV4cG9zZXMgZW50cmllcyB0aHJvdWdoIGl0cyBwdWJsaWMgaW50ZXJmYWNlXG4gICAgLy8gV2UgbmVlZCB0byBhY2Nlc3MgdGhlIGVudHJpZXMgLSBjaGVjayBpZiBtZW1vcnkgaGFzIGEgbWV0aG9kIG9yIHByb3BlcnR5XG4gICAgY29uc3QgZW50cmllcyA9IHRoaXMuZ2V0TWVtb3J5RW50cmllcyhtZW1vcnkpO1xuXG4gICAgZm9yIChjb25zdCBbaWQsIGVudHJ5XSBvZiBlbnRyaWVzKSB7XG4gICAgICBpdGVtcy5zZXQoaWQsIHRoaXMudG9SZXRhaW5hYmxlSXRlbShlbnRyeSkpO1xuICAgIH1cblxuICAgIHJldHVybiBpdGVtcztcbiAgfVxuXG4gIC8qKlxuICAgKiBDaGVjayBpZiBhIG1lbW9yeSBlbnRyeSBzaG91bGQgYmUgcmV0YWluZWQgYmFzZWQgb24gcmV0ZW50aW9uIHBvbGljeVxuICAgKiBFdmFsdWF0ZXMgZXhwaXJhdGlvbiwgcGlubmluZyBzdGF0dXMsIGFuZCBwb2xpY3kgY29uZmlndXJhdGlvblxuICAgKlxuICAgKiBAcGFyYW0gaXRlbSAtIFRoZSByZXRhaW5hYmxlIG1lbW9yeSBlbnRyeSB0byBjaGVja1xuICAgKiBAcGFyYW0gY29uZmlnIC0gUmV0ZW50aW9uIGNvbmZpZ3VyYXRpb24gZm9yIHRoaXMgZWxlbWVudCB0eXBlXG4gICAqIEByZXR1cm5zIFJlc3VsdCBpbmRpY2F0aW5nIHdoZXRoZXIgdG8gcmV0YWluIGFuZCB0aGUgcmVhc29uXG4gICAqXG4gICAqIFJldGVudGlvbiByZWFzb25zOlxuICAgKiAtICdwaW5uZWQnOiBFbnRyeSBpcyBtYXJrZWQgYXMgcGVybWFuZW50L3Bpbm5lZFxuICAgKiAtICdwb2xpY3lfZGlzYWJsZWQnOiBSZXRlbnRpb24gaXMgZGlzYWJsZWQgaW4gY29uZmlnXG4gICAqIC0gJ25vX2V4cGlyeSc6IEVudHJ5IGhhcyBubyBleHBpcmF0aW9uIGRhdGVcbiAgICogLSAndmFsaWQnOiBFbnRyeSBoYXMgbm90IGV4cGlyZWQgeWV0XG4gICAqIC0gJ3dpdGhpbl93YXJuaW5nJzogRW50cnkgZXhwaXJlcyB3aXRoaW4gd2FybmluZyB0aHJlc2hvbGRcbiAgICogLSAnZXhwaXJlZCc6IEVudHJ5IGhhcyBwYXNzZWQgaXRzIGV4cGlyYXRpb24gZGF0ZVxuICAgKi9cbiAgY2hlY2tJdGVtKGl0ZW06IFJldGFpbmFibGVNZW1vcnlFbnRyeSwgY29uZmlnOiBFbGVtZW50UmV0ZW50aW9uQ29uZmlnKTogUmV0ZW50aW9uQ2hlY2tSZXN1bHQge1xuICAgIGNvbnN0IG5vdyA9IG5ldyBEYXRlKCk7XG5cbiAgICAvLyBDaGVjayBpZiBpdGVtIGlzIHBpbm5lZFxuICAgIGlmICh0aGlzLmlzUGlubmVkKGl0ZW0pKSB7XG4gICAgICByZXR1cm4ge1xuICAgICAgICBpdGVtLFxuICAgICAgICBzaG91bGRSZXRhaW46IHRydWUsXG4gICAgICAgIHJlYXNvbjogJ3Bpbm5lZCcsXG4gICAgICB9O1xuICAgIH1cblxuICAgIC8vIENoZWNrIGlmIHJldGVudGlvbiBpcyBkaXNhYmxlZFxuICAgIGlmICghY29uZmlnLmVuYWJsZWQpIHtcbiAgICAgIHJldHVybiB7XG4gICAgICAgIGl0ZW0sXG4gICAgICAgIHNob3VsZFJldGFpbjogdHJ1ZSxcbiAgICAgICAgcmVhc29uOiAncG9saWN5X2Rpc2FibGVkJyxcbiAgICAgIH07XG4gICAgfVxuXG4gICAgLy8gQ2hlY2sgaWYgaXRlbSBoYXMgbm8gZXhwaXJ5IChwZXJtYW5lbnQpXG4gICAgaWYgKCFpdGVtLmV4cGlyZXNBdCkge1xuICAgICAgcmV0dXJuIHtcbiAgICAgICAgaXRlbSxcbiAgICAgICAgc2hvdWxkUmV0YWluOiB0cnVlLFxuICAgICAgICByZWFzb246ICdub19leHBpcnlfc2V0JyxcbiAgICAgIH07XG4gICAgfVxuXG4gICAgY29uc3QgZXhwaXJlc0F0ID0gbmV3IERhdGUoaXRlbS5leHBpcmVzQXQpO1xuICAgIGNvbnN0IG1zVW50aWxFeHBpcnkgPSBleHBpcmVzQXQuZ2V0VGltZSgpIC0gbm93LmdldFRpbWUoKTtcbiAgICBjb25zdCBkYXlzVW50aWxFeHBpcnkgPSBNYXRoLmNlaWwobXNVbnRpbEV4cGlyeSAvICgxMDAwICogNjAgKiA2MCAqIDI0KSk7XG5cbiAgICBpZiAoZGF5c1VudGlsRXhwaXJ5IDwgMCkge1xuICAgICAgLy8gSXRlbSBoYXMgZXhwaXJlZFxuICAgICAgcmV0dXJuIHtcbiAgICAgICAgaXRlbSxcbiAgICAgICAgc2hvdWxkUmV0YWluOiBmYWxzZSxcbiAgICAgICAgcmVhc29uOiAnZXhwaXJlZCcsXG4gICAgICAgIGRheXNVbnRpbEV4cGlyeSxcbiAgICAgIH07XG4gICAgfVxuXG4gICAgLy8gSXRlbSBpcyBzdGlsbCB2YWxpZFxuICAgIHJldHVybiB7XG4gICAgICBpdGVtLFxuICAgICAgc2hvdWxkUmV0YWluOiB0cnVlLFxuICAgICAgcmVhc29uOiBkYXlzVW50aWxFeHBpcnkgPD0gUkVURU5USU9OX0RFRkFVTFRTLldBUk5JTkdfVEhSRVNIT0xEX0RBWVNcbiAgICAgICAgPyAnd2l0aGluX3dhcm5pbmcnXG4gICAgICAgIDogJ25vdF9leHBpcmVkJyxcbiAgICAgIGRheXNVbnRpbEV4cGlyeSxcbiAgICB9O1xuICB9XG5cbiAgLyoqXG4gICAqIFJlbW92ZSBhIG1lbW9yeSBlbnRyeSBieSBJRFxuICAgKiBVc2VzIE1lbW9yeS5yZW1vdmVFbnRyeSgpIHB1YmxpYyBBUEkgKG5vIHJlZmxlY3Rpb24pXG4gICAqXG4gICAqIEBwYXJhbSBlbGVtZW50IC0gVGhlIE1lbW9yeSBpbnN0YW5jZVxuICAgKiBAcGFyYW0gaXRlbUlkIC0gVGhlIGVudHJ5IElEIHRvIHJlbW92ZVxuICAgKiBAdGhyb3dzIHtFcnJvcn0gSWYgZWxlbWVudCBpcyBub3QgYSB2YWxpZCBNZW1vcnkgaW5zdGFuY2VcbiAgICpcbiAgICogU0VDVVJJVFk6IE5vcm1hbGl6ZXMgaXRlbUlkIHRvIHByZXZlbnQgVW5pY29kZSBieXBhc3MgYXR0YWNrc1xuICAgKi9cbiAgcmVtb3ZlSXRlbShlbGVtZW50OiB1bmtub3duLCBpdGVtSWQ6IHN0cmluZyk6IHZvaWQge1xuICAgIC8vIFNFQ1VSSVRZOiBOb3JtYWxpemUgaXRlbUlkIHVzZXIgaW5wdXRcbiAgICBjb25zdCBzYW5pdGl6ZWRJdGVtSWQgPSBVbmljb2RlVmFsaWRhdG9yLm5vcm1hbGl6ZShpdGVtSWQpLm5vcm1hbGl6ZWRDb250ZW50O1xuXG4gICAgY29uc3QgbWVtb3J5ID0gZWxlbWVudCBhcyBNZW1vcnk7XG4gICAgLy8gVXNlIHB1YmxpYyBBUEkgLSByZW1vdmVFbnRyeSgpIGFkZGVkIGluIElzc3VlICM1MVxuICAgIG1lbW9yeS5yZW1vdmVFbnRyeShzYW5pdGl6ZWRJdGVtSWQpO1xuICB9XG5cbiAgLyoqXG4gICAqIENhbGN1bGF0ZSBleHBpcnkgZGF0ZSBmb3IgYSBuZXcgbWVtb3J5IGVudHJ5IGJhc2VkIG9uIFRUTCBjb25maWdcbiAgICpcbiAgICogQHBhcmFtIGNvbmZpZyAtIFJldGVudGlvbiBjb25maWd1cmF0aW9uIHdpdGggZGVmYXVsdFR0bERheXNcbiAgICogQHJldHVybnMgRGF0ZSB3aGVuIGVudHJ5IGV4cGlyZXMsIG9yIHVuZGVmaW5lZCBpZiBwZXJtYW5lbnQgKG5lZ2F0aXZlIFRUTClcbiAgICovXG4gIGNhbGN1bGF0ZUV4cGlyeURhdGUoY29uZmlnOiBFbGVtZW50UmV0ZW50aW9uQ29uZmlnKTogRGF0ZSB8IHVuZGVmaW5lZCB7XG4gICAgaWYgKGNvbmZpZy5kZWZhdWx0VHRsRGF5cyA8IDApIHtcbiAgICAgIHJldHVybiB1bmRlZmluZWQ7IC8vIFBlcm1hbmVudFxuICAgIH1cblxuICAgIGNvbnN0IGV4cGlyeSA9IG5ldyBEYXRlKCk7XG4gICAgZXhwaXJ5LnNldERhdGUoZXhwaXJ5LmdldERhdGUoKSArIGNvbmZpZy5kZWZhdWx0VHRsRGF5cyk7XG4gICAgcmV0dXJuIGV4cGlyeTtcbiAgfVxuXG4gIC8qKlxuICAgKiBDaGVjayBpZiBhIG1lbW9yeSBlbnRyeSBpcyBwaW5uZWQgKHByb3RlY3RlZCBmcm9tIGRlbGV0aW9uKVxuICAgKiBQaW5uZWQgZW50cmllcyBhcmUgbmV2ZXIgZGVsZXRlZCBieSByZXRlbnRpb24gcG9saWN5XG4gICAqXG4gICAqIEBwYXJhbSBpdGVtIC0gVGhlIHJldGFpbmFibGUgbWVtb3J5IGVudHJ5IHRvIGNoZWNrXG4gICAqIEByZXR1cm5zIHRydWUgaWYgZW50cnkgaGFzIHBpbm5lZCBvciBwZXJtYW5lbnQgZmxhZyBpbiBtZXRhZGF0YVxuICAgKi9cbiAgaXNQaW5uZWQoaXRlbTogUmV0YWluYWJsZU1lbW9yeUVudHJ5KTogYm9vbGVhbiB7XG4gICAgLy8gQ2hlY2sgbWV0YWRhdGEgZm9yIHBpbm5lZCBmbGFnXG4gICAgY29uc3QgbWV0YWRhdGEgPSBpdGVtLm9yaWdpbmFsRW50cnkubWV0YWRhdGE7XG4gICAgaWYgKG1ldGFkYXRhICYmIHR5cGVvZiBtZXRhZGF0YSA9PT0gJ29iamVjdCcpIHtcbiAgICAgIHJldHVybiBtZXRhZGF0YS5waW5uZWQgPT09IHRydWUgfHwgbWV0YWRhdGEucGVybWFuZW50ID09PSB0cnVlO1xuICAgIH1cbiAgICByZXR1cm4gZmFsc2U7XG4gIH1cblxuICAvKipcbiAgICogR2V0IHRydW5jYXRlZCBjb250ZW50IHByZXZpZXcgZm9yIGRpc3BsYXkgaW4gcmV0ZW50aW9uIHJlcG9ydHNcbiAgICpcbiAgICogQHBhcmFtIGl0ZW0gLSBUaGUgcmV0YWluYWJsZSBtZW1vcnkgZW50cnlcbiAgICogQHBhcmFtIG1heExlbmd0aCAtIE1heGltdW0gcHJldmlldyBsZW5ndGggKGRlZmF1bHQ6IFJFVEVOVElPTl9ERUZBVUxUUy5NQVhfUFJFVklFV19MRU5HVEgpXG4gICAqIEByZXR1cm5zIFRydW5jYXRlZCBjb250ZW50IHdpdGggZWxsaXBzaXMgaWYgbmVlZGVkXG4gICAqL1xuICBnZXRDb250ZW50UHJldmlldyhpdGVtOiBSZXRhaW5hYmxlTWVtb3J5RW50cnksIG1heExlbmd0aDogbnVtYmVyID0gUkVURU5USU9OX0RFRkFVTFRTLk1BWF9QUkVWSUVXX0xFTkdUSCk6IHN0cmluZyB7XG4gICAgY29uc3QgY29udGVudCA9IGl0ZW0ub3JpZ2luYWxFbnRyeS5jb250ZW50IHx8ICcnO1xuICAgIGlmIChjb250ZW50Lmxlbmd0aCA8PSBtYXhMZW5ndGgpIHtcbiAgICAgIHJldHVybiBjb250ZW50O1xuICAgIH1cbiAgICByZXR1cm4gY29udGVudC5zdWJzdHJpbmcoMCwgbWF4TGVuZ3RoKSArICcuLi4nO1xuICB9XG5cbiAgLyoqXG4gICAqIENvbnZlcnQgTWVtb3J5RW50cnkgdG8gSVJldGFpbmFibGVJdGVtXG4gICAqL1xuICBwcml2YXRlIHRvUmV0YWluYWJsZUl0ZW0oZW50cnk6IE1lbW9yeUVudHJ5KTogUmV0YWluYWJsZU1lbW9yeUVudHJ5IHtcbiAgICByZXR1cm4ge1xuICAgICAgaWQ6IGVudHJ5LmlkLFxuICAgICAgY3JlYXRlZEF0OiBlbnRyeS50aW1lc3RhbXAsXG4gICAgICBleHBpcmVzQXQ6IGVudHJ5LmV4cGlyZXNBdCxcbiAgICAgIGNvbnRlbnRQcmV2aWV3OiB0aGlzLnRydW5jYXRlQ29udGVudChlbnRyeS5jb250ZW50KSxcbiAgICAgIG1ldGFkYXRhOiBlbnRyeS5tZXRhZGF0YSxcbiAgICAgIG9yaWdpbmFsRW50cnk6IGVudHJ5LFxuICAgIH07XG4gIH1cblxuICAvKipcbiAgICogR2V0IGVudHJpZXMgZnJvbSBhIE1lbW9yeSBlbGVtZW50XG4gICAqIFVzZXMgdGhlIHB1YmxpYyBnZXRFbnRyaWVzKCkgQVBJIChJc3N1ZSAjNTEpXG4gICAqL1xuICBwcml2YXRlIGdldE1lbW9yeUVudHJpZXMobWVtb3J5OiBNZW1vcnkpOiBNYXA8c3RyaW5nLCBNZW1vcnlFbnRyeT4ge1xuICAgIC8vIE1lbW9yeSBjbGFzcyBub3cgZXhwb3NlcyBhIHB1YmxpYyBnZXRFbnRyaWVzKCkgbWV0aG9kXG4gICAgcmV0dXJuIG1lbW9yeS5nZXRFbnRyaWVzKCk7XG4gIH1cblxuICAvKipcbiAgICogVHJ1bmNhdGUgY29udGVudCBmb3IgcHJldmlld1xuICAgKi9cbiAgcHJpdmF0ZSB0cnVuY2F0ZUNvbnRlbnQoY29udGVudDogc3RyaW5nLCBtYXhMZW5ndGg6IG51bWJlciA9IFJFVEVOVElPTl9ERUZBVUxUUy5NQVhfUFJFVklFV19MRU5HVEgpOiBzdHJpbmcge1xuICAgIGlmICghY29udGVudCkgcmV0dXJuICcnO1xuICAgIGlmIChjb250ZW50Lmxlbmd0aCA8PSBtYXhMZW5ndGgpIHJldHVybiBjb250ZW50O1xuICAgIHJldHVybiBjb250ZW50LnN1YnN0cmluZygwLCBtYXhMZW5ndGgpICsgJy4uLic7XG4gIH1cbn1cbiJdfQ==