mirror of
https://github.com/stablyai/orca.git
synced 2026-10-07 16:02:29 +00:00
Refresh only actual on-screen review cards and the selected visible review panel, with a single metadata owner per execution host. Open reviews refresh every 60 seconds when selected and 120 seconds otherwise. Settled merged reviews stop automatic refresh; pending merged checks continue, hidden rows stop, and work changes or stale re-exposure discover fresh state. Remove the polling setting. Preserve provider/SSH/runtime ownership, mixed-version cache behavior, failure backoff, bounded foreground admission, request coalescing, and mutation invalidation. Cover older persisted caches and unknown HEADs. Validated with 1,628 focused tests, typechecking, lint/code-quality gates, hidden Electron viewport checks, and parallel Codex/Claude Opus 5.5 adversarial reviews. Measurements and policy details are in PR #25788. Fixes #25746 Co-authored-by: ggbdpq <ggbdpq@gmail.com>
54 lines
1.8 KiB
TypeScript
54 lines
1.8 KiB
TypeScript
import type {
|
|
GitHubPRRefreshCandidate,
|
|
PRRefreshOutcome
|
|
} from '../../shared/github/pull-request-refresh-types'
|
|
import { lookupBackoffDelayMs } from '../source-control/hosted-review-refresh-pacing'
|
|
import { refreshIntervalForCandidate } from './pr-refresh-candidate-policy'
|
|
|
|
export class PRRefreshRetryState {
|
|
private readonly errorBackoff = new Map<string, { failures: number; retryAt: number }>()
|
|
private readonly manualRetryGates = new Map<string, number>()
|
|
|
|
get errorBackoffCount(): number {
|
|
return this.errorBackoff.size
|
|
}
|
|
|
|
reset(key: string): void {
|
|
this.errorBackoff.delete(key)
|
|
this.manualRetryGates.delete(key)
|
|
}
|
|
|
|
noteManualGate(key: string, outcome: PRRefreshOutcome): void {
|
|
if (outcome.kind === 'upstream-error' && outcome.retryDisabledUntil !== undefined) {
|
|
this.manualRetryGates.set(key, outcome.retryDisabledUntil)
|
|
} else {
|
|
this.manualRetryGates.delete(key)
|
|
}
|
|
}
|
|
|
|
manualGateUntil(key: string): number {
|
|
return this.manualRetryGates.get(key) ?? 0
|
|
}
|
|
|
|
nextVisibleErrorRetryAt(key: string, candidate: GitHubPRRefreshCandidate): number {
|
|
const failures = (this.errorBackoff.get(key)?.failures ?? 0) + 1
|
|
const interval = refreshIntervalForCandidate(candidate)
|
|
const retryAt =
|
|
Date.now() +
|
|
Math.max(lookupBackoffDelayMs(failures), Number.isFinite(interval) ? interval : 0)
|
|
this.errorBackoff.set(key, { failures, retryAt })
|
|
return retryAt
|
|
}
|
|
|
|
withErrorSchedule(outcome: PRRefreshOutcome, retryAt: number): PRRefreshOutcome {
|
|
if (outcome.kind !== 'upstream-error') {
|
|
return outcome
|
|
}
|
|
const cooldownUntil = outcome.retryDisabledUntil
|
|
return {
|
|
...outcome,
|
|
nextAutoRetryAt: cooldownUntil !== undefined ? Math.max(retryAt, cooldownUntil) : retryAt
|
|
}
|
|
}
|
|
}
|