mirror of
https://github.com/stablyai/orca.git
synced 2026-11-14 18:07:45 +00:00
* Keep PR check status tied to complete observations * Correct checks presentation types and count import * Preserve localized empty states and declared runtime support * Correct discovery fixture stack types * Omit empty failed-check subtitles * Preserve check identity through refresh and row publication * Withhold row identity for malformed check errors * Limit local capability wakes to local task sources * Correct PR check regression fixtures * Preserve browser APIs in PR check regression fixture * Settle PR check adoption after failed detail reads * Use the parsed PR check detail type during refresh * Preserve PR metadata across checks discovery and committed query lifetimes * Keep complete checks proof separate from legacy publication policy * Keep PR lookup fixtures independent from complete checks collection * Keep complete-check fixtures within lint contracts * test: align PR recordings with source check discovery
305 lines
10 KiB
TypeScript
305 lines
10 KiB
TypeScript
import {
|
|
GITHUB_CONNECTION_CHANGED,
|
|
githubOperationResponse
|
|
} from '../transport/github-target-invocation'
|
|
import { captureMobilePRCheckContract } from '../transport/pr-check-observation-contract'
|
|
import { acceptPRCheckObservation } from '../../../src/shared/github/complete-pr-check-owner'
|
|
import type { RpcClient } from '../transport/rpc-client'
|
|
import { withGitHubTargetOperation } from '../tasks/github-work-item-operation'
|
|
import { requireGitHubWorkItemTargetDetails } from '../../../src/shared/github/work-item-target'
|
|
import type { PRCheckDetail, PRCheckRunDetails } from '../../../src/shared/github/check-types'
|
|
import type {
|
|
GitHubAssignableUser,
|
|
GitHubOwnerRepo,
|
|
PRInfo
|
|
} from '../../../src/shared/github/pull-request-types'
|
|
import type { GitHubWorkItemDetails } from '../../../src/shared/github/work-item-types'
|
|
import type { HostedReviewInfo } from '../../../src/shared/source-control/hosted-review/hosted-review-contracts'
|
|
import { refusedRpcMessageOrFallback } from '../transport/rpc-refusal-message'
|
|
import type { RpcResponse } from '../transport/types'
|
|
import { mobileRepoSelectorFromWorktreeId } from '../source-control/mobile-pr-create'
|
|
import type { GitHubPrForBranchOutcome } from './github-pr-read-reply-schema'
|
|
import {
|
|
githubPrAssignableUsersRead,
|
|
githubPrCheckDetailsRead,
|
|
githubPrChecksRead,
|
|
githubPrForBranchRead,
|
|
githubPrRepoSlugRead,
|
|
githubPrWorkItemDetailsRead,
|
|
hostedReviewBranchLookupRead
|
|
} from './github-pr-read-operations'
|
|
import type { GitHubPrSettleableOperation } from './github-pr-mutation-outcome'
|
|
import { githubPrRequestParams, type GitHubPrRepoSlug } from './github-pr-repo-slug'
|
|
import type { RpcOperationSender } from '../transport/rpc-operation-sender'
|
|
import { translate } from '../i18n/mobile-i18n'
|
|
|
|
export {
|
|
buildGithubPrParams,
|
|
githubPrRepoSlugParam,
|
|
type GitHubPrRepoSlug
|
|
} from './github-pr-repo-slug'
|
|
|
|
export type GitHubPrReadOutcome<T> = { ok: true; result: T } | { ok: false; error: string }
|
|
|
|
// Refusals without text use method fallback; decode/in-band upstream errors preserve their own text.
|
|
function githubPrFailureText(reply: RpcResponse, error: unknown, fallback: string): string {
|
|
if (!reply.ok) {
|
|
return refusedRpcMessageOrFallback(error, fallback)
|
|
}
|
|
return error instanceof Error ? error.message : fallback
|
|
}
|
|
|
|
async function settleGithubPrRead<Value>(
|
|
read: GitHubPrSettleableOperation<Value>,
|
|
send: () => Promise<RpcResponse>
|
|
): Promise<GitHubPrReadOutcome<Value>> {
|
|
const fallback = translate('session.githubPrRpc.requestFailed', 'Request failed: {{method}}', {
|
|
method: read.operation.method
|
|
})
|
|
let reply: RpcResponse
|
|
try {
|
|
reply = await send()
|
|
} catch (error) {
|
|
// A transport drop surfaces its own message verbatim, empty included.
|
|
return { ok: false, error: error instanceof Error ? error.message : fallback }
|
|
}
|
|
try {
|
|
return { ok: true, result: read.interpret(reply) }
|
|
} catch (error) {
|
|
return { ok: false, error: githubPrFailureText(reply, error, fallback) }
|
|
}
|
|
}
|
|
|
|
// Gate the PR icon on a GitHub remote independently of whether this branch has an open PR.
|
|
export function fetchGithubRepoSlug(
|
|
client: RpcOperationSender,
|
|
worktreeId: string
|
|
): Promise<GitHubPrReadOutcome<GitHubPrRepoSlug | null>> {
|
|
return settleGithubPrRead(githubPrRepoSlugRead, () =>
|
|
githubPrRepoSlugRead.request(
|
|
client,
|
|
githubPrRequestParams(githubPrRepoSlugRead.operation.method, worktreeId, {})
|
|
)
|
|
)
|
|
}
|
|
|
|
export function fetchHostedReviewForBranch(
|
|
client: RpcOperationSender,
|
|
worktreeId: string,
|
|
args: { branch: string; linkedGitHubPR?: number | null }
|
|
): Promise<GitHubPrReadOutcome<HostedReviewInfo | null>> {
|
|
return settleGithubPrRead(hostedReviewBranchLookupRead, () =>
|
|
hostedReviewBranchLookupRead.request(client, {
|
|
repo: mobileRepoSelectorFromWorktreeId(worktreeId),
|
|
branch: args.branch,
|
|
linkedGitHubPR: args.linkedGitHubPR ?? null,
|
|
// Why: the mobile PR sidebar is only ever open on the selected worktree,
|
|
// so it belongs in the host's fast re-check tier (#11532).
|
|
active: true
|
|
})
|
|
)
|
|
}
|
|
|
|
// Raise decoded upstream-error here so the sidebar retains host text; it is not a protocol decode failure.
|
|
export async function fetchPRForBranch(
|
|
client: RpcOperationSender,
|
|
worktreeId: string,
|
|
args: {
|
|
branch: string
|
|
linkedPRNumber?: number | null
|
|
fallbackPRNumber?: number | null
|
|
currentHeadOid?: string | null
|
|
refresh?: boolean
|
|
}
|
|
): Promise<GitHubPrReadOutcome<PRInfo | null>> {
|
|
return observe(true, args.refresh === true)
|
|
async function observe(
|
|
adopt: boolean,
|
|
refresh = false
|
|
): Promise<GitHubPrReadOutcome<PRInfo | null>> {
|
|
const contract = await captureMobilePRCheckContract(
|
|
client,
|
|
{
|
|
repo: mobileRepoSelectorFromWorktreeId(worktreeId)
|
|
},
|
|
refresh
|
|
)
|
|
try {
|
|
let sourceMismatch = false
|
|
const result = await settleGithubPrRead(
|
|
{
|
|
operation: githubPrForBranchRead.operation,
|
|
interpret: (reply) =>
|
|
resolveGithubPrForBranchOutcome(githubPrForBranchRead.interpret(reply))
|
|
},
|
|
async () => {
|
|
const reply = await githubPrForBranchRead.request(
|
|
contract.client ?? client,
|
|
githubPrRequestParams(githubPrForBranchRead.operation.method, worktreeId, {
|
|
branch: args.branch,
|
|
linkedPRNumber: args.linkedPRNumber ?? null,
|
|
fallbackPRNumber: args.fallbackPRNumber ?? null,
|
|
currentHeadOid: args.currentHeadOid ?? null
|
|
}),
|
|
contract.options
|
|
)
|
|
sourceMismatch =
|
|
!reply.ok &&
|
|
['runtime_source_mismatch', 'runtime_source_unverifiable'].includes(reply.error.code)
|
|
return githubOperationResponse(reply)
|
|
}
|
|
)
|
|
if (adopt && sourceMismatch && contract.routeCurrent()) {
|
|
return observe(false, true)
|
|
}
|
|
if (!contract.current()) {
|
|
if (adopt && !contract.complete && contract.routeCurrent()) {
|
|
return observe(false)
|
|
}
|
|
return { ok: false, error: GITHUB_CONNECTION_CHANGED }
|
|
}
|
|
if (result.ok && result.result && contract.complete) {
|
|
return {
|
|
ok: true,
|
|
result: acceptPRCheckObservation(result.result, null, {
|
|
scope: contract.scope!,
|
|
complete: true,
|
|
current: contract.current()
|
|
})!
|
|
}
|
|
}
|
|
return result
|
|
} finally {
|
|
contract.retire()
|
|
}
|
|
}
|
|
}
|
|
|
|
function resolveGithubPrForBranchOutcome(outcome: GitHubPrForBranchOutcome | null): PRInfo | null {
|
|
if (outcome === null) {
|
|
return null
|
|
}
|
|
if (outcome.kind === 'upstream-error') {
|
|
throw new Error(outcome.message)
|
|
}
|
|
return outcome.kind === 'found' ? outcome.pr : null
|
|
}
|
|
|
|
export async function fetchWorkItemDetails(
|
|
client: RpcClient,
|
|
worktreeId: string,
|
|
args: { prNumber: number; prRepo?: GitHubPrRepoSlug | null; refresh?: boolean }
|
|
): Promise<GitHubPrReadOutcome<GitHubWorkItemDetails | null>> {
|
|
const params = githubPrRequestParams(githubPrWorkItemDetailsRead.operation.method, worktreeId, {
|
|
number: args.prNumber,
|
|
type: 'pr'
|
|
})
|
|
const outcome = await settleGithubPrRead(githubPrWorkItemDetailsRead, () =>
|
|
args.prRepo
|
|
? withGitHubTargetOperation({
|
|
client,
|
|
params: { repo: mobileRepoSelectorFromWorktreeId(worktreeId) },
|
|
ownerRepo: args.prRepo,
|
|
read: true,
|
|
canonical: args.refresh === true,
|
|
send: (selected, exact, options) =>
|
|
githubPrWorkItemDetailsRead.request(selected, { ...params, ...exact }, options)
|
|
})
|
|
: githubPrWorkItemDetailsRead.request(client, params)
|
|
)
|
|
if (outcome.ok && args.prRepo) {
|
|
try {
|
|
return {
|
|
ok: true,
|
|
result: requireGitHubWorkItemTargetDetails(outcome.result, {
|
|
repository: args.prRepo,
|
|
type: 'pr',
|
|
number: args.prNumber
|
|
})
|
|
}
|
|
} catch (error) {
|
|
return {
|
|
ok: false,
|
|
error: error instanceof Error ? error.message : 'Unable to load pull request details.'
|
|
}
|
|
}
|
|
}
|
|
return outcome
|
|
}
|
|
|
|
export function fetchPRChecks(
|
|
client: RpcOperationSender,
|
|
worktreeId: string,
|
|
args: { prNumber: number; headSha?: string | null; prRepo?: GitHubPrRepoSlug | null }
|
|
): Promise<GitHubPrReadOutcome<PRCheckDetail[]>> {
|
|
return settleGithubPrRead(githubPrChecksRead, () =>
|
|
githubPrChecksRead.request(
|
|
client,
|
|
githubPrRequestParams(
|
|
githubPrChecksRead.operation.method,
|
|
worktreeId,
|
|
{ prNumber: args.prNumber },
|
|
{ prRepo: args.prRepo, headSha: args.headSha }
|
|
)
|
|
)
|
|
)
|
|
}
|
|
|
|
export function fetchPRCheckDetails(
|
|
client: RpcOperationSender,
|
|
worktreeId: string,
|
|
args: {
|
|
checkRunId?: number
|
|
workflowRunId?: number
|
|
checkName?: string
|
|
url?: string | null
|
|
prRepo?: GitHubPrRepoSlug | null
|
|
}
|
|
): Promise<GitHubPrReadOutcome<PRCheckRunDetails | null>> {
|
|
const params: Record<string, unknown> = {}
|
|
if (args.checkRunId !== undefined) {
|
|
params.checkRunId = args.checkRunId
|
|
}
|
|
if (args.workflowRunId !== undefined) {
|
|
params.workflowRunId = args.workflowRunId
|
|
}
|
|
if (args.checkName !== undefined) {
|
|
params.checkName = args.checkName
|
|
}
|
|
if (args.url !== undefined) {
|
|
params.url = args.url
|
|
}
|
|
return settleGithubPrRead(githubPrCheckDetailsRead, () =>
|
|
githubPrCheckDetailsRead.request(
|
|
client,
|
|
githubPrRequestParams(githubPrCheckDetailsRead.operation.method, worktreeId, params, {
|
|
prRepo: args.prRepo
|
|
})
|
|
)
|
|
)
|
|
}
|
|
|
|
export function fetchAssignableUsers(
|
|
client: RpcClient,
|
|
worktreeId: string,
|
|
prRepo: GitHubOwnerRepo | null
|
|
): Promise<GitHubPrReadOutcome<GitHubAssignableUser[]>> {
|
|
return settleGithubPrRead(githubPrAssignableUsersRead, () => {
|
|
if (!prRepo) {
|
|
throw new Error('Unable to identify this pull request. Open it again.')
|
|
}
|
|
const params = githubPrRequestParams(
|
|
githubPrAssignableUsersRead.operation.method,
|
|
worktreeId,
|
|
{}
|
|
)
|
|
return withGitHubTargetOperation({
|
|
client,
|
|
params,
|
|
ownerRepo: prRepo,
|
|
send: (selected, exact, options) =>
|
|
githubPrAssignableUsersRead.request(selected, { ...params, ...exact }, options)
|
|
})
|
|
})
|
|
}
|