@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.
473 lines • 65.6 kB
JavaScript
/**
* SkillManager - Refactored to extend BaseElementManager for shared CRUD logic.
* Maintains skill-specific behaviors such as trigger validation, import/export,
* and creation workflows while eliminating duplicated file handling code.
*/
import { BaseElementManager } from '../base/BaseElementManager.js';
import { Skill } from './Skill.js';
import { ElementType } from '../../portfolio/types.js';
import { toSingularLabel } from '../../utils/elementTypeNormalization.js';
import { SecurityMonitor } from '../../security/securityMonitor.js';
import { sanitizeInput } from '../../security/InputValidator.js';
import { logger } from '../../utils/logger.js';
import { ElementMessages } from '../../utils/elementMessages.js';
import { sanitizeGatekeeperPolicy } from '../../handlers/mcp-aql/policies/ElementPolicies.js';
import { SECURITY_LIMITS } from '../../security/constants.js';
// Validation constants for skill triggers
const MAX_TRIGGER_LENGTH = 50;
// Allows alphanumeric, hyphens, underscores, @ (mentions/emails), . (domains)
const TRIGGER_VALIDATION_REGEX = /^[a-zA-Z0-9\-_@.]+$/;
// Issue #83: Centralized active element limits (configurable via env vars)
import { getActiveElementLimitConfig, getMaxActiveLimit } from '../../config/active-element-limits.js';
export class SkillManager extends BaseElementManager {
metadataService;
triggerValidationService;
validationService;
serializationService;
// Track active skills by name (stable identifier)
activeSkillNames = new Set();
constructor(portfolioManager, fileLockManager, fileOperationsService, validationRegistry, serializationService, metadataService, fileWatchService, memoryBudget, backupService) {
super(ElementType.SKILL, portfolioManager, fileLockManager, { fileWatchService, memoryBudget, backupService }, fileOperationsService, validationRegistry);
this.metadataService = metadataService;
this.triggerValidationService = validationRegistry.getTriggerValidationService();
this.validationService = validationRegistry.getValidationService();
this.serializationService = serializationService;
}
getElementLabel() {
return 'skill';
}
/**
* Create a new skill element and persist it to disk.
* FIX: Issue #20 - Add duplicate name checking
*/
async create(data) {
// Use specialized validator for input validation
const validationResult = await this.validator.validateCreate(data);
if (!validationResult.isValid) {
throw new Error(`Validation failed: ${validationResult.errors.join(', ')}`);
}
// Log warnings if any
if (validationResult.warnings && validationResult.warnings.length > 0) {
logger.warn(`Skill creation warnings: ${validationResult.warnings.join(', ')}`);
}
// Get sanitized values for file operations (validator already validated, we just need sanitized values)
const nameInput = data.name || 'new-skill';
const nameResult = this.validationService.validateAndSanitizeInput(nameInput, {
maxLength: SECURITY_LIMITS.MAX_NAME_LENGTH,
allowSpaces: true
});
const sanitizedName = nameResult.sanitizedValue;
// Use inherited getElementFilename() for consistent filename normalization
const filename = this.getElementFilename(sanitizedName);
// FIX: Issue #20 - Check for duplicate before creating
const existingSkills = await this.list();
const duplicate = existingSkills.find(s => s.metadata.name.toLowerCase() === sanitizedName.toLowerCase());
if (duplicate) {
throw new Error(`A skill named "${sanitizedName}" already exists`);
}
const { content, instructions, ...metadata } = data;
// Dual-field: instructions = behavioral directives, content = reference material
// For skills, instructions is the primary field (how to apply the skill)
const effectiveInstructions = instructions || content || '';
const effectiveContent = instructions ? (content || '') : '';
const skill = new Skill({
...metadata,
name: sanitizedName,
description: data.description || ''
}, effectiveInstructions, this.metadataService, effectiveContent);
await this.save(skill, filename);
// Note: No reload() here — save() caches the element correctly.
// See Issue #491 for why PersonaManager's reload-after-create was removed.
SecurityMonitor.logSecurityEvent({
type: 'ELEMENT_CREATED',
severity: 'LOW',
source: 'SkillManager.create',
details: `Skill created: ${skill.metadata.name}`
});
return skill;
}
/**
* Import a skill from YAML or JSON input formats.
*/
async importElement(data, format = 'yaml') {
try {
let metadata;
let instructions;
if (format === 'yaml') {
// Use SerializationService for YAML parsing
const result = this.serializationService.parseFrontmatter(data, {
maxYamlSize: 64 * 1024,
validateContent: true,
source: 'SkillManager.importElement'
});
// Extract metadata and instructions
if (result.data.metadata) {
metadata = result.data.metadata;
instructions = result.data.instructions || result.content || '';
}
else {
metadata = result.data;
instructions = result.data.instructions || result.content || '';
delete metadata.instructions;
}
}
else {
// Use SerializationService for JSON parsing
const parsed = this.serializationService.parseJson(data, {
source: 'SkillManager.importElement'
});
if (parsed.metadata) {
metadata = parsed.metadata;
instructions = parsed.instructions || '';
}
else {
metadata = parsed;
instructions = parsed.instructions || '';
delete metadata.instructions;
}
}
return new Skill(metadata, instructions, this.metadataService);
}
catch (error) {
logger.error('Failed to import skill:', error);
throw error;
}
}
/**
* Export a skill to YAML or JSON.
*/
async exportElement(element, format = 'yaml') {
if (format === 'yaml') {
const data = {
metadata: element.metadata,
instructions: element.instructions,
parameters: Object.fromEntries(element.parameters)
};
return this.serializationService.dumpYaml(data, {
schema: 'json', // Fix #914: failsafe corrupts booleans/numbers to strings
noRefs: true,
skipInvalid: true
});
}
// For JSON, spread metadata properties directly (maintain backward compatibility)
const data = {
...element.metadata,
instructions: element.instructions,
parameters: Object.fromEntries(element.parameters)
};
return this.serializationService.stringifyJson(data, {
pretty: true,
indent: 2
});
}
getFileExtension() {
return '.md';
}
/**
* Validate and normalize metadata parsed from frontmatter.
*/
async parseMetadata(data) {
const metadata = { ...data };
if (Array.isArray(data?.triggers)) {
const validationResult = this.triggerValidationService.validateTriggers(data.triggers, ElementType.SKILL, metadata.name || 'unknown');
metadata.triggers = validationResult.validTriggers;
}
// Issue #676: Sanitize gatekeeper policy on load to prevent prompt-injection attacks
// Malformed policies are stripped and logged as security events (never reach enforcement)
if (metadata.gatekeeper) {
metadata.gatekeeper = sanitizeGatekeeperPolicy(metadata.gatekeeper, metadata.name || 'unknown', 'skill', metadata);
}
return metadata;
}
/**
* Create skill instance from metadata/content.
* Dual-field: detects v2 format (instructions in YAML frontmatter) vs v1 (body = instructions).
*/
createElement(metadata, bodyContent) {
// Fix #912: Prefer explicit format_version marker, fall back to instructions-presence check
const isV2 = metadata.format_version === 'v2' || !!metadata.instructions;
delete metadata.format_version; // Strip marker from runtime metadata
const metadataInstructions = metadata.instructions;
let instructions;
let content;
if (isV2 && metadataInstructions) {
// v2 format: instructions from YAML, body is reference content
instructions = metadataInstructions;
content = bodyContent;
delete metadata.instructions;
}
else {
// v1 format: body text is instructions
instructions = bodyContent;
content = '';
}
return new Skill(metadata, instructions, this.metadataService, content);
}
/**
* Serialize a skill to markdown with frontmatter.
* v2.0 format: instructions in YAML frontmatter, content as body.
*/
async serializeElement(element) {
// Prepare metadata with version and instructions
const metadata = { ...element.metadata };
// Issue #755: Serialize type as singular and persist unique_id
metadata.type = toSingularLabel(ElementType.SKILL);
metadata.unique_id = element.id;
if (element.version) {
metadata.version = element.version;
}
// Fix #912: Explicit format marker replaces fragile instructions-presence detection
metadata.format_version = 'v2';
// Write instructions to YAML frontmatter (v2.0 dual-field format)
if (element.instructions) {
metadata.instructions = element.instructions;
}
// Body is the reference content
const body = element.content || this.buildDefaultBody(element);
return this.serializationService.createFrontmatter(metadata, body, {
method: 'matter',
cleanMetadata: true,
cleaningStrategy: 'remove-both', // Fix #913: standardize across all managers
schema: 'json' // Fix #914: failsafe corrupts booleans/numbers to strings
});
}
buildDefaultBody(skill) {
const name = (skill.metadata.name ?? '').trim();
const description = (skill.metadata.description ?? '').trim();
const lines = [];
if (name) {
lines.push(`# ${name}`);
lines.push('');
}
if (description) {
lines.push(description);
}
return lines.join('\n');
}
/**
* Override list() to apply active status to skills
*/
async list() {
const skills = await super.list();
// Apply active status to skills that are in the active set (by name)
for (const skill of skills) {
if (this.activeSkillNames.has(skill.metadata.name)) {
// Access the protected _status field and set to ACTIVE (value 2)
await skill.activate();
}
}
return skills;
}
/**
* Activate a skill by name or identifier
*
* Issue #24 (LOW PRIORITY): Performance optimization using findByName()
* Issue #24 (LOW PRIORITY): Consistent error messages using ElementMessages
* Issue #24 (LOW PRIORITY): Cleanup trigger for memory leak prevention
*/
async activateSkill(identifier) {
// PERFORMANCE FIX: Use findByName() instead of list() to avoid loading all skills
// This provides O(1) or O(log n) lookup instead of O(n) for large portfolios
const skill = await this.findByName(identifier);
if (!skill) {
return {
success: false,
// CONSISTENCY FIX: Use standardized error message format
message: ElementMessages.notFound(ElementType.SKILL, identifier)
};
}
// MEMORY LEAK FIX: Check if cleanup is needed before adding
this.checkAndCleanupActiveSet();
// Add to active set (by name, which is stable across reloads)
this.activeSkillNames.add(skill.metadata.name);
// Update skill status in memory
await skill.activate();
SecurityMonitor.logSecurityEvent({
type: 'ELEMENT_CREATED',
severity: 'LOW',
source: 'SkillManager.activateSkill',
details: `Skill activated: ${skill.metadata.name}`
});
logger.info(`Skill activated: ${skill.metadata.name}`);
return {
success: true,
// CONSISTENCY FIX: Use standardized success message format
message: ElementMessages.activated(ElementType.SKILL, skill.metadata.name),
skill
};
}
/**
* Deactivate a skill by name or identifier
*
* Issue #24 (LOW PRIORITY): Performance optimization using findByName()
* Issue #24 (LOW PRIORITY): Consistent error messages using ElementMessages
*/
async deactivateSkill(identifier) {
// PERFORMANCE FIX: Use findByName() instead of list()
const skill = await this.findByName(identifier);
if (!skill) {
return {
success: false,
// CONSISTENCY FIX: Use standardized error message format
message: ElementMessages.notFound(ElementType.SKILL, identifier)
};
}
// Remove from active set
this.activeSkillNames.delete(skill.metadata.name);
// Update skill status in memory
await skill.deactivate();
logger.info(`Skill deactivated: ${skill.metadata.name}`);
return {
success: true,
// CONSISTENCY FIX: Use standardized success message format
message: ElementMessages.deactivated(ElementType.SKILL, skill.metadata.name)
};
}
/**
* Get all active skills
*/
async getActiveSkills() {
const results = [];
for (const name of this.activeSkillNames) {
const skill = await this.findByName(name);
if (skill)
results.push(skill);
}
return results;
}
/**
* Check if active set cleanup is needed and perform cleanup if necessary
*
* Issue #24 (LOW PRIORITY): Memory leak prevention
*
* CLEANUP STRATEGY:
* - Triggers when set reaches 90% of maximum capacity
* - Validates that all active skills still exist in portfolio
* - Removes stale references (skills that were deleted)
* - Logs cleanup operations for monitoring
*
* PERFORMANCE NOTES:
* - Only runs when threshold is reached (not on every activation)
* - Uses efficient Set operations
* - Minimal impact on activation performance
*
* @private
*/
checkAndCleanupActiveSet() {
const { max, cleanupThreshold } = getActiveElementLimitConfig('skills');
// Below threshold — no action needed
if (this.activeSkillNames.size < cleanupThreshold) {
return;
}
// At or above max — warn before cleanup
if (this.activeSkillNames.size >= max) {
logger.warn(`Active skills limit reached (${max}). ` +
`Consider deactivating unused skills or setting DOLLHOUSE_MAX_ACTIVE_SKILLS to a higher value.`);
SecurityMonitor.logSecurityEvent({
type: 'ELEMENT_CREATED',
severity: 'MEDIUM',
source: 'SkillManager.checkAndCleanupActiveSet',
details: `Active skills limit reached: ${this.activeSkillNames.size}/${max}`
});
}
// At or above threshold — proactively clean stale entries
void this.cleanupStaleActiveSkills();
}
/**
* Clean up stale entries from active skills set
*
* Issue #24 (LOW PRIORITY): Memory leak prevention
*
* Validates that all active skills still exist and removes orphaned references.
* This prevents memory leaks from deleted skills that weren't properly deactivated.
*
* @private
*/
async cleanupStaleActiveSkills() {
try {
const startSize = this.activeSkillNames.size;
const skills = await this.list();
const existingSkillNames = new Set(skills.map(s => s.metadata.name));
// Remove any active skill names that no longer exist in portfolio
const staleNames = [];
for (const activeName of this.activeSkillNames) {
if (!existingSkillNames.has(activeName)) {
this.activeSkillNames.delete(activeName);
staleNames.push(activeName);
}
}
const endSize = this.activeSkillNames.size;
const removed = startSize - endSize;
if (removed > 0) {
logger.info(`Cleaned up ${removed} stale active skill reference(s). ` +
`Active skills: ${endSize}/${getMaxActiveLimit('skills')}`);
SecurityMonitor.logSecurityEvent({
type: 'ELEMENT_DELETED',
severity: 'LOW',
source: 'SkillManager.cleanupStaleActiveSkills',
details: `Removed ${removed} stale active skill references`,
additionalData: {
removedCount: removed,
activeCount: endSize,
staleNames: staleNames.join(', ')
}
});
}
}
catch (error) {
// Log error but don't throw - cleanup failures shouldn't break activation
logger.error('Failed to cleanup stale active skills:', error);
SecurityMonitor.logSecurityEvent({
type: 'ELEMENT_DELETED',
severity: 'LOW',
source: 'SkillManager.cleanupStaleActiveSkills',
details: `Cleanup failed: ${error instanceof Error ? error.message : String(error)}`
});
}
}
/**
* Normalize and validate skill triggers.
*/
validateAndProcessTriggers(triggers, skillName) {
const validTriggers = [];
const rejectedTriggers = [];
const rawTriggers = triggers.slice(0, 20);
// SECURITY: Validate BEFORE sanitization to reject invalid characters
// This prevents 'bad!trigger' from becoming 'badtrigger' and passing
for (const raw of rawTriggers) {
const rawTrigger = String(raw).trim();
// Check if empty
if (!rawTrigger) {
rejectedTriggers.push(`"${raw}" (empty)`);
continue;
}
// SECURITY: Validate format BEFORE sanitization
if (!TRIGGER_VALIDATION_REGEX.test(rawTrigger)) {
rejectedTriggers.push(`"${raw}" (invalid format - allowed: letters, numbers, hyphens, underscores, @ and .)`);
continue;
}
// Only sanitize AFTER validation passes (for length limits)
const sanitized = sanitizeInput(rawTrigger, MAX_TRIGGER_LENGTH);
if (sanitized) {
validTriggers.push(sanitized);
}
}
if (rejectedTriggers.length > 0) {
logger.warn(`Skill "${skillName}": Rejected ${rejectedTriggers.length} invalid trigger(s)`, {
skillName,
rejectedTriggers,
acceptedCount: validTriggers.length
});
}
if (triggers.length > 20) {
logger.warn(`Skill "${skillName}": Trigger limit exceeded`, {
skillName,
providedCount: triggers.length,
limit: 20,
truncated: triggers.length - 20
});
}
return validTriggers;
}
}
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiU2tpbGxNYW5hZ2VyLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vLi4vc3JjL2VsZW1lbnRzL3NraWxscy9Ta2lsbE1hbmFnZXIudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUE7Ozs7R0FJRztBQUVILE9BQU8sRUFBRSxrQkFBa0IsRUFBRSxNQUFNLCtCQUErQixDQUFDO0FBQ25FLE9BQU8sRUFBRSxLQUFLLEVBQWlCLE1BQU0sWUFBWSxDQUFDO0FBQ2xELE9BQU8sRUFBRSxXQUFXLEVBQUUsTUFBTSwwQkFBMEIsQ0FBQztBQUN2RCxPQUFPLEVBQUUsZUFBZSxFQUFFLE1BQU0seUNBQXlDLENBQUM7QUFDMUUsT0FBTyxFQUFFLGVBQWUsRUFBRSxNQUFNLG1DQUFtQyxDQUFDO0FBQ3BFLE9BQU8sRUFBRSxhQUFhLEVBQUUsTUFBTSxrQ0FBa0MsQ0FBQztBQUVqRSxPQUFPLEVBQUUsTUFBTSxFQUFFLE1BQU0sdUJBQXVCLENBQUM7QUFTL0MsT0FBTyxFQUFFLGVBQWUsRUFBRSxNQUFNLGdDQUFnQyxDQUFDO0FBQ2pFLE9BQU8sRUFBRSx3QkFBd0IsRUFBRSxNQUFNLG9EQUFvRCxDQUFDO0FBQzlGLE9BQU8sRUFBRSxlQUFlLEVBQUUsTUFBTSw2QkFBNkIsQ0FBQztBQUU5RCwwQ0FBMEM7QUFDMUMsTUFBTSxrQkFBa0IsR0FBRyxFQUFFLENBQUM7QUFDOUIsOEVBQThFO0FBQzlFLE1BQU0sd0JBQXdCLEdBQUcscUJBQXFCLENBQUM7QUFFdkQsMkVBQTJFO0FBQzNFLE9BQU8sRUFBRSwyQkFBMkIsRUFBRSxpQkFBaUIsRUFBRSxNQUFNLHVDQUF1QyxDQUFDO0FBRXZHLE1BQU0sT0FBTyxZQUFhLFNBQVEsa0JBQXlCO0lBYS9DO0lBWkYsd0JBQXdCLENBQTJCO0lBQ25ELGlCQUFpQixDQUFvQjtJQUNyQyxvQkFBb0IsQ0FBdUI7SUFDbkQsa0RBQWtEO0lBQzFDLGdCQUFnQixHQUFnQixJQUFJLEdBQUcsRUFBRSxDQUFDO0lBRWxELFlB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R0FBRyxFQUFFLEVBQUUsQ0FBQztZQUN6QixNQUFNLENBQUMsSUFBSSxDQUFDLFVBQVUsU0FBUywyQkFBMkIsRUFBRTtnQkFDMUQsU0FBUztnQkFDVCxhQUFhLEVBQUUsUUFBUSxDQUFDLE1BQU07Z0JBQzlCLEtBQUssRUFBRSxFQUFFO2dCQUNULFNBQVMsRUFBRSxRQUFRLENBQUMsTUFBTSxHQUFHLEVBQUU7YUFDaEMsQ0FBQyxDQUFDO1FBQ0wsQ0FBQztRQUVELE9BQU8sYUFBYSxDQUFDO0lBQ3ZCLENBQUM7Q0FDRiIsInNvdXJjZXNDb250ZW50IjpbIi8qKlxuICogU2tpbGxNYW5hZ2VyIC0gUmVmYWN0b3JlZCB0byBleHRlbmQgQmFzZUVsZW1lbnRNYW5hZ2VyIGZvciBzaGFyZWQgQ1JVRCBsb2dpYy5cbiAqIE1haW50YWlucyBza2lsbC1zcGVjaWZpYyBiZWhhdmlvcnMgc3VjaCBhcyB0cmlnZ2VyIHZhbGlkYXRpb24sIGltcG9ydC9leHBvcnQsXG4gKiBhbmQgY3JlYXRpb24gd29ya2Zsb3dzIHdoaWxlIGVsaW1pbmF0aW5nIGR1cGxpY2F0ZWQgZmlsZSBoYW5kbGluZyBjb2RlLlxuICovXG5cbmltcG9ydCB7IEJhc2VFbGVtZW50TWFuYWdlciB9IGZyb20gJy4uL2Jhc2UvQmFzZUVsZW1lbnRNYW5hZ2VyLmpzJztcbmltcG9ydCB7IFNraWxsLCBTa2lsbE1ldGFkYXRhIH0gZnJvbSAnLi9Ta2lsbC5qcyc7XG5pbXBvcnQgeyBFbGVtZW50VHlwZSB9IGZyb20gJy4uLy4uL3BvcnRmb2xpby90eXBlcy5qcyc7XG5pbXBvcnQgeyB0b1Npbmd1bGFyTGFiZWwgfSBmcm9tICcuLi8uLi91dGlscy9lbGVtZW50VHlwZU5vcm1hbGl6YXRpb24uanMnO1xuaW1wb3J0IHsgU2VjdXJpdHlNb25pdG9yIH0gZnJvbSAnLi4vLi4vc2VjdXJpdHkvc2VjdXJpdHlNb25pdG9yLmpzJztcbmltcG9ydCB7IHNhbml0aXplSW5wdXQgfSBmcm9tICcuLi8uLi9zZWN1cml0eS9JbnB1dFZhbGlkYXRvci5qcyc7XG5pbXBvcnQgeyBGaWxlTG9ja01hbmFnZXIgfSBmcm9tICcuLi8uLi9zZWN1cml0eS9maWxlTG9ja01hbmFnZXIuanMnO1xuaW1wb3J0IHsgbG9nZ2VyIH0gZnJvbSAnLi4vLi4vdXRpbHMvbG9nZ2VyLmpzJztcbmltcG9ydCB7IFBvcnRmb2xpb01hbmFnZXIgfSBmcm9tICcuLi8uLi9wb3J0Zm9saW8vUG9ydGZvbGlvTWFuYWdlci5qcyc7XG5pbXBvcnQgeyBWYWxpZGF0aW9uUmVnaXN0cnkgfSBmcm9tICcuLi8uLi9zZXJ2aWNlcy92YWxpZGF0aW9uL1ZhbGlkYXRpb25SZWdpc3RyeS5qcyc7XG5pbXBvcnQgeyBUcmlnZ2VyVmFsaWRhdGlvblNlcnZpY2UgfSBmcm9tICcuLi8uLi9zZXJ2aWNlcy92YWxpZGF0aW9uL1RyaWdnZXJWYWxpZGF0aW9uU2VydmljZS5qcyc7XG5pbXBvcnQgeyBWYWxpZGF0aW9uU2VydmljZSB9IGZyb20gJy4uLy4uL3NlcnZpY2VzL3ZhbGlkYXRpb24vVmFsaWRhdGlvblNlcnZpY2UuanMnO1xuaW1wb3J0IHsgU2VyaWFsaXphdGlvblNlcnZpY2UgfSBmcm9tICcuLi8uLi9zZXJ2aWNlcy9TZXJpYWxpemF0aW9uU2VydmljZS5qcyc7XG5pbXBvcnQgeyBNZXRhZGF0YVNlcnZpY2UgfSBmcm9tICcuLi8uLi9zZXJ2aWNlcy9NZXRhZGF0YVNlcnZpY2UuanMnO1xuaW1wb3J0IHsgRmlsZU9wZXJhdGlvbnNTZXJ2aWNlIH0gZnJvbSAnLi4vLi4vc2VydmljZXMvRmlsZU9wZXJhdGlvbnNTZXJ2aWNlLmpzJztcbmltcG9ydCB7IEZpbGVXYXRjaFNlcnZpY2UgfSBmcm9tICcuLi8uLi9zZXJ2aWNlcy9GaWxlV2F0Y2hTZXJ2aWNlLmpzJztcbmltcG9ydCB7IEVsZW1lbnRNZXNzYWdlcyB9IGZyb20gJy4uLy4uL3V0aWxzL2VsZW1lbnRNZXNzYWdlcy5qcyc7XG5pbXBvcnQgeyBzYW5pdGl6ZUdhdGVrZWVwZXJQb2xpY3kgfSBmcm9tICcuLi8uLi9oYW5kbGVycy9tY3AtYXFsL3BvbGljaWVzL0VsZW1lbnRQb2xpY2llcy5qcyc7XG5pbXBvcnQgeyBTRUNVUklUWV9MSU1JVFMgfSBmcm9tICcuLi8uLi9zZWN1cml0eS9jb25zdGFudHMuanMnO1xuXG4vLyBWYWxpZGF0aW9uIGNvbnN0YW50cyBmb3Igc2tpbGwgdHJpZ2dlcnNcbmNvbnN0IE1BWF9UUklHR0VSX0xFTkdUSCA9IDUwO1xuLy8gQWxsb3dzIGFscGhhbnVtZXJpYywgaHlwaGVucywgdW5kZXJzY29yZXMsIEAgKG1lbnRpb25zL2VtYWlscyksIC4gKGRvbWFpbnMpXG5jb25zdCBUUklHR0VSX1ZBTElEQVRJT05fUkVHRVggPSAvXlthLXpBLVowLTlcXC1fQC5dKyQvO1xuXG4vLyBJc3N1ZSAjODM6IENlbnRyYWxpemVkIGFjdGl2ZSBlbGVtZW50IGxpbWl0cyAoY29uZmlndXJhYmxlIHZpYSBlbnYgdmFycylcbmltcG9ydCB7IGdldEFjdGl2ZUVsZW1lbnRMaW1pdENvbmZpZywgZ2V0TWF4QWN0aXZlTGltaXQgfSBmcm9tICcuLi8uLi9jb25maWcvYWN0aXZlLWVsZW1lbnQtbGltaXRzLmpzJztcblxuZXhwb3J0IGNsYXNzIFNraWxsTWFuYWdlciBleHRlbmRzIEJhc2VFbGVtZW50TWFuYWdlcjxTa2lsbD4ge1xuICBwcml2YXRlIHRyaWdnZXJWYWxpZGF0aW9uU2VydmljZTogVHJpZ2dlclZhbGlkYXRpb25TZXJ2aWNlO1xuICBwcml2YXRlIHZhbGlkYXRpb25TZXJ2aWNlOiBWYWxpZGF0aW9uU2VydmljZTtcbiAgcHJpdmF0ZSBzZXJpYWxpemF0aW9uU2VydmljZTogU2VyaWFsaXphdGlvblNlcnZpY2U7XG4gIC8vIFRyYWNrIGFjdGl2ZSBza2lsbHMgYnkgbmFtZSAoc3RhYmxlIGlkZW50aWZpZXIpXG4gIHByaXZhdGUgYWN0aXZlU2tpbGxOYW1lczogU2V0PHN0cmluZz4gPSBuZXcgU2V0KCk7XG5cbiAgY29uc3RydWN0b3IoXG4gICAgcG9ydGZvbGlvTWFuYWdlcjogUG9ydGZvbGlvTWFuYWdlcixcbiAgICBmaWxlTG9ja01hbmFnZXI6IEZpbGVMb2NrTWFuYWdlcixcbiAgICBmaWxlT3BlcmF0aW9uc1NlcnZpY2U6IEZpbGVPcGVyYXRpb25zU2VydmljZSxcbiAgICB2YWxpZGF0aW9uUmVnaXN0cnk6IFZhbGlkYXRpb25SZWdpc3RyeSxcbiAgICBzZXJpYWxpemF0aW9uU2VydmljZTogU2VyaWFsaXphdGlvblNlcnZpY2UsXG4gICAgcHJpdmF0ZSBtZXRhZGF0YVNlcnZpY2U6IE1ldGFkYXRhU2VydmljZSxcbiAgICBmaWxlV2F0Y2hTZXJ2aWNlPzogRmlsZVdhdGNoU2VydmljZSxcbiAgICBtZW1vcnlCdWRnZXQ/OiBpbXBvcnQoJy4uLy4uL2NhY2hlL0NhY2hlTWVtb3J5QnVkZ2V0LmpzJykuQ2FjaGVNZW1vcnlCdWRnZXQsXG4gICAgYmFja3VwU2VydmljZT86IGltcG9ydCgnLi4vLi4vc2VydmljZXMvQmFja3VwU2VydmljZS5qcycpLkJhY2t1cFNlcnZpY2VcbiAgKSB7XG4gICAgc3VwZXIoRWxlbWVudFR5cGUuU0tJTEwsIHBvcnRmb2xpb01hbmFnZXIsIGZpbGVMb2NrTWFuYWdlciwgeyBmaWxlV2F0Y2hTZXJ2aWNlLCBtZW1vcnlCdWRnZXQsIGJhY2t1cFNlcnZpY2UgfSwgZmlsZU9wZXJhdGlvbnNTZXJ2aWNlLCB2YWxpZGF0aW9uUmVnaXN0cnkpO1xuICAgIHRoaXMudHJpZ2dlclZhbGlkYXRpb25TZXJ2aWNlID0gdmFsaWRhdGlvblJlZ2lzdHJ5LmdldFRyaWdnZXJWYWxpZGF0aW9uU2VydmljZSgpO1xuICAgIHRoaXMudmFsaWRhdGlvblNlcnZpY2UgPSB2YWxpZGF0aW9uUmVnaXN0cnkuZ2V0VmFsaWRhdGlvblNlcnZpY2UoKTtcbiAgICB0aGlzLnNlcmlhbGl6YXRpb25TZXJ2aWNlID0gc2VyaWFsaXphdGlvblNlcnZpY2U7XG4gIH1cblxuICBwcm90ZWN0ZWQgb3ZlcnJpZGUgZ2V0RWxlbWVudExhYmVsKCk6IHN0cmluZyB7XG4gICAgcmV0dXJuICdza2lsbCc7XG4gIH1cblxuICAvKipcbiAgICogQ3JlYXRlIGEgbmV3IHNraWxsIGVsZW1lbnQgYW5kIHBlcnNpc3QgaXQgdG8gZGlzay5cbiAgICogRklYOiBJc3N1ZSAjMjAgLSBBZGQgZHVwbGljYXRlIG5hbWUgY2hlY2tpbmdcbiAgICovXG4gIGFzeW5jIGNyZWF0ZShkYXRhOiBQYXJ0aWFsPFNraWxsTWV0YWRhdGE+ICYgeyBjb250ZW50Pzogc3RyaW5nOyBpbnN0cnVjdGlvbnM/OiBzdHJpbmcgfSk6IFByb21pc2U8U2tpbGw+IHtcbiAgICAvLyBVc2Ugc3BlY2lhbGl6ZWQgdmFsaWRhdG9yIGZvciBpbnB1dCB2YWxpZGF0aW9uXG4gICAgY29uc3QgdmFsaWRhdGlvblJlc3VsdCA9IGF3YWl0IHRoaXMudmFsaWRhdG9yLnZhbGlkYXRlQ3JlYXRlKGRhdGEpO1xuXG4gICAgaWYgKCF2YWxpZGF0aW9uUmVzdWx0LmlzVmFsaWQpIHtcbiAgICAgIHRocm93IG5ldyBFcnJvcihgVmFsaWRhdGlvbiBmYWlsZWQ6ICR7dmFsaWRhdGlvblJlc3VsdC5lcnJvcnMuam9pbignLCAnKX1gKTtcbiAgICB9XG5cbiAgICAvLyBMb2cgd2FybmluZ3MgaWYgYW55XG4gICAgaWYgKHZhbGlkYXRpb25SZXN1bHQud2FybmluZ3MgJiYgdmFsaWRhdGlvblJlc3VsdC53YXJuaW5ncy5sZW5ndGggPiAwKSB7XG4gICAgICBsb2dnZXIud2FybihgU2tpbGwgY3JlYXRpb24gd2FybmluZ3M6ICR7dmFsaWRhdGlvblJlc3VsdC53YXJuaW5ncy5qb2luKCcsICcpfWApO1xuICAgIH1cblxuICAgIC8vIEdldCBzYW5pdGl6ZWQgdmFsdWVzIGZvciBmaWxlIG9wZXJhdGlvbnMgKHZhbGlkYXRvciBhbHJlYWR5IHZhbGlkYXRlZCwgd2UganVzdCBuZWVkIHNhbml0aXplZCB2YWx1ZXMpXG4gICAgY29uc3QgbmFtZUlucHV0ID0gZGF0YS5uYW1lIHx8ICduZXctc2tpbGwnO1xuICAgIGNvbnN0IG5hbWVSZXN1bHQgPSB0aGlzLnZhbGlkYXRpb25TZXJ2aWNlLnZhbGlkYXRlQW5kU2FuaXRpemVJbnB1dChuYW1lSW5wdXQsIHtcbiAgICAgIG1heExlbmd0aDogU0VDVVJJVFlfTElNSVRTLk1BWF9OQU1FX0xFTkdUSCxcbiAgICAgIGFsbG93U3BhY2VzOiB0cnVlXG4gICAgfSk7XG4gICAgY29uc3Qgc2FuaXRpemVkTmFtZSA9IG5hbWVSZXN1bHQuc2FuaXRpemVkVmFsdWUhO1xuXG4gICAgLy8gVXNlIGluaGVyaXRlZCBnZXRFbGVtZW50RmlsZW5hbWUoKSBmb3IgY29uc2lzdGVudCBmaWxlbmFtZSBub3JtYWxpemF0aW9uXG4gICAgY29uc3QgZmlsZW5hbWUgPSB0aGlzLmdldEVsZW1lbnRGaWxlbmFtZShzYW5pdGl6ZWROYW1lKTtcblxuICAgIC8vIEZJWDogSXNzdWUgIzIwIC0gQ2hlY2sgZm9yIGR1cGxpY2F0ZSBiZWZvcmUgY3JlYXRpbmdcbiAgICBjb25zdCBleGlzdGluZ1NraWxscyA9IGF3YWl0IHRoaXMubGlzdCgpO1xuICAgIGNvbnN0IGR1cGxpY2F0ZSA9IGV4aXN0aW5nU2tpbGxzLmZpbmQocyA9PlxuICAgICAgcy5tZXRhZGF0YS5uYW1lLnRvTG93ZXJDYXNlKCkgPT09IHNhbml0aXplZE5hbWUudG9Mb3dlckNhc2UoKVxuICAgICk7XG5cbiAgICBpZiAoZHVwbGljYXRlKSB7XG4gICAgICB0aHJvdyBuZXcgRXJyb3IoYEEgc2tpbGwgbmFtZWQgXCIke3Nhbml0aXplZE5hbWV9XCIgYWxyZWFkeSBleGlzdHNgKTtcbiAgICB9XG5cbiAgICBjb25zdCB7IGNvbnRlbnQsIGluc3RydWN0aW9ucywgLi4ubWV0YWRhdGEgfSA9IGRhdGE7XG4gICAgLy8gRHVhbC1maWVsZDogaW5zdHJ1Y3Rpb25zID0gYmVoYXZpb3JhbCBkaXJlY3RpdmVzLCBjb250ZW50ID0gcmVmZXJlbmNlIG1hdGVyaWFsXG4gICAgLy8gRm9yIHNraWxscywgaW5zdHJ1Y3Rpb25zIGlzIHRoZSBwcmltYXJ5IGZpZWxkIChob3cgdG8gYXBwbHkgdGhlIHNraWxsKVxuICAgIGNvbnN0IGVmZmVjdGl2ZUluc3RydWN0aW9ucyA9IGluc3RydWN0aW9ucyB8fCBjb250ZW50IHx8ICcnO1xuICAgIGNvbnN0IGVmZmVjdGl2ZUNvbnRlbnQgPSBpbnN0cnVjdGlvbnMgPyAoY29udGVudCB8fCAnJykgOiAnJztcbiAgICBjb25zdCBza2lsbCA9IG5ldyBTa2lsbChcbiAgICAgIHtcbiAgICAgICAgLi4ubWV0YWRhdGEsXG4gICAgICAgIG5hbWU6IHNhbml0aXplZE5hbWUsXG4gICAgICAgIGRlc2NyaXB0aW9uOiBkYXRhLmRlc2NyaXB0aW9uIHx8ICcnXG4gICAgICB9LFxuICAgICAgZWZmZWN0aXZlSW5zdHJ1Y3Rpb25zLFxuICAgICAgdGhpcy5tZXRhZGF0YVNlcnZpY2UsXG4gICAgICBlZmZlY3RpdmVDb250ZW50XG4gICAgKTtcblxuICAgIGF3YWl0IHRoaXMuc2F2ZShza2lsbCwgZmlsZW5hbWUpO1xuICAgIC8vIE5vdGU6IE5vIHJlbG9hZCgpIGhlcmUg4oCUIHNhdmUoKSBjYWNoZXMgdGhlIGVsZW1lbnQgY29ycmVjdGx5LlxuICAgIC8vIFNlZSBJc3N1ZSAjNDkxIGZvciB3aHkgUGVyc29uYU1hbmFnZXIncyByZWxvYWQtYWZ0ZXItY3JlYXRlIHdhcyByZW1vdmVkLlxuXG4gICAgU2VjdXJpdHlNb25pdG9yLmxvZ1NlY3VyaXR5RXZlbnQoe1xuICAgICAgdHlwZTogJ0VMRU1FTlRfQ1JFQVRFRCcsXG4gICAgICBzZXZlcml0eTogJ0xPVycsXG4gICAgICBzb3VyY2U6ICdTa2lsbE1hbmFnZXIuY3JlYXRlJyxcbiAgICAgIGRldGFpbHM6IGBTa2lsbCBjcmVhdGVkOiAke3NraWxsLm1ldGFkYXRhLm5hbWV9YFxuICAgIH0pO1xuXG4gICAgcmV0dXJuIHNraWxsO1xuICB9XG5cbiAgLyoqXG4gICAqIEltcG9ydCBhIHNraWxsIGZyb20gWUFNTCBvciBKU09OIGlucHV0IGZvcm1hdHMuXG4gICAqL1xuICBhc3luYyBpbXBvcnRFbGVtZW50KGRhdGE6IHN0cmluZywgZm9ybWF0OiAneWFtbCcgfCAnanNvbicgPSAneWFtbCcpOiBQcm9taXNlPFNraWxsPiB7XG4gICAgdHJ5IHtcbiAgICAgIGxldCBtZXRhZGF0YTogYW55O1xuICAgICAgbGV0IGluc3RydWN0aW9uczogc3RyaW5nO1xuXG4gICAgICBpZiAoZm9ybWF0ID09PSAneWFtbCcpIHtcbiAgICAgICAgLy8gVXNlIFNlcmlhbGl6YXRpb25TZXJ2aWNlIGZvciBZQU1MIHBhcnNpbmdcbiAgICAgICAgY29uc3QgcmVzdWx0ID0gdGhpcy5zZXJpYWxpemF0aW9uU2VydmljZS5wYXJzZUZyb250bWF0dGVyKGRhdGEsIHtcbiAgICAgICAgICBtYXhZYW1sU2l6ZTogNjQgKiAxMDI0LFxuICAgICAgICAgIHZhbGlkYXRlQ29udGVudDogdHJ1ZSxcbiAgICAgICAgICBzb3VyY2U6ICdTa2lsbE1hbmFnZXIuaW1wb3J0RWxlbWVudCdcbiAgICAgICAgfSk7XG5cbiAgICAgICAgLy8gRXh0cmFjdCBtZXRhZGF0YSBhbmQgaW5zdHJ1Y3Rpb25zXG4gICAgICAgIGlmIChyZXN1bHQuZGF0YS5tZXRhZGF0YSkge1xuICAgICAgICAgIG1ldGFkYXRhID0gcmVzdWx0LmRhdGEubWV0YWRhdGE7XG4gICAgICAgICAgaW5zdHJ1Y3Rpb25zID0gcmVzdWx0LmRhdGEuaW5zdHJ1Y3Rpb25zIHx8IHJlc3VsdC5jb250ZW50IHx8ICcnO1xuICAgICAgICB9IGVsc2Uge1xuICAgICAgICAgIG1ldGFkYXRhID0gcmVzdWx0LmRhdGE7XG4gICAgICAgICAgaW5zdHJ1Y3Rpb25zID0gcmVzdWx0LmRhdGEuaW5zdHJ1Y3Rpb25zIHx8IHJlc3VsdC5jb250ZW50IHx8ICcnO1xuICAgICAgICAgIGRlbGV0ZSBtZXRhZGF0YS5pbnN0cnVjdGlvbnM7XG4gICAgICAgIH1cbiAgICAgIH0gZWxzZSB7XG4gICAgICAgIC8vIFVzZSBTZXJpYWxpemF0aW9uU2VydmljZSBmb3IgSlNPTiBwYXJzaW5nXG4gICAgICAgIGNvbnN0IHBhcnNlZCA9IHRoaXMuc2VyaWFsaXphdGlvblNlcnZpY2UucGFyc2VKc29uKGRhdGEsIHtcbiAgICAgICAgICBzb3VyY2U6ICdTa2lsbE1hbmFnZXIuaW1wb3J0RWxlbWVudCdcbiAgICAgICAgfSk7XG5cbiAgICAgICAgaWYgKHBhcnNlZC5tZXRhZGF0YSkge1xuICAgICAgICAgIG1ldGFkYXRhID0gcGFyc2VkLm1ldGFkYXRhO1xuICAgICAgICAgIGluc3RydWN0aW9ucyA9IHBhcnNlZC5pbnN0cnVjdGlvbnMgfHwgJyc7XG4gICAgICAgIH0gZWxzZSB7XG4gICAgICAgICAgbWV0YWRhdGEgPSBwYXJzZWQ7XG4gICAgICAgICAgaW5zdHJ1Y3Rpb25zID0gcGFyc2VkLmluc3RydWN0aW9ucyB8fCAnJztcbiAgICAgICAgICBkZWxldGUgbWV0YWRhdGEuaW5zdHJ1Y3Rpb25zO1xuICAgICAgICB9XG4gICAgICB9XG5cbiAgICAgIHJldHVybiBuZXcgU2tpbGwobWV0YWRhdGEsIGluc3RydWN0aW9ucywgdGhpcy5tZXRhZGF0YVNlcnZpY2UpO1xuICAgIH0gY2F0Y2ggKGVycm9yKSB7XG4gICAgICBsb2dnZXIuZXJyb3IoJ0ZhaWxlZCB0byBpbXBvcnQgc2tpbGw6JywgZXJyb3IpO1xuICAgICAgdGhyb3cgZXJyb3I7XG4gICAgfVxuICB9XG5cbiAgLyoqXG4gICAqIEV4cG9ydCBhIHNraWxsIHRvIFlBTUwgb3IgSlNPTi5cbiAgICovXG4gIGFzeW5jIGV4cG9ydEVsZW1lbnQoZWxlbWVudDogU2tpbGwsIGZvcm1hdDogJ3lhbWwnIHwgJ2pzb24nID0gJ3lhbWwnKTogUHJvbWlzZTxzdHJpbmc+IHtcbiAgICBpZiAoZm9ybWF0ID09PSAneWFtbCcpIHtcbiAgICAgIGNvbnN0IGRhdGEgPSB7XG4gICAgICAgIG1ldGFkYXRhOiBlbGVtZW50Lm1ldGFkYXRhLFxuICAgICAgICBpbnN0cnVjdGlvbnM6IGVsZW1lbnQuaW5zdHJ1Y3Rpb25zLFxuICAgICAgICBwYXJhbWV0ZXJzOiBPYmplY3QuZnJvbUVudHJpZXMoZWxlbWVudC5wYXJhbWV0ZXJzKVxuICAgICAgfTtcblxuICAgICAgcmV0dXJuIHRoaXMuc2VyaWFsaXphdGlvblNlcnZpY2UuZHVtcFlhbWwoZGF0YSwge1xuICAgICAgICBzY2hlbWE6ICdqc29uJywgIC8vIEZpeCAjOTE0OiBmYWlsc2FmZSBjb3JydXB0cyBib29sZWFucy9udW1iZXJzIHRvIHN0cmluZ3NcbiAgICAgICAgbm9SZWZzOiB0cnVlLFxuICAgICAgICBza2lwSW52YWxpZDogdHJ1ZVxuICAgICAgfSk7XG4gICAgfVxuXG4gICAgLy8gRm9yIEpTT04sIHNwcmVhZCBtZXRhZGF0YSBwcm9wZXJ0aWVzIGRpcmVjdGx5IChtYWludGFpbiBiYWNrd2FyZCBjb21wYXRpYmlsaXR5KVxuICAgIGNvbnN0IGRhdGEgPSB7XG4gICAgICAuLi5lbGVtZW50Lm1ldGFkYXRhLFxuICAgICAgaW5zdHJ1Y3Rpb25zOiBlbGVtZW50Lmluc3RydWN0aW9ucyxcbiAgICAgIHBhcmFtZXRlcnM6IE9iamVjdC5mcm9tRW50cmllcyhlbGVtZW50LnBhcmFtZXRlcnMpXG4gICAgfTtcblxuICAgIHJldHVybiB0aGlzLnNlcmlhbGl6YXRpb25TZXJ2aWNlLnN0cmluZ2lmeUpzb24oZGF0YSwge1xuICAgICAgcHJldHR5OiB0cnVlLFxuICAgICAgaW5kZW50OiAyXG4gICAgfSk7XG4gIH1cblxuICBnZXRGaWxlRXh0ZW5zaW9uKCk6IHN0cmluZyB7XG4gICAgcmV0dXJuICcubWQnO1xuICB9XG5cbiAgLyoqXG4gICAqIFZhbGlkYXRlIGFuZCBub3JtYWxpemUgbWV0YWRhdGEgcGFyc2VkIGZyb20gZnJvbnRtYXR0ZXIuXG4gICAqL1xuICBwcm90ZWN0ZWQgYXN5bmMgcGFyc2VNZXRhZGF0YShkYXRhOiBhbnkpOiBQcm9taXNlPFNraWxsTWV0YWRhdGE+IHtcbiAgICBjb25zdCBtZXRhZGF0YSA9IHsgLi4uKGRhdGEgYXMgU2tpbGxNZXRhZGF0YSkgfTtcblxuICAgIGlmIChBcnJheS5pc0FycmF5KGRhdGE/LnRyaWdnZXJzKSkge1xuICAgICAgY29uc3QgdmFsaWRhdGlvblJlc3VsdCA9IHRoaXMudHJpZ2dlclZhbGlkYXRpb25TZXJ2aWNlLnZhbGlkYXRlVHJpZ2dlcnMoXG4gICAgICAgIGRhdGEudHJpZ2dlcnMsXG4gICAgICAgIEVsZW1lbnRUeXBlLlNLSUxMLFxuICAgICAgICBtZXRhZGF0YS5uYW1lIHx8ICd1bmtub3duJ1xuICAgICAgKTtcbiAgICAgIG1ldGFkYXRhLnRyaWdnZXJzID0gdmFsaWRhdGlvblJlc3VsdC52YWxpZFRyaWdnZXJzO1xuICAgIH1cblxuICAgIC8vIElzc3VlICM2NzY6IFNhbml0aXplIGdhdGVrZWVwZXIgcG9saWN5IG9uIGxvYWQgdG8gcHJldmVudCBwcm9tcHQtaW5qZWN0aW9uIGF0dGFja3NcbiAgICAvLyBNYWxmb3JtZWQgcG9saWNpZXMgYXJlIHN0cmlwcGVkIGFuZCBsb2dnZWQgYXMgc2VjdXJpdHkgZXZlbnRzIChuZXZlciByZWFjaCBlbmZvcmNlbWVudClcbiAgICBpZiAobWV0YWRhdGEuZ2F0ZWtlZXBlcikge1xuICAgICAgbWV0YWRhdGEuZ2F0ZWtlZXBlciA9IHNhbml0aX