/** * CircuitBreaker — prevents runaway bash/exec tool chains by: * * - Tripping on consecutive failures (models that keep repeating the * same failing command, e.g. `npm install` with wrong args in a loop) * - Tripping on slow call ratio (too many long-running commands suggest * a hung subprocess that the model doesn't know how to kill) * - Rate-limiting bursts (rapid succession of commands without reading * output suggests the model isn't processing results) * - Auto-recovering after a cooldown period so a fixed model can resume * * The breaker is owned by the ProcessRegistry so any tool that registers * a process participates in the same circuit. "Per-tool" isolation is * intentionally NOT implemented — the model treats bash/exec as one * resource pool; isolating them would let the model route around the * breaker by alternating which tool it uses. */ export interface CircuitBreakerConfig { /** * Consecutive failures before trip. Default: 5. * A single success resets this counter to 0. */ maxConsecutiveFailures?: number | undefined; /** * Slow-call threshold in ms. A call that runs longer than this is * counted as "slow". Default: 60_000 (1 minute). */ slowCallThresholdMs?: number | undefined; /** * Max slow calls before trip (within the sliding window). Default: 3. */ maxSlowCalls?: number | undefined; /** * Sliding window for rate-limit and slow-call counting, in ms. * Default: 60_000 (1 minute). */ windowMs?: number | undefined; /** * Max calls within the sliding window. Default: 30. * Burst exceeding this trips the breaker immediately. */ maxCallsPerWindow?: number | undefined; /** * Cooldown before auto-recovery attempt, in ms. Default: 30_000 (30s). * After this the breaker enters "half-open" state and allows one call * through to test whether the problem is resolved. */ cooldownMs?: number | undefined; } export type BreakerState = 'closed' | 'open' | 'half-open'; export interface CircuitBreakerSnapshot { state: 'closed' | 'open' | 'half-open'; consecutiveFailures: number; slowCallsInWindow: number; callsInWindow: number; windowMs: number; cooldownRemainingMs: number | null; lastFailureAt: number | null; lastSlowAt: number | null; } export declare class CircuitBreaker { private readonly maxConsecutiveFailures; private readonly slowCallThresholdMs; private readonly maxSlowCalls; private readonly windowMs; private readonly maxCallsPerWindow; private readonly cooldownMs; private state; private consecutiveFailures; private window; private lastFailureAt; private lastSlowAt; /** Timestamp when the breaker was opened (for cooldown calculation). */ private openedAt; /** * Master enable flag. When false the breaker is bypassed: `beforeCall` * always returns true and `afterCall` records nothing. The class itself * defaults to enabled (so the standalone unit tests exercise tripping); the * ProcessRegistry flips this off until the user opts in via `/settings`. */ private enabled; /** * Fired (best-effort) when the breaker transitions into the `open` state. * The registry uses this to arm its auto kill/reset countdown. */ onTrip?: (() => void) | undefined; /** * Fired (best-effort) when the breaker returns to `closed` after having been * open/half-open. The registry uses this to cancel a pending kill/reset. */ onReset?: (() => void) | undefined; constructor(config?: CircuitBreakerConfig); /** Toggle the master enable. Disabling resets to a clean `closed` state. */ setEnabled(enabled: boolean): void; get isEnabled(): boolean; /** * Returns true if the circuit allows a new call to proceed. * When false, callers should abort the tool call and return a * circuit-breaker error instead of spawning a process. */ get canProceed(): boolean; /** * Snapshot of the current breaker state for observability (`/kill`). */ snapshot(): CircuitBreakerSnapshot; /** * Call this BEFORE spawning a bash/exec process. * Returns true if the call is allowed; false if the breaker is open. * When false, callers MUST NOT spawn a process. * * @param bypass - If true, skip the circuit breaker check entirely. * Use for background/fire-and-forget processes that should * not affect breaker state. */ beforeCall(bypass?: boolean): boolean; /** * Call this AFTER a bash/exec process finishes (success or failure). * `durationMs` is the wall-clock time the process ran. * `failed` is true when the process returned a non-zero exit code or * threw an exception before spawning. * * @param bypass - If true, do not update breaker state. * Use for background/fire-and-forget processes. */ afterCall(durationMs: number, failed: boolean, bypass?: boolean): void; /** Force the breaker open. Used by /kill force and Ctrl+C. */ forceOpen(): void; /** Force a reset to closed. Used by tests and /kill reset. */ forceReset(): void; private _trip; private _reset; /** Transition from open → half-open when cooldown elapses. */ private _checkStateTransition; private _pruneWindow; } //# sourceMappingURL=circuit-breaker.d.ts.map