import { CycleDetector, ToolCallProgressTracker, type CycleDetection, type LoopBreakDecision } from "../loop-breaker.js"; import { type SemanticCallDigest, type SemanticLoopVerdict } from "../semantic-loop-check.js"; /** LLM-judged loop detection state for one run. */ export interface SemanticLoopRunState { checksUsed: number; lastCheckTurn: number; pending: boolean; verdict: SemanticLoopVerdict | null; injected: boolean; } /** * Per-run stuck-detection state (mirrors the TUI's useAgentLoop refs): tool * stats, streamed text, progress/cycle trackers, the two-stage loop-breaker and * the semantic loop judge's budget. Reset at the start of every run; observed * from the event stream. Owns state and decisions only — logging, events and * the judge call stay with the session. */ export declare class RunLoopMonitor { stats: { toolCalls: number; toolFailures: number; turns: number; }; text: string; consecutiveFailures: number; repeatedNoProgressCalls: number; readonly progressTracker: ToolCallProgressTracker; readonly cycleDetector: CycleDetector; cyclicPattern: CycleDetection | null; /** 0 = none; 1 = first nudge sent; 2 = final stop-and-report injected. */ loopBreakInjected: 0 | 1 | 2; /** Recent tool-call digests for the semantic loop judge — bounded ring. */ recentCalls: SemanticCallDigest[]; /** LLM-judged loop detection state. `verdict` holds a LOOP verdict awaiting * injection at the next steering poll; judge failures fail open (no * injection) and still consume budget + cooldown. Replaced (never mutated) * on reset so a late judge from an earlier run cannot write into it. */ semanticLoop: SemanticLoopRunState; /** Clean slate for a new run. */ reset(): void; recordText(text: string): void; recordToolEnd(name: string, args: Record | undefined, result: string, isError: boolean): void; recordTurn(turn: number): void; /** Deterministic stuck verdict from the current signals. */ evaluateLoopBreak(): LoopBreakDecision; /** * Two-stage loop-breaker: stage 1 nudges; a FRESH detection after that * escalates to stage 2. Returns the stage to inject (signals reset so stage 2 * only fires on new evidence), or null when nothing should be injected. */ takeLoopBreakStage(decision: LoopBreakDecision): 1 | 2 | null; /** Consume a pending semantic LOOP verdict, exactly once per run. */ takeSemanticVerdict(): SemanticLoopVerdict | null; shouldRunSemanticCheck(deterministicBreak: boolean): boolean; /** Mark a judge call in flight; the returned run state is its stale-guard token. */ beginSemanticCheck(): SemanticLoopRunState; /** Publish a judge verdict only if its run is still the current one. */ publishSemanticVerdict(runState: SemanticLoopRunState, verdict: SemanticLoopVerdict): void; /** Settle a judge call: consume budget + cooldown, unless its run is stale. */ settleSemanticCheck(runState: SemanticLoopRunState): void; /** Reasons to refuse a turn-budget extension; empty means grant. */ turnExtensionRefusals(): string[]; }