@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.
111 lines • 13.1 kB
JavaScript
/**
* Resilience Metrics Tracker
*
* Provides observability into agent execution resilience actions:
* auto-continuations past step limits, step retries, auto-restarts,
* circuit breaker trips, and outcome tracking after resilience fires.
*
* Issue #526: Visibility into how often resilience actions fire and
* whether they lead to successful completions.
*/
import { logger } from '../../utils/logger.js';
// ============================================================================
// Metrics tracker
// ============================================================================
/**
* Lightweight in-memory tracker for agent resilience patterns.
* Issue #526: Provides observability into auto-continue, retry, and
* circuit breaker behaviour during agent execution.
*/
export class ResilienceMetricsTracker {
_autoContinuations = 0;
_autoRestarts = 0;
_stepRetries = 0;
_resilienceLimitsReached = 0;
_circuitBreakerTrips = 0;
_successAfterResilience = 0;
_failureAfterResilience = 0;
static LOG_INTERVAL = 25;
recordAutoContinuation() {
this._autoContinuations++;
this.maybeLogSnapshot();
}
recordAutoRestart() {
this._autoRestarts++;
this.maybeLogSnapshot();
}
recordStepRetry() {
this._stepRetries++;
this.maybeLogSnapshot();
}
recordResilienceLimit() {
this._resilienceLimitsReached++;
this.maybeLogSnapshot();
}
recordCircuitBreakerTrip() {
this._circuitBreakerTrips++;
this.maybeLogSnapshot();
}
recordCompletionAfterResilience(success) {
if (success) {
this._successAfterResilience++;
}
else {
this._failureAfterResilience++;
}
this.maybeLogSnapshot();
}
getSnapshot() {
return {
autoContinuations: this._autoContinuations,
autoRestarts: this._autoRestarts,
stepRetries: this._stepRetries,
resilienceLimitsReached: this._resilienceLimitsReached,
circuitBreakerTrips: this._circuitBreakerTrips,
successAfterResilience: this._successAfterResilience,
failureAfterResilience: this._failureAfterResilience,
};
}
/** Reset all counters (for testing). */
reset() {
this._autoContinuations = 0;
this._autoRestarts = 0;
this._stepRetries = 0;
this._resilienceLimitsReached = 0;
this._circuitBreakerTrips = 0;
this._successAfterResilience = 0;
this._failureAfterResilience = 0;
}
get _totalActions() {
return (this._autoContinuations +
this._autoRestarts +
this._stepRetries +
this._resilienceLimitsReached +
this._circuitBreakerTrips);
}
maybeLogSnapshot() {
if (this._totalActions > 0 &&
this._totalActions % ResilienceMetricsTracker.LOG_INTERVAL === 0) {
logger.info('Resilience metrics snapshot', {
...this.getSnapshot(),
});
}
}
}
/** Module-level singleton instance (Issue #526) */
export const resilienceMetrics = new ResilienceMetricsTracker();
/**
* Get the current resilience metrics snapshot.
* Issue #526: Programmatic access for monitoring/diagnostics.
*/
export function getResilienceMetrics() {
return resilienceMetrics.getSnapshot();
}
/**
* Reset resilience metrics (for testing only).
* @internal
*/
export function resetResilienceMetrics() {
resilienceMetrics.reset();
}
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicmVzaWxpZW5jZU1ldHJpY3MuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi8uLi9zcmMvZWxlbWVudHMvYWdlbnRzL3Jlc2lsaWVuY2VNZXRyaWNzLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBOzs7Ozs7Ozs7R0FTRztBQUVILE9BQU8sRUFBRSxNQUFNLEVBQUUsTUFBTSx1QkFBdUIsQ0FBQztBQTJCL0MsK0VBQStFO0FBQy9FLGtCQUFrQjtBQUNsQiwrRUFBK0U7QUFFL0U7Ozs7R0FJRztBQUNILE1BQU0sT0FBTyx3QkFBd0I7SUFDM0Isa0JBQWtCLEdBQUcsQ0FBQyxDQUFDO0lBQ3ZCLGFBQWEsR0FBRyxDQUFDLENBQUM7SUFDbEIsWUFBWSxHQUFHLENBQUMsQ0FBQztJQUNqQix3QkFBd0IsR0FBRyxDQUFDLENBQUM7SUFDN0Isb0JBQW9CLEdBQUcsQ0FBQyxDQUFDO0lBQ3pCLHVCQUF1QixHQUFHLENBQUMsQ0FBQztJQUM1Qix1QkFBdUIsR0FBRyxDQUFDLENBQUM7SUFFNUIsTUFBTSxDQUFVLFlBQVksR0FBRyxFQUFFLENBQUM7SUFFMUMsc0JBQXNCO1FBQ3BCLElBQUksQ0FBQyxrQkFBa0IsRUFBRSxDQUFDO1FBQzFCLElBQUksQ0FBQyxnQkFBZ0IsRUFBRSxDQUFDO0lBQzFCLENBQUM7SUFFRCxpQkFBaUI7UUFDZixJQUFJLENBQUMsYUFBYSxFQUFFLENBQUM7UUFDckIsSUFBSSxDQUFDLGdCQUFnQixFQUFFLENBQUM7SUFDMUIsQ0FBQztJQUVELGVBQWU7UUFDYixJQUFJLENBQUMsWUFBWSxFQUFFLENBQUM7UUFDcEIsSUFBSSxDQUFDLGdCQUFnQixFQUFFLENBQUM7SUFDMUIsQ0FBQztJQUVELHFCQUFxQjtRQUNuQixJQUFJLENBQUMsd0JBQXdCLEVBQUUsQ0FBQztRQUNoQyxJQUFJLENBQUMsZ0JBQWdCLEVBQUUsQ0FBQztJQUMxQixDQUFDO0lBRUQsd0JBQXdCO1FBQ3RCLElBQUksQ0FBQyxvQkFBb0IsRUFBRSxDQUFDO1FBQzVCLElBQUksQ0FBQyxnQkFBZ0IsRUFBRSxDQUFDO0lBQzFCLENBQUM7SUFFRCwrQkFBK0IsQ0FBQyxPQUFnQjtRQUM5QyxJQUFJLE9BQU8sRUFBRSxDQUFDO1lBQ1osSUFBSSxDQUFDLHVCQUF1QixFQUFFLENBQUM7UUFDakMsQ0FBQzthQUFNLENBQUM7WUFDTixJQUFJLENBQUMsdUJBQXVCLEVBQUUsQ0FBQztRQUNqQyxDQUFDO1FBQ0QsSUFBSSxDQUFDLGdCQUFnQixFQUFFLENBQUM7SUFDMUIsQ0FBQztJQUVELFdBQVc7UUFDVCxPQUFPO1lBQ0wsaUJBQWlCLEVBQUUsSUFBSSxDQUFDLGtCQUFrQjtZQUMxQyxZQUFZLEVBQUUsSUFBSSxDQUFDLGFBQWE7WUFDaEMsV0FBVyxFQUFFLElBQUksQ0FBQyxZQUFZO1lBQzlCLHVCQUF1QixFQUFFLElBQUksQ0FBQyx3QkFBd0I7WUFDdEQsbUJBQW1CLEVBQUUsSUFBSSxDQUFDLG9CQUFvQjtZQUM5QyxzQkFBc0IsRUFBRSxJQUFJLENBQUMsdUJBQXVCO1lBQ3BELHNCQUFzQixFQUFFLElBQUksQ0FBQyx1QkFBdUI7U0FDckQsQ0FBQztJQUNKLENBQUM7SUFFRCx3Q0FBd0M7SUFDeEMsS0FBSztRQUNILElBQUksQ0FBQyxrQkFBa0IsR0FBRyxDQUFDLENBQUM7UUFDNUIsSUFBSSxDQUFDLGFBQWEsR0FBRyxDQUFDLENBQUM7UUFDdkIsSUFBSSxDQUFDLFlBQVksR0FBRyxDQUFDLENBQUM7UUFDdEIsSUFBSSxDQUFDLHdCQUF3QixHQUFHLENBQUMsQ0FBQztRQUNsQyxJQUFJLENBQUMsb0JBQW9CLEdBQUcsQ0FBQyxDQUFDO1FBQzlCLElBQUksQ0FBQyx1QkFBdUIsR0FBRyxDQUFDLENBQUM7UUFDakMsSUFBSSxDQUFDLHVCQUF1QixHQUFHLENBQUMsQ0FBQztJQUNuQyxDQUFDO0lBRUQsSUFBWSxhQUFhO1FBQ3ZCLE9BQU8sQ0FDTCxJQUFJLENBQUMsa0JBQWtCO1lBQ3ZCLElBQUksQ0FBQyxhQUFhO1lBQ2xCLElBQUksQ0FBQyxZQUFZO1lBQ2pCLElBQUksQ0FBQyx3QkFBd0I7WUFDN0IsSUFBSSxDQUFDLG9CQUFvQixDQUMxQixDQUFDO0lBQ0osQ0FBQztJQUVPLGdCQUFnQjtRQUN0QixJQUNFLElBQUksQ0FBQyxhQUFhLEdBQUcsQ0FBQztZQUN0QixJQUFJLENBQUMsYUFBYSxHQUFHLHdCQUF3QixDQUFDLFlBQVksS0FBSyxDQUFDLEVBQ2hFLENBQUM7WUFDRCxNQUFNLENBQUMsSUFBSSxDQUFDLDZCQUE2QixFQUFFO2dCQUN6QyxHQUFHLElBQUksQ0FBQyxXQUFXLEVBQUU7YUFDdEIsQ0FBQyxDQUFDO1FBQ0wsQ0FBQztJQUNILENBQUM7O0FBR0gsbURBQW1EO0FBQ25ELE1BQU0sQ0FBQyxNQUFNLGlCQUFpQixHQUFHLElBQUksd0JBQXdCLEVBQUUsQ0FBQztBQUVoRTs7O0dBR0c7QUFDSCxNQUFNLFVBQVUsb0JBQW9CO0lBQ2xDLE9BQU8saUJBQWlCLENBQUMsV0FBVyxFQUFFLENBQUM7QUFDekMsQ0FBQztBQUVEOzs7R0FHRztBQUNILE1BQU0sVUFBVSxzQkFBc0I7SUFDcEMsaUJBQWlCLENBQUMsS0FBSyxFQUFFLENBQUM7QUFDNUIsQ0FBQyIsInNvdXJjZXNDb250ZW50IjpbIi8qKlxuICogUmVzaWxpZW5jZSBNZXRyaWNzIFRyYWNrZXJcbiAqXG4gKiBQcm92aWRlcyBvYnNlcnZhYmlsaXR5IGludG8gYWdlbnQgZXhlY3V0aW9uIHJlc2lsaWVuY2UgYWN0aW9uczpcbiAqIGF1dG8tY29udGludWF0aW9ucyBwYXN0IHN0ZXAgbGltaXRzLCBzdGVwIHJldHJpZXMsIGF1dG8tcmVzdGFydHMsXG4gKiBjaXJjdWl0IGJyZWFrZXIgdHJpcHMsIGFuZCBvdXRjb21lIHRyYWNraW5nIGFmdGVyIHJlc2lsaWVuY2UgZmlyZXMuXG4gKlxuICogSXNzdWUgIzUyNjogVmlzaWJpbGl0eSBpbnRvIGhvdyBvZnRlbiByZXNpbGllbmNlIGFjdGlvbnMgZmlyZSBhbmRcbiAqIHdoZXRoZXIgdGhleSBsZWFkIHRvIHN1Y2Nlc3NmdWwgY29tcGxldGlvbnMuXG4gKi9cblxuaW1wb3J0IHsgbG9nZ2VyIH0gZnJvbSAnLi4vLi4vdXRpbHMvbG9nZ2VyLmpzJztcblxuLy8gPT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PVxuLy8gTWV0cmljcyBzbmFwc2hvdCBpbnRlcmZhY2Vcbi8vID09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT1cblxuLyoqXG4gKiBTbmFwc2hvdCBvZiByZXNpbGllbmNlIG1ldHJpY3MgY291bnRlcnMuXG4gKiBOb24tcGVyc2lzdGVkLCBpbi1tZW1vcnkgY291bnRlcnMgcmVzZXQgb24gc2VydmVyIHJlc3RhcnQuXG4gKi9cbmV4cG9ydCBpbnRlcmZhY2UgUmVzaWxpZW5jZU1ldHJpY3NTbmFwc2hvdCB7XG4gIC8qKiBUb3RhbCBhdXRvLWNvbnRpbnVhdGlvbnMgdHJpZ2dlcmVkIChhZ2VudCBjb250aW51ZWQgcGFzdCBzdGVwIGxpbWl0KSAqL1xuICBhdXRvQ29udGludWF0aW9uczogbnVtYmVyO1xuICAvKiogVG90YWwgYXV0by1yZXN0YXJ0cyB0cmlnZ2VyZWQgKGFnZW50IHJlc3RhcnRlZCBhZnRlciBmYWlsdXJlKSAqL1xuICBhdXRvUmVzdGFydHM6IG51bWJlcjtcbiAgLyoqIFRvdGFsIHN0ZXAgcmV0cmllcyB0cmlnZ2VyZWQgKGluZGl2aWR1YWwgc3RlcCByZXRyaWVkIGFmdGVyIGVycm9yKSAqL1xuICBzdGVwUmV0cmllczogbnVtYmVyO1xuICAvKiogVGltZXMgbWF4Q29udGludWF0aW9ucy9tYXhSZXRyaWVzIGV4aGF1c3RlZCwgZm9yY2luZyBhIHBhdXNlICovXG4gIHJlc2lsaWVuY2VMaW1pdHNSZWFjaGVkOiBudW1iZXI7XG4gIC8qKiBUaW1lcyBjaXJjdWl0IGJyZWFrZXIgZW5nYWdlZCAocmVwZWF0ZWQgZmFpbHVyZXMgdHJpcHBlZCBicmVha2VyKSAqL1xuICBjaXJjdWl0QnJlYWtlclRyaXBzOiBudW1iZXI7XG4gIC8qKiBFeGVjdXRpb25zIHRoYXQgY29tcGxldGVkIHN1Y2Nlc3NmdWxseSBhZnRlciBhdCBsZWFzdCBvbmUgcmVzaWxpZW5jZSBhY3Rpb24gKi9cbiAgc3VjY2Vzc0FmdGVyUmVzaWxpZW5jZTogbnVtYmVyO1xuICAvKiogRXhlY3V0aW9ucyB0aGF0IGZhaWxlZC9hYm9ydGVkIGFmdGVyIGF0IGxlYXN0IG9uZSByZXNpbGllbmNlIGFjdGlvbiAqL1xuICBmYWlsdXJlQWZ0ZXJSZXNpbGllbmNlOiBudW1iZXI7XG59XG5cbi8vID09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT1cbi8vIE1ldHJpY3MgdHJhY2tlclxuLy8gPT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PVxuXG4vKipcbiAqIExpZ2h0d2VpZ2h0IGluLW1lbW9yeSB0cmFja2VyIGZvciBhZ2VudCByZXNpbGllbmNlIHBhdHRlcm5zLlxuICogSXNzdWUgIzUyNjogUHJvdmlkZXMgb2JzZXJ2YWJpbGl0eSBpbnRvIGF1dG8tY29udGludWUsIHJldHJ5LCBhbmRcbiAqIGNpcmN1aXQgYnJlYWtlciBiZWhhdmlvdXIgZHVyaW5nIGFnZW50IGV4ZWN1dGlvbi5cbiAqL1xuZXhwb3J0IGNsYXNzIFJlc2lsaWVuY2VNZXRyaWNzVHJhY2tlciB7XG4gIHByaXZhdGUgX2F1dG9Db250aW51YXRpb25zID0gMDtcbiAgcHJpdmF0ZSBfYXV0b1Jlc3RhcnRzID0gMDtcbiAgcHJpdmF0ZSBfc3RlcFJldHJpZXMgPSAwO1xuICBwcml2YXRlIF9yZXNpbGllbmNlTGltaXRzUmVhY2hlZCA9IDA7XG4gIHByaXZhdGUgX2NpcmN1aXRCcmVha2VyVHJpcHMgPSAwO1xuICBwcml2YXRlIF9zdWNjZXNzQWZ0ZXJSZXNpbGllbmNlID0gMDtcbiAgcHJpdmF0ZSBfZmFpbHVyZUFmdGVyUmVzaWxpZW5jZSA9IDA7XG5cbiAgcHJpdmF0ZSBzdGF0aWMgcmVhZG9ubHkgTE9HX0lOVEVSVkFMID0gMjU7XG5cbiAgcmVjb3JkQXV0b0NvbnRpbnVhdGlvbigpOiB2b2lkIHtcbiAgICB0aGlzLl9hdXRvQ29udGludWF0aW9ucysrO1xuICAgIHRoaXMubWF5YmVMb2dTbmFwc2hvdCgpO1xuICB9XG5cbiAgcmVjb3JkQXV0b1Jlc3RhcnQoKTogdm9pZCB7XG4gICAgdGhpcy5fYXV0b1Jlc3RhcnRzKys7XG4gICAgdGhpcy5tYXliZUxvZ1NuYXBzaG90KCk7XG4gIH1cblxuICByZWNvcmRTdGVwUmV0cnkoKTogdm9pZCB7XG4gICAgdGhpcy5fc3RlcFJldHJpZXMrKztcbiAgICB0aGlzLm1heWJlTG9nU25hcHNob3QoKTtcbiAgfVxuXG4gIHJlY29yZFJlc2lsaWVuY2VMaW1pdCgpOiB2b2lkIHtcbiAgICB0aGlzLl9yZXNpbGllbmNlTGltaXRzUmVhY2hlZCsrO1xuICAgIHRoaXMubWF5YmVMb2dTbmFwc2hvdCgpO1xuICB9XG5cbiAgcmVjb3JkQ2lyY3VpdEJyZWFrZXJUcmlwKCk6IHZvaWQge1xuICAgIHRoaXMuX2NpcmN1aXRCcmVha2VyVHJpcHMrKztcbiAgICB0aGlzLm1heWJlTG9nU25hcHNob3QoKTtcbiAgfVxuXG4gIHJlY29yZENvbXBsZXRpb25BZnRlclJlc2lsaWVuY2Uoc3VjY2VzczogYm9vbGVhbik6IHZvaWQge1xuICAgIGlmIChzdWNjZXNzKSB7XG4gICAgICB0aGlzLl9zdWNjZXNzQWZ0ZXJSZXNpbGllbmNlKys7XG4gICAgfSBlbHNlIHtcbiAgICAgIHRoaXMuX2ZhaWx1cmVBZnRlclJlc2lsaWVuY2UrKztcbiAgICB9XG4gICAgdGhpcy5tYXliZUxvZ1NuYXBzaG90KCk7XG4gIH1cblxuICBnZXRTbmFwc2hvdCgpOiBSZXNpbGllbmNlTWV0cmljc1NuYXBzaG90IHtcbiAgICByZXR1cm4ge1xuICAgICAgYXV0b0NvbnRpbnVhdGlvbnM6IHRoaXMuX2F1dG9Db250aW51YXRpb25zLFxuICAgICAgYXV0b1Jlc3RhcnRzOiB0aGlzLl9hdXRvUmVzdGFydHMsXG4gICAgICBzdGVwUmV0cmllczogdGhpcy5fc3RlcFJldHJpZXMsXG4gICAgICByZXNpbGllbmNlTGltaXRzUmVhY2hlZDogdGhpcy5fcmVzaWxpZW5jZUxpbWl0c1JlYWNoZWQsXG4gICAgICBjaXJjdWl0QnJlYWtlclRyaXBzOiB0aGlzLl9jaXJjdWl0QnJlYWtlclRyaXBzLFxuICAgICAgc3VjY2Vzc0FmdGVyUmVzaWxpZW5jZTogdGhpcy5fc3VjY2Vzc0FmdGVyUmVzaWxpZW5jZSxcbiAgICAgIGZhaWx1cmVBZnRlclJlc2lsaWVuY2U6IHRoaXMuX2ZhaWx1cmVBZnRlclJlc2lsaWVuY2UsXG4gICAgfTtcbiAgfVxuXG4gIC8qKiBSZXNldCBhbGwgY291bnRlcnMgKGZvciB0ZXN0aW5nKS4gKi9cbiAgcmVzZXQoKTogdm9pZCB7XG4gICAgdGhpcy5fYXV0b0NvbnRpbnVhdGlvbnMgPSAwO1xuICAgIHRoaXMuX2F1dG9SZXN0YXJ0cyA9IDA7XG4gICAgdGhpcy5fc3RlcFJldHJpZXMgPSAwO1xuICAgIHRoaXMuX3Jlc2lsaWVuY2VMaW1pdHNSZWFjaGVkID0gMDtcbiAgICB0aGlzLl9jaXJjdWl0QnJlYWtlclRyaXBzID0gMDtcbiAgICB0aGlzLl9zdWNjZXNzQWZ0ZXJSZXNpbGllbmNlID0gMDtcbiAgICB0aGlzLl9mYWlsdXJlQWZ0ZXJSZXNpbGllbmNlID0gMDtcbiAgfVxuXG4gIHByaXZhdGUgZ2V0IF90b3RhbEFjdGlvbnMoKTogbnVtYmVyIHtcbiAgICByZXR1cm4gKFxuICAgICAgdGhpcy5fYXV0b0NvbnRpbnVhdGlvbnMgK1xuICAgICAgdGhpcy5fYXV0b1Jlc3RhcnRzICtcbiAgICAgIHRoaXMuX3N0ZXBSZXRyaWVzICtcbiAgICAgIHRoaXMuX3Jlc2lsaWVuY2VMaW1pdHNSZWFjaGVkICtcbiAgICAgIHRoaXMuX2NpcmN1aXRCcmVha2VyVHJpcHNcbiAgICApO1xuICB9XG5cbiAgcHJpdmF0ZSBtYXliZUxvZ1NuYXBzaG90KCk6IHZvaWQge1xuICAgIGlmIChcbiAgICAgIHRoaXMuX3RvdGFsQWN0aW9ucyA+IDAgJiZcbiAgICAgIHRoaXMuX3RvdGFsQWN0aW9ucyAlIFJlc2lsaWVuY2VNZXRyaWNzVHJhY2tlci5MT0dfSU5URVJWQUwgPT09IDBcbiAgICApIHtcbiAgICAgIGxvZ2dlci5pbmZvKCdSZXNpbGllbmNlIG1ldHJpY3Mgc25hcHNob3QnLCB7XG4gICAgICAgIC4uLnRoaXMuZ2V0U25hcHNob3QoKSxcbiAgICAgIH0pO1xuICAgIH1cbiAgfVxufVxuXG4vKiogTW9kdWxlLWxldmVsIHNpbmdsZXRvbiBpbnN0YW5jZSAoSXNzdWUgIzUyNikgKi9cbmV4cG9ydCBjb25zdCByZXNpbGllbmNlTWV0cmljcyA9IG5ldyBSZXNpbGllbmNlTWV0cmljc1RyYWNrZXIoKTtcblxuLyoqXG4gKiBHZXQgdGhlIGN1cnJlbnQgcmVzaWxpZW5jZSBtZXRyaWNzIHNuYXBzaG90LlxuICogSXNzdWUgIzUyNjogUHJvZ3JhbW1hdGljIGFjY2VzcyBmb3IgbW9uaXRvcmluZy9kaWFnbm9zdGljcy5cbiAqL1xuZXhwb3J0IGZ1bmN0aW9uIGdldFJlc2lsaWVuY2VNZXRyaWNzKCk6IFJlc2lsaWVuY2VNZXRyaWNzU25hcHNob3Qge1xuICByZXR1cm4gcmVzaWxpZW5jZU1ldHJpY3MuZ2V0U25hcHNob3QoKTtcbn1cblxuLyoqXG4gKiBSZXNldCByZXNpbGllbmNlIG1ldHJpY3MgKGZvciB0ZXN0aW5nIG9ubHkpLlxuICogQGludGVybmFsXG4gKi9cbmV4cG9ydCBmdW5jdGlvbiByZXNldFJlc2lsaWVuY2VNZXRyaWNzKCk6IHZvaWQge1xuICByZXNpbGllbmNlTWV0cmljcy5yZXNldCgpO1xufVxuIl19