/** * "Never-executes" containment: a turn that spent its ENTIRE output/thinking * budget thinking (zero visible text, zero tool calls) contributed nothing * durable and burned wall-clock + rate window (transponder incident * 2026-07-07 ses_0c435485cffe: one 6.6-minute thinking-only turn, then the * retry 429'd and the session died). * * Faithful port of transponder/src/thinking-exhaustion.mjs, adapted to the * Cursor bridge where Anthropic's stop_reason=max_tokens is usually absent: * - strict path: stopReason === "max_tokens" OR hitTokenCap === true * - Cursor proxy sets hitTokenCap via a thinking-volume / duration heuristic * when a turn settles thinking-only with no tools (see proxy settle seams) * * Distinct from reasoning-tail.ts on purpose: the tail REPLAYS a silent turn's * conclusion AFTER tool calls ran; this lane fires when NO tool call ever ran * — the work was lost, and the model must be told to externalize instead of * re-planning into the same spiral. */ /** Kill switch: ON by default; CURSOR_OPENCODE_THINKING_EXHAUSTION=0 disables. */ export declare function thinkingExhaustionEnabled(env?: Record): boolean; /** * Minimum thinking chars before a Cursor thinking-only settle is treated as * exhaustion (no Anthropic max_tokens signal). Default 8000 (~2k tokens). * Override with CURSOR_OPENCODE_THINKING_EXHAUSTION_MIN_CHARS; 0 disables the * char heuristic (duration-only / strict max_tokens still apply). */ export declare function thinkingExhaustionMinChars(env?: Record): number; /** * Minimum thinking duration (ms) before a Cursor thinking-only settle is * treated as exhaustion. Default 60_000. Override with * CURSOR_OPENCODE_THINKING_EXHAUSTION_MIN_MS; 0 disables the duration heuristic. */ export declare function thinkingExhaustionMinMs(env?: Record): number; /** Same runaway-reasoning family as guard/tail/frames. */ export declare const THINKING_EXHAUSTION_MODEL_RE: RegExp; export declare function thinkingExhaustionEligible(modelId: string): boolean; /** Evidence shape accepted by the settle-time classifier. */ export interface ThinkingExhaustionEvidence { /** Anthropic-shaped stop reason when available ("max_tokens", "end_turn", …). */ stopReason?: string | null; /** Explicit Cursor-side "hit the thinking/output cap" flag from the proxy. */ hitTokenCap?: boolean; sawVisibleText?: boolean; toolUseBlocks?: number; /** Thinking/text blocks that arrived (>0 distinguishes from empty streams). */ blocks?: number; thinkingChars?: number; thinkingDurationMs?: number | null; outputTokens?: number; } /** * Settle-time classifier: thinking-only max_tokens / cap exhaustion. * blocks>0 distinguishes from the empty-stream lane (nothing arrived at all). */ export declare function isThinkingExhaustion(m: ThinkingExhaustionEvidence | null | undefined): boolean; /** * Infer a Cursor hitTokenCap flag from thinking volume / duration when the * Anthropic stop_reason is absent. Returns false when heuristics are disabled * or thresholds are not met. */ export declare function inferCursorHitTokenCap(m: Pick | null | undefined, env?: Record): boolean; export declare function getThinkingExhaustionCount(): number; export declare function resetThinkingExhaustionCount(): void; /** Per-conversation arm/streak state (rides conversationStates TTL). */ export interface ThinkingExhaustionState { thinkingExhaustedAt: number | null; thinkingExhaustStreak: number; } export declare function newThinkingExhaustionState(): ThinkingExhaustionState; /** * Turn-settle hook: detect, count, and arm the one-shot nudge. * Consecutive exhaustions grow streak so the nudge can escalate; any healthy * settle (text or tool_use) resets the streak. */ export declare function settleThinkingExhaustion(session: ThinkingExhaustionState | null | undefined, m: ThinkingExhaustionEvidence | null | undefined, timing?: { durationMs?: number; } | null): boolean; /** Nudge wording. Streak >= 2 names the pattern; single event stays direct. */ export declare function thinkingExhaustionNudge(streak?: number): string; /** * Single-shot consume for the harness appendix: the -wrapped * nudge, or null when nothing is armed / model ineligible. Streak is NOT * cleared — only a healthy settle clears it. */ export declare function consumeThinkingExhaustionNote(session: ThinkingExhaustionState | null | undefined, modelId: string): string | null;