mirror of
https://github.com/stablyai/orca.git
synced 2026-11-02 10:08:13 +00:00
Refresh visible reviews automatically and stop settled merged polling (#25788)
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>
This commit is contained in:
@@ -1,3 +1,6 @@
|
||||
import { runCoalescedProbe, type CoalescedProbes } from '../../../git/coalesced-probe'
|
||||
import { getSshGitProviderGeneration } from '../../../providers/ssh-git-dispatch'
|
||||
import { githubReadExecutionScope } from '../../github-read-execution-scope'
|
||||
import type { PRCheckDetail } from '../../../../shared/github/check-types'
|
||||
import { GITHUB_WORK_ITEMS_SSH_REMOTE_REQUIRED_MESSAGE } from '../../../../shared/work-items'
|
||||
import { ghExecFileAsync, acquire, release, type LocalGitExecOptions } from '../../gh-utils'
|
||||
@@ -120,7 +123,7 @@ export async function getPRChecksViaRestFallback(
|
||||
* Uses GitHub's combined GraphQL rollup so check runs and legacy commit statuses
|
||||
* arrive in one cached request; suite-only approval blockers are included too.
|
||||
*/
|
||||
export async function getPRChecks(
|
||||
async function readPRChecks(
|
||||
repoPath: string,
|
||||
prNumber: number,
|
||||
headSha?: string,
|
||||
@@ -231,3 +234,29 @@ export async function getPRChecks(
|
||||
throw err
|
||||
}
|
||||
}
|
||||
|
||||
const checksReads: CoalescedProbes<PRCheckDetail[]> = new Map()
|
||||
|
||||
export function getPRChecks(
|
||||
repoPath: string,
|
||||
prNumber: number,
|
||||
headSha?: string,
|
||||
prRepo?: GitHubApiRepository | null,
|
||||
options?: { noCache?: boolean },
|
||||
connectionId?: string | null,
|
||||
localGitOptions: LocalGitExecOptions = {}
|
||||
): Promise<PRCheckDetail[]> {
|
||||
const key = JSON.stringify([
|
||||
repoPath,
|
||||
prNumber,
|
||||
headSha ?? null,
|
||||
prRepo ?? null,
|
||||
Boolean(options?.noCache),
|
||||
connectionId ?? null,
|
||||
connectionId ? getSshGitProviderGeneration(connectionId) : null,
|
||||
githubReadExecutionScope(localGitOptions)
|
||||
])
|
||||
return runCoalescedProbe(checksReads, key, () =>
|
||||
readPRChecks(repoPath, prNumber, headSha, prRepo, options, connectionId, localGitOptions)
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { z } from 'zod'
|
||||
import { createHash } from 'node:crypto'
|
||||
import { githubReadExecutionScope as workItemSearchScope } from '../../github-read-execution-scope'
|
||||
export { githubReadExecutionScope as workItemSearchScope } from '../../github-read-execution-scope'
|
||||
import { BoundedMap } from '../../../../shared/bounded-map'
|
||||
import { runCoalescedProbe, type CoalescedProbes } from '../../../git/coalesced-probe'
|
||||
import { createGhRateLimitBlockedError } from '../../../git/gh-rate-limit-breaker'
|
||||
@@ -48,22 +49,6 @@ const responses = new BoundedMap<string, { at: number; value: unknown }>({
|
||||
sizeOf: (value, key) => Buffer.byteLength(key) + Buffer.byteLength(JSON.stringify(value))
|
||||
})
|
||||
|
||||
export function workItemSearchScope(
|
||||
options: GitHubRepoExecOptions,
|
||||
environment: NodeJS.ProcessEnv = options.env ?? process.env
|
||||
): string {
|
||||
// gh wrappers and credential selection can depend on cwd and the inherited environment.
|
||||
return createHash('sha256')
|
||||
.update(
|
||||
JSON.stringify([
|
||||
options,
|
||||
process.cwd(),
|
||||
Object.entries(environment).sort(([a], [b]) => a.localeCompare(b))
|
||||
])
|
||||
)
|
||||
.digest('hex')
|
||||
}
|
||||
|
||||
export function requestWorkItemSearch<T>(request: SearchRequest): Promise<SearchResponse<T>> {
|
||||
const environment = { ...(request.environment ?? request.options.env ?? process.env) }
|
||||
const scope = workItemSearchScope(request.options, environment)
|
||||
|
||||
@@ -1,9 +1,19 @@
|
||||
import { getRepoExecutionHostId } from '../../../../shared/execution-host'
|
||||
import {
|
||||
hostedReviewRepoScope,
|
||||
scopeGeneration
|
||||
} from '../../../source-control/hosted-review-scope-generations'
|
||||
import { runCoalescedProbe, type CoalescedProbes } from '../../../git/coalesced-probe'
|
||||
import { getSshGitProviderGeneration } from '../../../providers/ssh-git-dispatch'
|
||||
import { githubReadExecutionScope } from '../../github-read-execution-scope'
|
||||
import type { PRRefreshOutcome } from '../../../../shared/github/pull-request-refresh-types'
|
||||
import { acquire, release, ghRepoExecOptions, githubRepoContext } from '../../gh-utils'
|
||||
import { hostedReviewLocalGitOptionArgs, githubPRStackExecutionScope } from './../github-exec-scope'
|
||||
import type { GitHubPRBranchLookupOptions } from './pull-request-lookup-data'
|
||||
import { prRefreshUpstreamError } from './../gh-error-predicates'
|
||||
import { resolvePRForBranchOutcome } from './branch-lookup-resolution'
|
||||
const reads: CoalescedProbes<PRRefreshOutcome> = new Map()
|
||||
|
||||
export async function getPRForBranchOutcome(
|
||||
repoPath: string,
|
||||
branch: string,
|
||||
@@ -23,22 +33,41 @@ export async function getPRForBranchOutcome(
|
||||
const ghOptions = ghRepoExecOptions(context)
|
||||
const executionScope = githubPRStackExecutionScope(connectionId, localGitOptions)
|
||||
|
||||
await acquire()
|
||||
try {
|
||||
return await resolvePRForBranchOutcome({
|
||||
repoPath,
|
||||
branchName,
|
||||
linkedPRNumber,
|
||||
connectionId,
|
||||
fallbackPRNumber,
|
||||
options,
|
||||
localGitOptions,
|
||||
ghOptions,
|
||||
executionScope
|
||||
})
|
||||
} catch (err) {
|
||||
return prRefreshUpstreamError(err)
|
||||
} finally {
|
||||
release()
|
||||
}
|
||||
const key = JSON.stringify([
|
||||
executionScope,
|
||||
connectionId ? getSshGitProviderGeneration(connectionId) : null,
|
||||
repoPath,
|
||||
scopeGeneration(hostedReviewRepoScope(repoPath, getRepoExecutionHostId({ connectionId }))),
|
||||
branchName,
|
||||
linkedPRNumber ?? null,
|
||||
fallbackPRNumber ?? null,
|
||||
options.acceptMergedFallbackPR ?? false,
|
||||
options.currentHeadOid ?? null,
|
||||
githubReadExecutionScope(ghOptions)
|
||||
])
|
||||
return runCoalescedProbe(
|
||||
reads,
|
||||
key,
|
||||
async () => {
|
||||
await acquire()
|
||||
try {
|
||||
return await resolvePRForBranchOutcome({
|
||||
repoPath,
|
||||
branchName,
|
||||
linkedPRNumber,
|
||||
connectionId,
|
||||
fallbackPRNumber,
|
||||
options,
|
||||
localGitOptions,
|
||||
ghOptions,
|
||||
executionScope
|
||||
})
|
||||
} catch (err) {
|
||||
return prRefreshUpstreamError(err)
|
||||
} finally {
|
||||
release()
|
||||
}
|
||||
},
|
||||
2 * 60_000
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { invalidateReviewLookupsAfterPRMutation } from '../../pr-mutation-review-invalidation'
|
||||
import type { PRConflictSummary } from '../../../../shared/github/pull-request-types'
|
||||
import { getPRConflictSummary } from '../../conflict-summary'
|
||||
import { ghExecFileAsync, acquire, release, type LocalGitExecOptions } from '../../gh-utils'
|
||||
@@ -59,7 +60,7 @@ export async function mergePR(
|
||||
)
|
||||
release()
|
||||
concurrencySlotHeld = false
|
||||
return await mergeGitHubPRStack({
|
||||
const result = await mergeGitHubPRStack({
|
||||
repository: ownerRepo,
|
||||
prNumber,
|
||||
method,
|
||||
@@ -67,6 +68,10 @@ export async function mergePR(
|
||||
headSha: restData.headRefOid,
|
||||
ghOptions
|
||||
})
|
||||
if (result.ok) {
|
||||
invalidateReviewLookupsAfterPRMutation(repoPath, connectionId)
|
||||
}
|
||||
return result
|
||||
}
|
||||
const mergeBlocker = await getPRMergeBlocker(
|
||||
repoPath,
|
||||
@@ -89,6 +94,7 @@ export async function mergePR(
|
||||
...ghOptions,
|
||||
env: { ...process.env, GH_PROMPT_DISABLED: '1' }
|
||||
})
|
||||
invalidateReviewLookupsAfterPRMutation(repoPath, connectionId)
|
||||
return { ok: true }
|
||||
} catch (err) {
|
||||
const message =
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { invalidateReviewLookupsAfterPRMutation } from '../../pr-mutation-review-invalidation'
|
||||
import type { GitHubPRMergeMethod } from '../../../../shared/github/pull-request-types'
|
||||
import {
|
||||
ghExecFileAsync,
|
||||
@@ -165,13 +166,17 @@ export async function setPRAutoMerge(
|
||||
await acquire()
|
||||
try {
|
||||
if (enabled) {
|
||||
return await enablePRAutoMerge(
|
||||
const result = await enablePRAutoMerge(
|
||||
prNumber,
|
||||
method,
|
||||
ownerRepo,
|
||||
ghOptions,
|
||||
githubPRStackExecutionScope(connectionId, localGitOptions)
|
||||
)
|
||||
if (result.ok) {
|
||||
invalidateReviewLookupsAfterPRMutation(repoPath, connectionId)
|
||||
}
|
||||
return result
|
||||
}
|
||||
const args = ['pr', 'merge', String(prNumber), '--disable-auto']
|
||||
if (ownerRepo) {
|
||||
@@ -181,6 +186,7 @@ export async function setPRAutoMerge(
|
||||
...ghOptions,
|
||||
env: { ...process.env, GH_PROMPT_DISABLED: '1' }
|
||||
})
|
||||
invalidateReviewLookupsAfterPRMutation(repoPath, connectionId)
|
||||
return { ok: true }
|
||||
} catch (err) {
|
||||
const message =
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { invalidateReviewLookupsAfterPRMutation } from '../../pr-mutation-review-invalidation'
|
||||
import {
|
||||
ghExecFileAsync,
|
||||
acquire,
|
||||
@@ -35,6 +36,7 @@ export async function updatePRTitle(
|
||||
await ghExecFileAsync(args, {
|
||||
...ghOptions
|
||||
})
|
||||
invalidateReviewLookupsAfterPRMutation(repoPath, connectionId)
|
||||
return true
|
||||
} catch (err) {
|
||||
console.warn('updatePRTitle failed:', err)
|
||||
@@ -89,6 +91,7 @@ export async function updatePRDetails(
|
||||
],
|
||||
ghOptions
|
||||
)
|
||||
invalidateReviewLookupsAfterPRMutation(repoPath, connectionId)
|
||||
return { ok: true }
|
||||
} catch (err) {
|
||||
const message =
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { invalidateReviewLookupsAfterPRMutation } from '../../pr-mutation-review-invalidation'
|
||||
import {
|
||||
acquire,
|
||||
classifyPullRequestUpdateError,
|
||||
@@ -30,6 +31,7 @@ export async function markPRReadyForReview(
|
||||
['pr', 'ready', String(prNumber), '--repo', `${ownerRepo.owner}/${ownerRepo.repo}`],
|
||||
ghOptions
|
||||
)
|
||||
invalidateReviewLookupsAfterPRMutation(repoPath, connectionId)
|
||||
return { ok: true }
|
||||
} catch (err) {
|
||||
const message =
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { invalidateReviewLookupsAfterPRMutation } from '../../pr-mutation-review-invalidation'
|
||||
import { ghExecFileAsync, acquire, release, type LocalGitExecOptions } from '../../gh-utils'
|
||||
import { resolveGitHubRepoExecution, type GitHubApiRepository } from '../../github-api-repository'
|
||||
export async function requestPRReviewers(
|
||||
@@ -31,6 +32,7 @@ export async function requestPRReviewers(
|
||||
...ghOptions,
|
||||
env: { ...process.env, GH_PROMPT_DISABLED: '1' }
|
||||
})
|
||||
invalidateReviewLookupsAfterPRMutation(repoPath, connectionId)
|
||||
return { ok: true }
|
||||
} catch (err) {
|
||||
const message =
|
||||
@@ -72,6 +74,7 @@ export async function removePRReviewers(
|
||||
...ghOptions,
|
||||
env: { ...process.env, GH_PROMPT_DISABLED: '1' }
|
||||
})
|
||||
invalidateReviewLookupsAfterPRMutation(repoPath, connectionId)
|
||||
return { ok: true }
|
||||
} catch (err) {
|
||||
const message =
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { invalidateReviewLookupsAfterPRMutation } from '../../pr-mutation-review-invalidation'
|
||||
import type { GitHubPullRequestStateUpdate } from '../../../../shared/issue-mutation-types'
|
||||
import {
|
||||
ghExecFileAsync,
|
||||
@@ -35,6 +36,7 @@ export async function updatePRState(
|
||||
...ghOptions
|
||||
}
|
||||
)
|
||||
invalidateReviewLookupsAfterPRMutation(repoPath, connectionId)
|
||||
return { ok: true }
|
||||
} catch (err) {
|
||||
const message =
|
||||
|
||||
Reference in New Issue
Block a user