@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.
265 lines • 35.2 kB
JavaScript
/**
* Resilience Evaluator for Agent Execution
*
* Pure functions that evaluate resilience policies to determine recovery actions
* when agents hit step limits or encounter execution failures.
*
* Design: All functions are pure (no side effects, no I/O). The MCPAQLHandler
* is responsible for acting on the returned ResilienceAction.
*
* The CircuitBreakerState class is the one exception to purity — it holds
* singleton state that tracks agents whose resilience limits were recently
* exhausted. When an agent trips the breaker and is re-executed within the
* cooldown window, the evaluator forces an immediate pause instead of allowing
* further auto-continuation or retry loops.
*
* @since v2.1.0 - Agent Execution Resilience (Issue #526)
*/
// =============================================================================
// Constants
// =============================================================================
/** Default resilience policy — all 'pause', matching pre-#526 behavior */
export const DEFAULT_RESILIENCE_POLICY = {
onStepLimitReached: 'pause',
onExecutionFailure: 'pause',
maxRetries: 3,
maxContinuations: 10,
retryBackoff: 'exponential',
preserveState: true,
};
/** Base delay for backoff calculations (milliseconds) */
export const BACKOFF_BASE_MS = 1000;
/** Maximum backoff delay cap (milliseconds) — 30 seconds */
export const BACKOFF_MAX_MS = 30_000;
/** Exponential base used in backoff calculations */
export const EXPONENTIAL_BASE = 2;
/** Cooldown window for the circuit breaker (milliseconds) — 5 minutes */
export const CIRCUIT_BREAKER_COOLDOWN_MS = 5 * 60 * 1000;
// =============================================================================
// Circuit Breaker
// =============================================================================
/**
* Tracks agents that have recently exhausted their resilience limits.
*
* When an agent hits maxContinuations or maxRetries and is forced to pause,
* the circuit breaker records a "trip". If the same agent is re-executed and
* triggers resilience evaluation again within the cooldown window, the breaker
* fires and forces an immediate pause — preventing tight re-execution loops
* of broken agents.
*/
export class CircuitBreakerState {
trips = new Map();
/**
* Record that an agent exhausted its resilience limits.
* Increments the trip count and updates the timestamp.
*/
trip(agentName) {
const existing = this.trips.get(agentName);
this.trips.set(agentName, {
tripCount: (existing?.tripCount ?? 0) + 1,
lastTrippedAt: Date.now(),
});
}
/**
* Check whether an agent's circuit breaker is currently tripped.
*
* Returns true if the agent was tripped within the given cooldown window,
* meaning it should be immediately paused rather than allowed to retry/continue.
*/
isTripped(agentName, cooldownMs) {
const entry = this.trips.get(agentName);
if (!entry)
return false;
return (Date.now() - entry.lastTrippedAt) < cooldownMs;
}
/**
* Clear circuit breaker state for a specific agent.
* Call this when an agent completes successfully to reset its breaker.
*/
reset(agentName) {
this.trips.delete(agentName);
}
/**
* Clear all circuit breaker state. Primarily for testing.
*/
resetAll() {
this.trips.clear();
}
/**
* Get the trip record for an agent (read-only, for diagnostics).
* Returns undefined if the agent has no trip history.
*/
getState(agentName) {
const entry = this.trips.get(agentName);
return entry ? { ...entry } : undefined;
}
}
/** Singleton circuit breaker instance shared across all resilience evaluations */
export const circuitBreaker = new CircuitBreakerState();
// =============================================================================
// Evaluation Functions
// =============================================================================
/**
* Evaluate a resilience policy given the trigger context.
*
* Returns a ResilienceAction describing what recovery action to take.
* The action 'pause' means "do nothing special" (legacy behavior).
*/
export function evaluateResiliencePolicy(policy, context) {
const resolved = resolvePolicy(policy);
if (context.trigger === 'step_limit') {
return evaluateStepLimitResilience(resolved, context);
}
return evaluateFailureResilience(resolved, context);
}
/**
* Merge a partial policy with defaults to get a fully resolved policy.
*/
export function resolvePolicy(policy) {
if (!policy)
return { ...DEFAULT_RESILIENCE_POLICY };
return { ...DEFAULT_RESILIENCE_POLICY, ...policy };
}
/**
* Calculate backoff delay in milliseconds for a given retry count and strategy.
*
* @param strategy - The backoff strategy: 'none' (0ms), 'linear', or 'exponential'
* @param retryCount - The current retry attempt number (0-based)
* @param jitter - When true, multiplies the delay by a random factor between 0.5
* and 1.5 to decorrelate concurrent retries. The jittered result is still
* capped at BACKOFF_MAX_MS. Default: false.
* @returns Backoff delay in milliseconds
*/
export function calculateBackoff(strategy, retryCount, jitter = false) {
let delay;
switch (strategy) {
case 'none':
delay = 0;
break;
case 'linear':
delay = Math.min(BACKOFF_BASE_MS * (retryCount + 1), BACKOFF_MAX_MS);
break;
case 'exponential':
delay = Math.min(BACKOFF_BASE_MS * Math.pow(EXPONENTIAL_BASE, retryCount), BACKOFF_MAX_MS);
break;
}
if (jitter && delay > 0) {
const jitterFactor = 0.5 + Math.random(); // range [0.5, 1.5)
delay = Math.min(Math.floor(delay * jitterFactor), BACKOFF_MAX_MS);
}
return delay;
}
// =============================================================================
// Internal Evaluation Logic
// =============================================================================
function evaluateStepLimitResilience(policy, context) {
// Circuit breaker check — immediately pause if recently tripped
if (context.agentName && circuitBreaker.isTripped(context.agentName, CIRCUIT_BREAKER_COOLDOWN_MS)) {
return {
action: 'pause',
reason: 'Circuit breaker: agent recently exhausted resilience limits — forcing immediate pause to prevent re-execution loop',
continuationCount: context.continuationCount,
maxContinuations: policy.maxContinuations,
};
}
const { onStepLimitReached, maxContinuations } = policy;
// Default behavior — pause
if (onStepLimitReached === 'pause') {
return {
action: 'pause',
reason: 'Resilience policy: pause on step limit (default behavior)',
continuationCount: context.continuationCount,
maxContinuations,
};
}
// Check continuation cap (0 = unlimited)
if (maxContinuations > 0 && context.continuationCount >= maxContinuations) {
// Trip the circuit breaker so re-execution within cooldown is blocked
if (context.agentName) {
circuitBreaker.trip(context.agentName);
}
return {
action: 'pause',
reason: `Resilience limit reached: ${context.continuationCount}/${maxContinuations} continuations exhausted`,
continuationCount: context.continuationCount,
maxContinuations,
};
}
// Auto-continue or restart
if (onStepLimitReached === 'continue') {
return {
action: 'continue',
reason: `Auto-continuing: step limit reached, continuation ${context.continuationCount + 1}${maxContinuations > 0 ? `/${maxContinuations}` : ''}`,
continuationCount: context.continuationCount + 1,
maxContinuations,
};
}
// restart
return {
action: 'restart',
reason: `Auto-restarting: step limit reached, restart ${context.continuationCount + 1}${maxContinuations > 0 ? `/${maxContinuations}` : ''}`,
continuationCount: context.continuationCount + 1,
maxContinuations,
};
}
function evaluateFailureResilience(policy, context) {
// Circuit breaker check — immediately pause if recently tripped
if (context.agentName && circuitBreaker.isTripped(context.agentName, CIRCUIT_BREAKER_COOLDOWN_MS)) {
return {
action: 'pause',
reason: 'Circuit breaker: agent recently exhausted resilience limits — forcing immediate pause to prevent re-execution loop',
retryCount: context.retryCount,
};
}
const { onExecutionFailure, maxRetries, retryBackoff } = policy;
// Default behavior — pause
if (onExecutionFailure === 'pause') {
return {
action: 'pause',
reason: 'Resilience policy: pause on execution failure (default behavior)',
retryCount: context.retryCount,
};
}
// retry
if (onExecutionFailure === 'retry') {
if (context.retryCount >= maxRetries) {
// Trip the circuit breaker so re-execution within cooldown is blocked
if (context.agentName) {
circuitBreaker.trip(context.agentName);
}
return {
action: 'pause',
reason: `Retry limit reached: ${context.retryCount}/${maxRetries} retries exhausted`,
retryCount: context.retryCount,
};
}
const backoffMs = calculateBackoff(retryBackoff, context.retryCount);
return {
action: 'retry',
reason: `Retrying failed step: attempt ${context.retryCount + 1}/${maxRetries}${backoffMs > 0 ? ` (backoff: ${backoffMs}ms)` : ''}`,
backoffMs,
retryCount: context.retryCount + 1,
};
}
// restart-fresh — check continuation cap
const { maxContinuations } = policy;
if (maxContinuations > 0 && context.continuationCount >= maxContinuations) {
// Trip the circuit breaker so re-execution within cooldown is blocked
if (context.agentName) {
circuitBreaker.trip(context.agentName);
}
return {
action: 'pause',
reason: `Resilience limit reached: ${context.continuationCount}/${maxContinuations} continuations exhausted`,
continuationCount: context.continuationCount,
maxContinuations,
};
}
return {
action: 'restart',
reason: `Restarting fresh after failure: restart ${context.continuationCount + 1}${maxContinuations > 0 ? `/${maxContinuations}` : ''}`,
continuationCount: context.continuationCount + 1,
maxContinuations,
};
}
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicmVzaWxpZW5jZUV2YWx1YXRvci5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uLy4uL3NyYy9lbGVtZW50cy9hZ2VudHMvcmVzaWxpZW5jZUV2YWx1YXRvci50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQTs7Ozs7Ozs7Ozs7Ozs7OztHQWdCRztBQUlILGdGQUFnRjtBQUNoRixZQUFZO0FBQ1osZ0ZBQWdGO0FBRWhGLDBFQUEwRTtBQUMxRSxNQUFNLENBQUMsTUFBTSx5QkFBeUIsR0FBb0M7SUFDeEUsa0JBQWtCLEVBQUUsT0FBTztJQUMzQixrQkFBa0IsRUFBRSxPQUFPO0lBQzNCLFVBQVUsRUFBRSxDQUFDO0lBQ2IsZ0JBQWdCLEVBQUUsRUFBRTtJQUNwQixZQUFZLEVBQUUsYUFBYTtJQUMzQixhQUFhLEVBQUUsSUFBSTtDQUNwQixDQUFDO0FBRUYseURBQXlEO0FBQ3pELE1BQU0sQ0FBQyxNQUFNLGVBQWUsR0FBRyxJQUFJLENBQUM7QUFFcEMsNERBQTREO0FBQzVELE1BQU0sQ0FBQyxNQUFNLGNBQWMsR0FBRyxNQUFNLENBQUM7QUFFckMsb0RBQW9EO0FBQ3BELE1BQU0sQ0FBQyxNQUFNLGdCQUFnQixHQUFHLENBQUMsQ0FBQztBQUVsQyx5RUFBeUU7QUFDekUsTUFBTSxDQUFDLE1BQU0sMkJBQTJCLEdBQUcsQ0FBQyxHQUFHLEVBQUUsR0FBRyxJQUFJLENBQUM7QUFFekQsZ0ZBQWdGO0FBQ2hGLGtCQUFrQjtBQUNsQixnRkFBZ0Y7QUFFaEY7Ozs7Ozs7O0dBUUc7QUFDSCxNQUFNLE9BQU8sbUJBQW1CO0lBQ3RCLEtBQUssR0FBRyxJQUFJLEdBQUcsRUFBd0QsQ0FBQztJQUVoRjs7O09BR0c7SUFDSCxJQUFJLENBQUMsU0FBaUI7UUFDcEIsTUFBTSxRQUFRLEdBQUcsSUFBSSxDQUFDLEtBQUssQ0FBQyxHQUFHLENBQUMsU0FBUyxDQUFDLENBQUM7UUFDM0MsSUFBSSxDQUFDLEtBQUssQ0FBQyxHQUFHLENBQUMsU0FBUyxFQUFF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QUM7SUFDSixDQUFDO0lBRUQseUNBQXlDO0lBQ3pDLE1BQU0sRUFBRSxnQkFBZ0IsRUFBRSxHQUFHLE1BQU0sQ0FBQztJQUNwQyxJQUFJLGdCQUFnQixHQUFHLENBQUMsSUFBSSxPQUFPLENBQUMsaUJBQWlCLElBQUksZ0JBQWdCLEVBQUUsQ0FBQztRQUMxRSxzRUFBc0U7UUFDdEUsSUFBSSxPQUFPLENBQUMsU0FBUyxFQUFFLENBQUM7WUFDdEIsY0FBYyxDQUFDLElBQUksQ0FBQyxPQUFPLENBQUMsU0FBUyxDQUFDLENBQUM7UUFDekMsQ0FBQztRQUNELE9BQU87WUFDTCxNQUFNLEVBQUUsT0FBTztZQUNmLE1BQU0sRUFBRSw2QkFBNkIsT0FBTyxDQUFDLGlCQUFpQixJQUFJLGdCQUFnQiwwQkFBMEI7WUFDNUcsaUJBQWlCLEVBQUUsT0FBTyxDQUFDLGlCQUFpQjtZQUM1QyxnQkFBZ0I7U0FDakIsQ0FBQztJQUNKLENBQUM7SUFFRCxPQUFPO1FBQ0wsTUFBTSxFQUFFLFNBQVM7UUFDakIsTUFBTSxFQUFFLDJDQUEyQyxPQUFPLENBQUMsaUJBQWlCLEdBQUcsQ0FBQyxHQUFHLGdCQUFnQixHQUFHLENBQUMsQ0FBQyxDQUFDLENBQUMsSUFBSSxnQkFBZ0IsRUFBRSxDQUFDLENBQUMsQ0FBQyxFQUFFLEVBQUU7UUFDdkksaUJBQWlCLEVBQUUsT0FBTyxDQUFDLGlCQUFpQixHQUFHLENBQUM7UUFDaEQsZ0JBQWdCO0tBQ2pCLENBQUM7QUFDSixDQUFDIiwic291cmNlc0NvbnRlbnQiOlsiLyoqXG4gKiBSZXNpbGllbmNlIEV2YWx1YXRvciBmb3IgQWdlbnQgRXhlY3V0aW9uXG4gKlxuICogUHVyZSBmdW5jdGlvbnMgdGhhdCBldmFsdWF0ZSByZXNpbGllbmNlIHBvbGljaWVzIHRvIGRldGVybWluZSByZWNvdmVyeSBhY3Rpb25zXG4gKiB3aGVuIGFnZW50cyBoaXQgc3RlcCBsaW1pdHMgb3IgZW5jb3VudGVyIGV4ZWN1dGlvbiBmYWlsdXJlcy5cbiAqXG4gKiBEZXNpZ246IEFsbCBmdW5jdGlvbnMgYXJlIHB1cmUgKG5vIHNpZGUgZWZmZWN0cywgbm8gSS9PKS4gVGhlIE1DUEFRTEhhbmRsZXJcbiAqIGlzIHJlc3BvbnNpYmxlIGZvciBhY3Rpbmcgb24gdGhlIHJldHVybmVkIFJlc2lsaWVuY2VBY3Rpb24uXG4gKlxuICogVGhlIENpcmN1aXRCcmVha2VyU3RhdGUgY2xhc3MgaXMgdGhlIG9uZSBleGNlcHRpb24gdG8gcHVyaXR5IOKAlCBpdCBob2xkc1xuICogc2luZ2xldG9uIHN0YXRlIHRoYXQgdHJhY2tzIGFnZW50cyB3aG9zZSByZXNpbGllbmNlIGxpbWl0cyB3ZXJlIHJlY2VudGx5XG4gKiBleGhhdXN0ZWQuICBXaGVuIGFuIGFnZW50IHRyaXBzIHRoZSBicmVha2VyIGFuZCBpcyByZS1leGVjdXRlZCB3aXRoaW4gdGhlXG4gKiBjb29sZG93biB3aW5kb3csIHRoZSBldmFsdWF0b3IgZm9yY2VzIGFuIGltbWVkaWF0ZSBwYXVzZSBpbnN0ZWFkIG9mIGFsbG93aW5nXG4gKiBmdXJ0aGVyIGF1dG8tY29udGludWF0aW9uIG9yIHJldHJ5IGxvb3BzLlxuICpcbiAqIEBzaW5jZSB2Mi4xLjAgLSBBZ2VudCBFeGVjdXRpb24gUmVzaWxpZW5jZSAoSXNzdWUgIzUyNilcbiAqL1xuXG5pbXBvcnQgdHlwZSB7IEFnZW50UmVzaWxpZW5jZVBvbGljeSwgUmVzaWxpZW5jZUFjdGlvbiB9IGZyb20gJy4vdHlwZXMuanMnO1xuXG4vLyA9PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PVxuLy8gQ29uc3RhbnRzXG4vLyA9PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PVxuXG4vKiogRGVmYXVsdCByZXNpbGllbmNlIHBvbGljeSDigJQgYWxsICdwYXVzZScsIG1hdGNoaW5nIHByZS0jNTI2IGJlaGF2aW9yICovXG5leHBvcnQgY29uc3QgREVGQVVMVF9SRVNJTElFTkNFX1BPTElDWTogUmVxdWlyZWQ8QWdlbnRSZXNpbGllbmNlUG9saWN5PiA9IHtcbiAgb25TdGVwTGltaXRSZWFjaGVkOiAncGF1c2UnLFxuICBvbkV4ZWN1dGlvbkZhaWx1cmU6ICdwYXVzZScsXG4gIG1heFJldHJpZXM6IDMsXG4gIG1heENvbnRpbnVhdGlvbnM6IDEwLFxuICByZXRyeUJhY2tvZmY6ICdleHBvbmVudGlhbCcsXG4gIHByZXNlcnZlU3RhdGU6IHRydWUsXG59O1xuXG4vKiogQmFzZSBkZWxheSBmb3IgYmFja29mZiBjYWxjdWxhdGlvbnMgKG1pbGxpc2Vjb25kcykgKi9cbmV4cG9ydCBjb25zdCBCQUNLT0ZGX0JBU0VfTVMgPSAxMDAwO1xuXG4vKiogTWF4aW11bSBiYWNrb2ZmIGRlbGF5IGNhcCAobWlsbGlzZWNvbmRzKSDigJQgMzAgc2Vjb25kcyAqL1xuZXhwb3J0IGNvbnN0IEJBQ0tPRkZfTUFYX01TID0gMzBfMDAwO1xuXG4vKiogRXhwb25lbnRpYWwgYmFzZSB1c2VkIGluIGJhY2tvZmYgY2FsY3VsYXRpb25zICovXG5leHBvcnQgY29uc3QgRVhQT05FTlRJQUxfQkFTRSA9IDI7XG5cbi8qKiBDb29sZG93biB3aW5kb3cgZm9yIHRoZSBjaXJjdWl0IGJyZWFrZXIgKG1pbGxpc2Vjb25kcykg4oCUIDUgbWludXRlcyAqL1xuZXhwb3J0IGNvbnN0IENJUkNVSVRfQlJFQUtFUl9DT09MRE9XTl9NUyA9IDUgKiA2MCAqIDEwMDA7XG5cbi8vID09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09XG4vLyBDaXJjdWl0IEJyZWFrZXJcbi8vID09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09XG5cbi8qKlxuICogVHJhY2tzIGFnZW50cyB0aGF0IGhhdmUgcmVjZW50bHkgZXhoYXVzdGVkIHRoZWlyIHJlc2lsaWVuY2UgbGltaXRzLlxuICpcbiAqIFdoZW4gYW4gYWdlbnQgaGl0cyBtYXhDb250aW51YXRpb25zIG9yIG1heFJldHJpZXMgYW5kIGlzIGZvcmNlZCB0byBwYXVzZSxcbiAqIHRoZSBjaXJjdWl0IGJyZWFrZXIgcmVjb3JkcyBhIFwidHJpcFwiLiBJZiB0aGUgc2FtZSBhZ2VudCBpcyByZS1leGVjdXRlZCBhbmRcbiAqIHRyaWdnZXJzIHJlc2lsaWVuY2UgZXZhbHVhdGlvbiBhZ2FpbiB3aXRoaW4gdGhlIGNvb2xkb3duIHdpbmRvdywgdGhlIGJyZWFrZXJcbiAqIGZpcmVzIGFuZCBmb3JjZXMgYW4gaW1tZWRpYXRlIHBhdXNlIOKAlCBwcmV2ZW50aW5nIHRpZ2h0IHJlLWV4ZWN1dGlvbiBsb29wc1xuICogb2YgYnJva2VuIGFnZW50cy5cbiAqL1xuZXhwb3J0IGNsYXNzIENpcmN1aXRCcmVha2VyU3RhdGUge1xuICBwcml2YXRlIHRyaXBzID0gbmV3IE1hcDxzdHJpbmcsIHsgdHJpcENvdW50OiBudW1iZXI7IGxhc3RUcmlwcGVkQXQ6IG51bWJlciB9PigpO1xuXG4gIC8qKlxuICAgKiBSZWNvcmQgdGhhdCBhbiBhZ2VudCBleGhhdXN0ZWQgaXRzIHJlc2lsaWVuY2UgbGltaXRzLlxuICAgKiBJbmNyZW1lbnRzIHRoZSB0cmlwIGNvdW50IGFuZCB1cGRhdGVzIHRoZSB0aW1lc3RhbXAuXG4gICAqL1xuICB0cmlwKGFnZW50TmFtZTogc3RyaW5nKTogdm9pZCB7XG4gICAgY29uc3QgZXhpc3RpbmcgPSB0aGlzLnRyaXBzLmdldChhZ2VudE5hbWUpO1xuICAgIHRoaXMudHJpcHMuc2V0KGFnZW50TmFtZSwge1xuICAgICAgdHJpcENvdW50OiAoZXhpc3Rpbmc/LnRyaXBDb3VudCA/PyAwKSArIDEsXG4gICAgICBsYXN0VHJpcHBlZEF0OiBEYXRlLm5vdygpLFxuICAgIH0pO1xuICB9XG5cbiAgLyoqXG4gICAqIENoZWNrIHdoZXRoZXIgYW4gYWdlbnQncyBjaXJjdWl0IGJyZWFrZXIgaXMgY3VycmVudGx5IHRyaXBwZWQuXG4gICAqXG4gICAqIFJldHVybnMgdHJ1ZSBpZiB0aGUgYWdlbnQgd2FzIHRyaXBwZWQgd2l0aGluIHRoZSBnaXZlbiBjb29sZG93biB3aW5kb3csXG4gICAqIG1lYW5pbmcgaXQgc2hvdWxkIGJlIGltbWVkaWF0ZWx5IHBhdXNlZCByYXRoZXIgdGhhbiBhbGxvd2VkIHRvIHJldHJ5L2NvbnRpbnVlLlxuICAgKi9cbiAgaXNUcmlwcGVkKGFnZW50TmFtZTogc3RyaW5nLCBjb29sZG93bk1zOiBudW1iZXIpOiBib29sZWFuIHtcbiAgICBjb25zdCBlbnRyeSA9IHRoaXMudHJpcHMuZ2V0KGFnZW50TmFtZSk7XG4gICAgaWYgKCFlbnRyeSkgcmV0dXJuIGZhbHNlO1xuICAgIHJldHVybiAoRGF0ZS5ub3coKSAtIGVudHJ5Lmxhc3RUcmlwcGVkQXQpIDwgY29vbGRvd25NcztcbiAgfVxuXG4gIC8qKlxuICAgKiBDbGVhciBjaXJjdWl0IGJyZWFrZXIgc3RhdGUgZm9yIGEgc3BlY2lmaWMgYWdlbnQuXG4gICAqIENhbGwgdGhpcyB3aGVuIGFuIGFnZW50IGNvbXBsZXRlcyBzdWNjZXNzZnVsbHkgdG8gcmVzZXQgaXRzIGJyZWFrZXIuXG4gICAqL1xuICByZXNldChhZ2VudE5hbWU6IHN0cmluZyk6IHZvaWQge1xuICAgIHRoaXMudHJpcHMuZGVsZXRlKGFnZW50TmFtZSk7XG4gIH1cblxuICAvKipcbiAgICogQ2xlYXIgYWxsIGNpcmN1aXQgYnJlYWtlciBzdGF0ZS4gUHJpbWFyaWx5IGZvciB0ZXN0aW5nLlxuICAgKi9cbiAgcmVzZXRBbGwoKTogdm9pZCB7XG4gICAgdGhpcy50cmlwcy5jbGVhcigpO1xuICB9XG5cbiAgLyoqXG4gICAqIEdldCB0aGUgdHJpcCByZWNvcmQgZm9yIGFuIGFnZW50IChyZWFkLW9ubHksIGZvciBkaWFnbm9zdGljcykuXG4gICAqIFJldHVybnMgdW5kZWZpbmVkIGlmIHRoZSBhZ2VudCBoYXMgbm8gdHJpcCBoaXN0b3J5LlxuICAgKi9cbiAgZ2V0U3RhdGUoYWdlbnROYW1lOiBzdHJpbmcpOiB7IHRyaXBDb3VudDogbnVtYmVyOyBsYXN0VHJpcHBlZEF0OiBudW1iZXIgfSB8IHVuZGVmaW5lZCB7XG4gICAgY29uc3QgZW50cnkgPSB0aGlzLnRyaXBzLmdldChhZ2VudE5hbWUpO1xuICAgIHJldHVybiBlbnRyeSA/IHsgLi4uZW50cnkgfSA6IHVuZGVmaW5lZDtcbiAgfVxufVxuXG4vKiogU2luZ2xldG9uIGNpcmN1aXQgYnJlYWtlciBpbnN0YW5jZSBzaGFyZWQgYWNyb3NzIGFsbCByZXNpbGllbmNlIGV2YWx1YXRpb25zICovXG5leHBvcnQgY29uc3QgY2lyY3VpdEJyZWFrZXIgPSBuZXcgQ2lyY3VpdEJyZWFrZXJTdGF0ZSgpO1xuXG4vLyA9PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PVxuLy8gUmVzaWxpZW5jZSBDb250ZXh0XG4vLyA9PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PVxuXG4vKipcbiAqIENvbnRleHQgbmVlZGVkIHRvIGV2YWx1YXRlIGEgcmVzaWxpZW5jZSBkZWNpc2lvbi5cbiAqIEtlcHQgbWluaW1hbCDigJQgb25seSB3aGF0IHRoZSBldmFsdWF0b3IgbmVlZHMuXG4gKi9cbmV4cG9ydCBpbnRlcmZhY2UgUmVzaWxpZW5jZUNvbnRleHQge1xuICAvKiogV2hhdCB0cmlnZ2VyZWQgdGhlIGV2YWx1YXRpb24gKi9cbiAgdHJpZ2dlcjogJ3N0ZXBfbGltaXQnIHwgJ2V4ZWN1dGlvbl9mYWlsdXJlJztcbiAgLyoqIEN1cnJlbnQgY29udGludWF0aW9uIGNvdW50IGZvciB0aGlzIGV4ZWN1dGlvbiBjaGFpbiAqL1xuICBjb250aW51YXRpb25Db3VudDogbnVtYmVyO1xuICAvKiogQ3VycmVudCByZXRyeSBjb3VudCBmb3IgdGhpcyBzcGVjaWZpYyBzdGVwICovXG4gIHJldHJ5Q291bnQ6IG51bWJlcjtcbiAgLyoqIFN0ZXAgb3V0Y29tZSB0aGF0IHRyaWdnZXJlZCB0aGUgZXZhbHVhdGlvbiAoZm9yIGZhaWx1cmUgdHJpZ2dlcnMpICovXG4gIHN0ZXBPdXRjb21lPzogJ3N1Y2Nlc3MnIHwgJ2ZhaWx1cmUnIHwgJ3BhcnRpYWwnO1xuICAvKiogQWdlbnQgbmFtZSBmb3IgY2lyY3VpdCBicmVha2VyIHRyYWNraW5nIChvcHRpb25hbCBmb3IgYmFja3dhcmQgY29tcGF0aWJpbGl0eSkgKi9cbiAgYWdlbnROYW1lPzogc3RyaW5nO1xufVxuXG4vLyA9PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PVxuLy8gRXZhbHVhdGlvbiBGdW5jdGlvbnNcbi8vID09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09XG5cbi8qKlxuICogRXZhbHVhdGUgYSByZXNpbGllbmNlIHBvbGljeSBnaXZlbiB0aGUgdHJpZ2dlciBjb250ZXh0LlxuICpcbiAqIFJldHVybnMgYSBSZXNpbGllbmNlQWN0aW9uIGRlc2NyaWJpbmcgd2hhdCByZWNvdmVyeSBhY3Rpb24gdG8gdGFrZS5cbiAqIFRoZSBhY3Rpb24gJ3BhdXNlJyBtZWFucyBcImRvIG5vdGhpbmcgc3BlY2lhbFwiIChsZWdhY3kgYmVoYXZpb3IpLlxuICovXG5leHBvcnQgZnVuY3Rpb24gZXZhbHVhdGVSZXNpbGllbmNlUG9saWN5KFxuICBwb2xpY3k6IEFnZW50UmVzaWxpZW5jZVBvbGljeSB8IHVuZGVmaW5lZCxcbiAgY29udGV4dDogUmVzaWxpZW5jZUNvbnRleHRcbik6IFJlc2lsaWVuY2VBY3Rpb24ge1xuICBjb25zdCByZXNvbHZlZCA9IHJlc29sdmVQb2xpY3kocG9saWN5KTtcblxuICBpZiAoY29udGV4dC50cmlnZ2VyID09PSAnc3RlcF9saW1pdCcpIHtcbiAgICByZXR1cm4gZXZhbHVhdGVTdGVwTGltaXRSZXNpbGllbmNlKHJlc29sdmVkLCBjb250ZXh0KTtcbiAgfVxuXG4gIHJldHVybiBldmFsdWF0ZUZhaWx1cmVSZXNpbGllbmNlKHJlc29sdmVkLCBjb250ZXh0KTtcbn1cblxuLyoqXG4gKiBNZXJnZSBhIHBhcnRpYWwgcG9saWN5IHdpdGggZGVmYXVsdHMgdG8gZ2V0IGEgZnVsbHkgcmVzb2x2ZWQgcG9saWN5LlxuICovXG5leHBvcnQgZnVuY3Rpb24gcmVzb2x2ZVBvbGljeShcbiAgcG9saWN5OiBBZ2VudFJlc2lsaWVuY2VQb2xpY3kgfCB1bmRlZmluZWRcbik6IFJlcXVpcmVkPEFnZW50UmVzaWxpZW5jZVBvbGljeT4ge1xuICBpZiAoIXBvbGljeSkgcmV0dXJuIHsgLi4uREVGQVVMVF9SRVNJTElFTkNFX1BPTElDWSB9O1xuICByZXR1cm4geyAuLi5ERUZBVUxUX1JFU0lMSUVOQ0VfUE9MSUNZLCAuLi5wb2xpY3kgfTtcbn1cblxuLyoqXG4gKiBDYWxjdWxhdGUgYmFja29mZiBkZWxheSBpbiBtaWxsaXNlY29uZHMgZm9yIGEgZ2l2ZW4gcmV0cnkgY291bnQgYW5kIHN0cmF0ZWd5LlxuICpcbiAqIEBwYXJhbSBzdHJhdGVneSAtIFRoZSBiYWNrb2ZmIHN0cmF0ZWd5OiAnbm9uZScgKDBtcyksICdsaW5lYXInLCBvciAnZXhwb25lbnRpYWwnXG4gKiBAcGFyYW0gcmV0cnlDb3VudCAtIFRoZSBjdXJyZW50IHJldHJ5IGF0dGVtcHQgbnVtYmVyICgwLWJhc2VkKVxuICogQHBhcmFtIGppdHRlciAtIFdoZW4gdHJ1ZSwgbXVsdGlwbGllcyB0aGUgZGVsYXkgYnkgYSByYW5kb20gZmFjdG9yIGJldHdlZW4gMC41XG4gKiAgIGFuZCAxLjUgdG8gZGVjb3JyZWxhdGUgY29uY3VycmVudCByZXRyaWVzLiBUaGUgaml0dGVyZWQgcmVzdWx0IGlzIHN0aWxsXG4gKiAgIGNhcHBlZCBhdCBCQUNLT0ZGX01BWF9NUy4gRGVmYXVsdDogZmFsc2UuXG4gKiBAcmV0dXJucyBCYWNrb2ZmIGRlbGF5IGluIG1pbGxpc2Vjb25kc1xuICovXG5leHBvcnQgZnVuY3Rpb24gY2FsY3VsYXRlQmFja29mZihcbiAgc3RyYXRlZ3k6ICdub25lJyB8ICdsaW5lYXInIHwgJ2V4cG9uZW50aWFsJyxcbiAgcmV0cnlDb3VudDogbnVtYmVyLFxuICBqaXR0ZXI6IGJvb2xlYW4gPSBmYWxzZVxuKTogbnVtYmVyIHtcbiAgbGV0IGRlbGF5OiBudW1iZXI7XG5cbiAgc3dpdGNoIChzdHJhdGVneSkge1xuICAgIGNhc2UgJ25vbmUnOlxuICAgICAgZGVsYXkgPSAwO1xuICAgICAgYnJlYWs7XG4gICAgY2FzZSAnbGluZWFyJzpcbiAgICAgIGRlbGF5ID0gTWF0aC5taW4oQkFDS09GRl9CQVNFX01TICogKHJldHJ5Q291bnQgKyAxKSwgQkFDS09GRl9NQVhfTVMpO1xuICAgICAgYnJlYWs7XG4gICAgY2FzZSAnZXhwb25lbnRpYWwnOlxuICAgICAgZGVsYXkgPSBNYXRoLm1pbihCQUNLT0ZGX0JBU0VfTVMgKiBNYXRoLnBvdyhFWFBPTkVOVElBTF9CQVNFLCByZXRyeUNvdW50KSwgQkFDS09GRl9NQVhfTVMpO1xuICAgICAgYnJlYWs7XG4gIH1cblxuICBpZiAoaml0dGVyICYmIGRlbGF5ID4gMCkge1xuICAgIGNvbnN0IGppdHRlckZhY3RvciA9IDAuNSArIE1hdGgucmFuZG9tKCk7IC8vIHJhbmdlIFswLjUsIDEuNSlcbiAgICBkZWxheSA9IE1hdGgubWluKE1hdGguZmxvb3IoZGVsYXkgKiBqaXR0ZXJGYWN0b3IpLCBCQUNLT0ZGX01BWF9NUyk7XG4gIH1cblxuICByZXR1cm4gZGVsYXk7XG59XG5cbi8vID09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09XG4vLyBJbnRlcm5hbCBFdmFsdWF0aW9uIExvZ2ljXG4vLyA9PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PVxuXG5mdW5jdGlvbiBldmFsdWF0ZVN0ZXBMaW1pdFJlc2lsaWVuY2UoXG4gIHBvbGljeTogUmVxdWlyZWQ8QWdlbnRSZXNpbGllbmNlUG9saWN5PixcbiAgY29udGV4dDogUmVzaWxpZW5jZUNvbnRleHRcbik6IFJlc2lsaWVuY2VBY3Rpb24ge1xuICAvLyBDaXJjdWl0IGJyZWFrZXIgY2hlY2sg4oCUIGltbWVkaWF0ZWx5IHBhdXNlIGlmIHJlY2VudGx5IHRyaXBwZWRcbiAgaWYgKGNvbnRleHQuYWdlbnROYW1lICYmIGNpcmN1aXRCcmVha2VyLmlzVHJpcHBlZChjb250ZXh0LmFnZW50TmFtZSwgQ0lSQ1VJVF9CUkVBS0VSX0NPT0xET1dOX01TKSkge1xuICAgIHJldHVybiB7XG4gICAgICBhY3Rpb246ICdwYXVzZScsXG4gICAgICByZWFzb246ICdDaXJjdWl0IGJyZWFrZXI6IGFnZW50IHJlY2VudGx5IGV4aGF1c3RlZCByZXNpbGllbmNlIGxpbWl0cyDigJQgZm9yY2luZyBpbW1lZGlhdGUgcGF1c2UgdG8gcHJldmVudCByZS1leGVjdXRpb24gbG9vcCcsXG4gICAgICBjb250aW51YXRpb25Db3VudDogY29udGV4dC5jb250aW51YXRpb25Db3VudCxcbiAgICAgIG1heENvbnRpbnVhdGlvbnM6IHBvbGljeS5tYXhDb250aW51YXRpb25zLFxuICAgIH07XG4gIH1cblxuICBjb25zdCB7IG9uU3RlcExpbWl0UmVhY2hlZCwgbWF4Q29udGludWF0aW9ucyB9ID0gcG9saWN5O1xuXG4gIC8vIERlZmF1bHQgYmVoYXZpb3Ig4oCUIHBhdXNlXG4gIGlmIChvblN0ZXBMaW1pdFJlYWNoZWQgPT09ICdwYXVzZScpIHtcbiAgICByZXR1cm4ge1xuICAgICAgYWN0aW9uOiAncGF1c2UnLFxuICAgICAgcmVhc29uOiAnUmVzaWxpZW5jZSBwb2xpY3k6IHBhdXNlIG9uIHN0ZXAgbGltaXQgKGRlZmF1bHQgYmVoYXZpb3IpJyxcbiAgICAgIGNvbnRpbnVhdGlvbkNvdW50OiBjb250ZXh0LmNvbnRpbnVhdGlvbkNvdW50LFxuICAgICAgbWF4Q29udGludWF0aW9ucyxcbiAgICB9O1xuICB9XG5cbiAgLy8gQ2hlY2sgY29udGludWF0aW9uIGNhcCAoMCA9IHVubGltaXRlZClcbiAgaWYgKG1heENvbnRpbnVhdGlvbnMgPiAwICYmIGNvbnRleHQuY29udGludWF0aW9uQ291bnQgPj0gbWF4Q29udGludWF0aW9ucykge1xuICAgIC8vIFRyaXAgdGhlIGNpcmN1aXQgYnJlYWtlciBzbyByZS1leGVjdXRpb24gd2l0aGluIGNvb2xkb3duIGlzIGJsb2NrZWRcbiAgICBpZiAoY29udGV4dC5hZ2VudE5hbWUpIHtcbiAgICAgIGNpcmN1aXRCcmVha2VyLnRyaXAoY29udGV4dC5hZ2VudE5hbWUpO1xuICAgIH1cbiAgICByZXR1cm4ge1xuICAgICAgYWN0aW9uOiAncGF1c2UnLFxuICAgICAgcmVhc29uOiBgUmVzaWxpZW5jZSBsaW1pdCByZWFjaGVkOiAke2NvbnRleHQuY29udGludWF0aW9uQ291bnR9LyR7bWF4Q29udGludWF0aW9uc30gY29udGludWF0aW9ucyBleGhhdXN0ZWRgLFxuICAgICAgY29udGludWF0aW9uQ291bnQ6IGNvbnRleHQuY29udGludWF0aW9uQ291bnQsXG4gICAgICBtYXhDb250aW51YXRpb25zLFxuICAgIH07XG4gIH1cblxuICAvLyBBdXRvLWNvbnRpbnVlIG9yIHJlc3RhcnRcbiAgaWYgKG9uU3RlcExpbWl0UmVhY2hlZCA9PT0gJ2NvbnRpbnVlJykge1xuICAgIHJldHVybiB7XG4gICAgICBhY3Rpb246ICdjb250aW51ZScsXG4gICAgICByZWFzb246IGBBdXRvLWNvbnRpbnVpbmc6IHN0ZXAgbGltaXQgcmVhY2hlZCwgY29udGludWF0aW9uICR7Y29udGV4dC5jb250aW51YXRpb25Db3VudCArIDF9JHttYXhDb250aW51YXRpb25zID4gMCA/IGAvJHttYXhDb250aW51YXRpb25zfWAgOiAnJ31gLFxuICAgICAgY29udGludWF0aW9uQ291bnQ6IGNvbnRleHQuY29udGludWF0aW9uQ291bnQgKyAxLFxuICAgICAgbWF4Q29udGludWF0aW9ucyxcbiAgICB9O1xuICB9XG5cbiAgLy8gcmVzdGFydFxuICByZXR1cm4ge1xuICAgIGFjdGlvbjogJ3Jlc3RhcnQnLFxuICAgIHJlYXNvbjogYEF1dG8tcmVzdGFydGluZzogc3RlcCBsaW1pdCByZWFjaGVkLCByZXN0YXJ0ICR7Y29udGV4dC5jb250aW51YXRpb25Db3VudCArIDF9JHttYXhDb250aW51YXRpb25zID4gMCA/IGAvJHttYXhDb250aW51YXRpb25zfWAgOiAnJ31gLFxuICAgIGNvbnRpbnVhdGlvbkNvdW50OiBjb250ZXh0LmNvbnRpbnVhdGlvbkNvdW50ICsgMSxcbiAgICBtYXhDb250aW51YXRpb25zLFxuICB9O1xufVxuXG5mdW5jdGlvbiBldmFsdWF0ZUZhaWx1cmVSZXNpbGllbmNlKFxuICBwb2xpY3k6IFJlcXVpcmVkPEFnZW50UmVzaWxpZW5jZVBvbGljeT4sXG4gIGNvbnRleHQ6IFJlc2lsaWVuY2VDb250ZXh0XG4pOiBSZXNpbGllbmNlQWN0aW9uIHtcbiAgLy8gQ2lyY3VpdCBicmVha2VyIGNoZWNrIOKAlCBpbW1lZGlhdGVseSBwYXVzZSBpZiByZWNlbnRseSB0cmlwcGVkXG4gIGlmIChjb250ZXh0LmFnZW50TmFtZSAmJiBjaXJjdWl0QnJlYWtlci5pc1RyaXBwZWQoY29udGV4dC5hZ2VudE5hbWUsIENJUkNVSVRfQlJFQUtFUl9DT09MRE9XTl9NUykpIHtcbiAgICByZXR1cm4ge1xuICAgICAgYWN0aW9uOiAncGF1c2UnLFxuICAgICAgcmVhc29uOiAnQ2lyY3VpdCBicmVha2VyOiBhZ2VudCByZWNlbnRseSBleGhhdXN0ZWQgcmVzaWxpZW5jZSBsaW1pdHMg4oCUIGZvcmNpbmcgaW1tZWRpYXRlIHBhdXNlIHRvIHByZXZlbnQgcmUtZXhlY3V0aW9uIGxvb3AnLFxuICAgICAgcmV0cnlDb3VudDogY29udGV4dC5yZXRyeUNvdW50LFxuICAgIH07XG4gIH1cblxuICBjb25zdCB7IG9uRXhlY3V0aW9uRmFpbHVyZSwgbWF4UmV0cmllcywgcmV0cnlCYWNrb2ZmIH0gPSBwb2xpY3k7XG5cbiAgLy8gRGVmYXVsdCBiZWhhdmlvciDigJQgcGF1c2VcbiAgaWYgKG9uRXhlY3V0aW9uRmFpbHVyZSA9PT0gJ3BhdXNlJykge1xuICAgIHJldHVybiB7XG4gICAgICBhY3Rpb246ICdwYXVzZScsXG4gICAgICByZWFzb246ICdSZXNpbGllbmNlIHBvbGljeTogcGF1c2Ugb24gZXhlY3V0aW9uIGZhaWx1cmUgKGRlZmF1bHQgYmVoYXZpb3IpJyxcbiAgICAgIHJldHJ5Q291bnQ6IGNvbnRleHQucmV0cnlDb3VudCxcbiAgICB9O1xuICB9XG5cbiAgLy8gcmV0cnlcbiAgaWYgKG9uRXhlY3V0aW9uRmFpbHVyZSA9PT0gJ3JldHJ5Jykge1xuICAgIGlmIChjb250ZXh0LnJldHJ5Q291bnQgPj0gbWF4UmV0cmllcykge1xuICAgICAgLy8gVHJpcCB0aGUgY2lyY3VpdCBicmVha2VyIHNvIHJlLWV4ZWN1dGlvbiB3aXRoaW4gY29vbGRvd24gaXMgYmxvY2tlZFxuICAgICAgaWYgKGNvbnRleHQuYWdlbnROYW1lKSB7XG4gICAgICAgIGNpcmN1aXRCcmVha2VyLnRyaXAoY29udGV4dC5hZ2VudE5hbWUpO1xuICAgICAgfVxuICAgICAgcmV0dXJuIHtcbiAgICAgICAgYWN0aW9uOiAncGF1c2UnLFxuICAgICAgICByZWFzb246IGBSZXRyeSBsaW1pdCByZWFjaGVkOiAke2NvbnRleHQucmV0cnlDb3VudH0vJHttYXhSZXRyaWVzfSByZXRyaWVzIGV4aGF1c3RlZGAsXG4gICAgICAgIHJldHJ5Q291bnQ6IGNvbnRleHQucmV0cnlDb3VudCxcbiAgICAgIH07XG4gICAgfVxuXG4gICAgY29uc3QgYmFja29mZk1zID0gY2FsY3VsYXRlQmFja29mZihyZXRyeUJhY2tvZmYsIGNvbnRleHQucmV0cnlDb3VudCk7XG4gICAgcmV0dXJuIHtcbiAgICAgIGFjdGlvbjogJ3JldHJ5JyxcbiAgICAgIHJlYXNvbjogYFJldHJ5aW5nIGZhaWxlZCBzdGVwOiBhdHRlbXB0ICR7Y29udGV4dC5yZXRyeUNvdW50ICsgMX0vJHttYXhSZXRyaWVzfSR7YmFja29mZk1zID4gMCA/IGAgKGJhY2tvZmY6ICR7YmFja29mZk1zfW1zKWAgOiAnJ31gLFxuICAgICAgYmFja29mZk1zLFxuICAgICAgcmV0cnlDb3VudDogY29udGV4dC5yZXRyeUNvdW50ICsgMSxcbiAgICB9O1xuICB9XG5cbiAgLy8gcmVzdGFydC1mcmVzaCDigJQgY2hlY2sgY29udGludWF0aW9uIGNhcFxuICBjb25zdCB7IG1heENvbnRpbnVhdGlvbnMgfSA9IHBvbGljeTtcbiAgaWYgKG1heENvbnRpbnVhdGlvbnMgPiAwICYmIGNvbnRleHQuY29udGludWF0aW9uQ291bnQgPj0gbWF4Q29udGludWF0aW9ucykge1xuICAgIC8vIFRyaXAgdGhlIGNpcmN1aXQgYnJlYWtlciBzbyByZS1leGVjdXRpb24gd2l0aGluIGNvb2xkb3duIGlzIGJsb2NrZWRcbiAgICBpZiAoY29udGV4dC5hZ2VudE5hbWUpIHtcbiAgICAgIGNpcmN1aXRCcmVha2VyLnRyaXAoY29udGV4dC5hZ2VudE5hbWUpO1xuICAgIH1cbiAgICByZXR1cm4ge1xuICAgICAgYWN0aW9uOiAncGF1c2UnLFxuICAgICAgcmVhc29uOiBgUmVzaWxpZW5jZSBsaW1pdCByZWFjaGVkOiAke2NvbnRleHQuY29udGludWF0aW9uQ291bnR9LyR7bWF4Q29udGludWF0aW9uc30gY29udGludWF0aW9ucyBleGhhdXN0ZWRgLFxuICAgICAgY29udGludWF0aW9uQ291bnQ6IGNvbnRleHQuY29udGludWF0aW9uQ291bnQsXG4gICAgICBtYXhDb250aW51YXRpb25zLFxuICAgIH07XG4gIH1cblxuICByZXR1cm4ge1xuICAgIGFjdGlvbjogJ3Jlc3RhcnQnLFxuICAgIHJlYXNvbjogYFJlc3RhcnRpbmcgZnJlc2ggYWZ0ZXIgZmFpbHVyZTogcmVzdGFydCAke2NvbnRleHQuY29udGludWF0aW9uQ291bnQgKyAxfSR7bWF4Q29udGludWF0aW9ucyA+IDAgPyBgLyR7bWF4Q29udGludWF0aW9uc31gIDogJyd9YCxcbiAgICBjb250aW51YXRpb25Db3VudDogY29udGV4dC5jb250aW51YXRpb25Db3VudCArIDEsXG4gICAgbWF4Q29udGludWF0aW9ucyxcbiAgfTtcbn1cbiJdfQ==