export type CliUsageLimitKind = 'usage' | 'rate'; export interface CliUsageLimitState { limited: true; kind: CliUsageLimitKind; retryAtMs: number; retryLabel: string; retryReady: boolean; } export interface CliUsageLimitNotDetected { limited: false; } export type CliUsageLimitDetection = CliUsageLimitState | CliUsageLimitNotDetected; export declare const HARD_RATE_LIMIT_COOLDOWN_MS: number; export interface DetectUsageLimitOptions { /** * Suppress the screen-scan `rate` verdict entirely. Set for CLIs that have an * authoritative structured rate-limit signal (Claude family via transcript * `error:"rate_limit"`): there, scraping the screen for "429" / "rate limit" * only produces false positives (the model's own output, or a dev editing * rate-limit code, puts those phrases on screen). `usage` quota detection is * unaffected — it has no structured equivalent yet. */ suppressRateKind?: boolean; /** * Whether the CLI is demonstrably producing output (PTY activity within the * caller's freshness window). The active-work gate in detectScreenUsageLimit * only suppresses the verdict while this is true. `working`/`analyzing` * alone do not prove output is progressing — `working` is the default * projection whenever promptReady === false — so a non-structured CLI * blocked at a rate/quota error screen that never renders its configured * ready prompt stays `working` forever (only Codex App projects `stalled`), * and a genuine blocking 429 would be suppressed indefinitely. Pass `false` * when PTY output has been quiescent past the window so the detector still * runs on a parked error screen. Omit (or pass `true`) to keep the * conservative suppress-on-working behavior. */ outputActive?: boolean; } /** * Whether a runtime screen status proves the CLI is actively doing work. * * The screen-scan detector cannot tell a live limit block from limit-shaped * text the CLI itself put on screen — a model answer quoting a business 429, * tool output, docs, test fixtures. The runtime status disambiguates: a * rate/usage limit blocks the CLI, and a blocked CLI sits at an error/prompt * screen (`idle`/`stalled`); it does not keep producing output. So while the * CLI is `working`/`analyzing`, any "429 / rate limit / usage limit" text on * screen is the CLI's own output or a transient retry it is handling * internally, and a screen-scan verdict must be suppressed. Root cause of the * "CLI 还在跑却提示限额已达" false reports (Codex/Hermes, 2026-08). * * Caveat (refined by detectScreenUsageLimit's `outputActive` option): in this * worker `working` is the DEFAULT projection whenever promptReady === false, * not proof that output is progressing. A non-structured CLI blocked at a * rate-limit error screen that never renders its ready prompt stays `working` * forever (only Codex App projects `stalled`), so the status alone is not a * safe suppression gate — pair it with an output-activity signal. */ export declare function isActiveWorkRuntimeStatus(status: string | null | undefined): boolean; /** * Screen-frame detection: `detectCliUsageLimit` gated on the frame's runtime * status. The worker's per-frame classify path goes through here so a single * choke point owns the "active work suppresses the verdict" policy — keeping * it out of the pure text classifier, whose existing callers (turn-start * stale-banner snapshot) have no fresh status context. * * The suppression requires BOTH an active-work status AND actively progressing * output (`outputActive !== false`). When `outputActive` is `false` the CLI is * parked (PTY quiescent) even though the status says `working` — a blocked * non-structured CLI's error screen — so the detector must still run. Callers * that cannot assess output activity omit the hint and keep suppressing on * working/analyzing (the conservative default). */ export declare function detectScreenUsageLimit(text: string, status: string | null | undefined, now?: Date, opts?: DetectUsageLimitOptions): CliUsageLimitDetection; /** * Whether a CLI adapter is authoritative for structured rate limits — i.e. it * actually PUBLISHES a `limited` screen_update from a machine signal in its * transcript rather than from scraping screen text. Two families qualify: * * - The Claude family (`claudeDataDir`): the worker's * `maybeEmitStructuredRateLimit` reads the transcript's `error:"rate_limit"` * record on the `bridgeJsonlPath` path. * - Codex (`emitsStructuredRateLimit`): `maybeEmitCodexStructuredRateLimit` * reads the rollout's `codex_rate_limited` terminal (`isCodexRateLimitEvent`) * and emits `limited`. This emit runs only under the `structuredBridgeIsCodex` * gate, so among codexBridgeQueue CLIs only codex sets the flag. * * When true the worker passes `suppressRateKind` so the screen-scan `rate` * verdict is dropped in favor of the structured signal — otherwise the model's * own output (or a dev editing rate-limit code) puts "429" / "exceeded retry * limit" on screen and the scraper cannot tell a printed 429 from a request * that actually returned 429. * * The other codexBridgeQueue CLIs (grok / traex / pi / hermes / mtr / cursor) * emit NO structured `limited` state, so they must keep screen-scanning: set * neither field on them or a real 429 silently loses its backoff + Dashboard * 「需要你」signal. Most set `reliableTurnTerminal`, so gating on that flag would * wrongly suppress them; gating on these two explicit capability fields keeps * the split exact. Extracted as a pure predicate so it has a direct unit-test * surface (see cli-usage-limit.test.ts) — a future adapter can't silently * re-broaden it. */ export declare function isStructuredRateLimitAuthoritative(adapter: { readonly claudeDataDir?: string; readonly emitsStructuredRateLimit?: boolean; } | null | undefined): boolean; export declare function detectCliUsageLimit(text: string, now?: Date, opts?: DetectUsageLimitOptions): CliUsageLimitDetection; export declare function usageLimitStateKey(state: CliUsageLimitState): string; /** * Build a rate-limit state from a STRUCTURED signal (e.g. Claude Code's * transcript `error: "rate_limit"` record) instead of screen text. * * The caller has already decided this IS a rate limit via a machine field, so * unlike detectCliUsageLimit() there is no pattern gate here — we only need to * fix the retry time. Claude's rate-limit records usually carry a human clock * in their text ("You've hit your session limit · resets 10:40pm"); when * `text` yields a parseable time we honor it (accurate retry_ready flip), * otherwise we reuse the same wall-clock-bucketed fallback the screen-text * hard-429 path uses so the usageLimitStateKey stays stable across repeated * ticks within a bucket (no persist/timer churn on the daemon side). */ export declare function structuredRateLimitState(text?: string, now?: Date): CliUsageLimitState; //# sourceMappingURL=cli-usage-limit.d.ts.map