Files
orca/src/preload/gitlab.ts
T
Neil 8c3551bde3 fix(wsl): background Git, GitHub and GitLab reads never start a stopped distro; explicit opens may (E9) (#35410)
* fix(wsl): keep background git and GitHub reads from booting a stopped distro

Git history, diff and status-follower reads, GitHub issue/work-item/check
polls, chat link existence checks, the composer's create-base prefetch,
the worktree-root fan-out and the Settings/Automations orca.yaml previews
now refuse or skip a stopped WSL distro instead of starting it (E9).

* fix(wsl): explicit opens may start a stopped distro; more polls gated (E9)

Folds the parallel G1-G6 work into this branch and splits background
reads from user acts:

- An explicit open runs as a user act for its distro and may boot it:
  showing a diff tab or its Retry, showing Commits or its Refresh,
  opening the checks panel or entering its tab, checks Refresh, a check
  action, and linking or creating a PR. Main takes a wakeWslDistro flag
  on git:diff, git:branchDiff, git:commitDiff, git:history and
  gh:prChecks and runs those reads under runAsWslUserAct.
- Polls, timers, refreshes after Git actions, prefetches and fan-outs
  stay background and are refused while the distro is stopped.
- Also gated: the hosted-review card poll for every provider, the GitLab
  checks-panel poll (background flag), and the composer's open-time
  orca.yaml check (Create re-checks in the foreground).
- Pollers keep the data they already show on a refusal: the issue on a
  card, the checks list, an open diff and loaded submodule children, and
  stop logging it.

* docs(wsl): note that a hosted-review poll's stopped-distro refusal is still logged (E9)

* fix(wsl): Settings re-asks orca.yaml once a stopped distro runs; drop link-check gating (E9)

- Link existence checks (shell:pathsExist / pathExists) are no longer
  gated here: answering "missing" cached chat and terminal links as
  absent and toasted "File not found" on markdown link clicks. The
  pathsExist refusal lives in its own PR, which throws instead.
- Settings no longer sits on "checking" forever: a repo whose distro
  refused the background check is re-asked on window focus, on
  visibility, and after 10 s, until it answers. An unverifiable distro is
  now marked (wsl-distro-unverifiable) and treated as unread too, instead
  of being stored as "no orca.yaml".
- The sidebar issue poll and the Tasks list and count refuse with "Open a
  terminal in it to start it", since their Retry is itself a background
  read; the checks panel keeps "Retry", whose Refresh is a user act.

* fix(wsl): Tasks Refresh, interactive review refresh and manual PR lookups may start a stopped distro (E9)

- Tasks: a Refresh, Retry or search, or Tasks opened on a single repo,
  passes wakeWslDistro through gh:listWorkItems and gh:countWorkItems
  (IPC only; the RPC twins and web routes drop it). A multi-repo open
  stays background. Their refusal says "Retry" again; only the sidebar
  issue poll says "Open a terminal".
- hostedReview:forBranch wakes for an interactive request (the checks
  panel's manual Refresh), so GitLab, Bitbucket, Azure DevOps and Gitea
  reviews with nothing cached can load.
- gh:prForBranch, the renderer's fallback when refreshPRNow is missing,
  is gated like the other card polls; a manual lookup wakes, matching
  refreshPRNow's manual-only rule. The fallback call moves into its own
  module, built from the refresh candidate.

* fix(wsl): only an explicit Refresh starts a stopped distro for review status (E9)

hostedReview:forBranch woke on admissionTier 'interactive', but the
visible-review scheduler sends 'interactive' for the selected card on a
timer, so that poll restarted a stopped distro every interval. The wake
is now an explicit wakeWslDistro flag (desktop IPC only). The card
refresh helper sets it, since every caller follows a user act (the
checks panel's manual Refresh, or a review created, linked or edited),
and the manual Refresh also wakes its pre-refresh git status.
admissionTier is back to priority only.

* fix(wsl): an explicit review Refresh never joins a refused poll (E9)

fetchHostedReviewForBranch let a forced card Refresh join an in-flight
forced scheduler poll, since the join only compared force. On a stopped
WSL distro that poll is refused, so the click got the refusal. The
in-flight entry now records the wake, and a waking request joins only a
waking flight; a poll may still join an explicit Refresh.
2026-10-28 18:16:49 -07:00

166 lines
4.7 KiB
TypeScript

import { ipcRenderer } from 'electron'
import type { TaskSourceContext } from '../shared/task-source-context'
type GitLabRepoSelectorArgs = {
repoPath: string
repoId?: string | null
sourceContext?: TaskSourceContext | null
}
export const glApi = {
viewer: () => ipcRenderer.invoke('gitlab:viewer'),
diagnoseAuth: () => ipcRenderer.invoke('gitlab:diagnoseAuth'),
rateLimit: (args?: { force?: boolean; host?: string | null }) =>
ipcRenderer.invoke('gitlab:rateLimit', args),
projectSlug: (args: GitLabRepoSelectorArgs) => ipcRenderer.invoke('gitlab:projectSlug', args),
mrForBranch: (
args: GitLabRepoSelectorArgs & {
branch: string
linkedMRIid?: number | null
}
) => ipcRenderer.invoke('gitlab:mrForBranch', args),
mr: (args: GitLabRepoSelectorArgs & { iid: number }) => ipcRenderer.invoke('gitlab:mr', args),
listMRs: (
args: GitLabRepoSelectorArgs & {
state?: 'opened' | 'merged' | 'closed' | 'all'
page?: number
perPage?: number
query?: string
}
) => ipcRenderer.invoke('gitlab:listMRs', args),
listWorkItems: (
args: GitLabRepoSelectorArgs & {
state?: 'opened' | 'merged' | 'closed' | 'all'
page?: number
perPage?: number
query?: string
}
) => ipcRenderer.invoke('gitlab:listWorkItems', args),
issue: (args: GitLabRepoSelectorArgs & { number: number }) =>
ipcRenderer.invoke('gitlab:issue', args),
listIssues: (
args: GitLabRepoSelectorArgs & {
state?: 'opened' | 'closed' | 'all'
assignee?: string
limit?: number
page?: number
}
) => ipcRenderer.invoke('gitlab:listIssues', args),
createIssue: (
args: GitLabRepoSelectorArgs & {
title: string
body: string
}
): Promise<{ ok: true; number: number; url: string } | { ok: false; error: string }> =>
ipcRenderer.invoke('gitlab:createIssue', args),
updateIssue: (
args: GitLabRepoSelectorArgs & {
number: number
updates: unknown
}
): Promise<{ ok: true } | { ok: false; error: string }> =>
ipcRenderer.invoke('gitlab:updateIssue', args),
addIssueComment: (args: GitLabRepoSelectorArgs & { number: number; body: string }) =>
ipcRenderer.invoke('gitlab:addIssueComment', args),
listLabels: (args: GitLabRepoSelectorArgs): Promise<string[]> =>
ipcRenderer.invoke('gitlab:listLabels', args),
listAssignableUsers: (args: GitLabRepoSelectorArgs) =>
ipcRenderer.invoke('gitlab:listAssignableUsers', args),
todos: (args: GitLabRepoSelectorArgs) => ipcRenderer.invoke('gitlab:todos', args),
workItemDetails: (
args: GitLabRepoSelectorArgs & {
iid: number
type: 'issue' | 'mr'
includeReviewDetails?: boolean
background?: boolean
}
) => ipcRenderer.invoke('gitlab:workItemDetails', args),
closeMR: (
args: GitLabRepoSelectorArgs & {
iid: number
}
): Promise<{ ok: true } | { ok: false; error: string }> =>
ipcRenderer.invoke('gitlab:closeMR', args),
reopenMR: (
args: GitLabRepoSelectorArgs & {
iid: number
}
): Promise<{ ok: true } | { ok: false; error: string }> =>
ipcRenderer.invoke('gitlab:reopenMR', args),
mergeMR: (
args: GitLabRepoSelectorArgs & {
iid: number
method?: 'merge' | 'squash' | 'rebase'
}
): Promise<{ ok: true } | { ok: false; error: string }> =>
ipcRenderer.invoke('gitlab:mergeMR', args),
updateMR: (
args: GitLabRepoSelectorArgs & {
iid: number
updates: unknown
}
): Promise<{ ok: true } | { ok: false; error: string }> =>
ipcRenderer.invoke('gitlab:updateMR', args),
updateMRReviewers: (
args: GitLabRepoSelectorArgs & {
iid: number
reviewerIds: number[]
projectRef?: unknown
}
) => ipcRenderer.invoke('gitlab:updateMRReviewers', args),
addMRComment: (args: GitLabRepoSelectorArgs & { iid: number; body: string }) =>
ipcRenderer.invoke('gitlab:addMRComment', args),
addMRInlineComment: (
args: GitLabRepoSelectorArgs & {
iid: number
input: unknown
projectRef?: unknown
}
) => ipcRenderer.invoke('gitlab:addMRInlineComment', args),
resolveMRDiscussion: (
args: GitLabRepoSelectorArgs & {
iid: number
discussionId: string
resolved: boolean
}
) => ipcRenderer.invoke('gitlab:resolveMRDiscussion', args),
jobTrace: (
args: GitLabRepoSelectorArgs & { jobId: number; projectRef?: unknown; logExcerpt?: boolean }
) => ipcRenderer.invoke('gitlab:jobTrace', args),
retryJob: (args: GitLabRepoSelectorArgs & { jobId: number; projectRef?: unknown }) =>
ipcRenderer.invoke('gitlab:retryJob', args),
workItemByPath: (
args: GitLabRepoSelectorArgs & {
host: string
path: string
iid: number
type: 'issue' | 'mr'
}
) => ipcRenderer.invoke('gitlab:workItemByPath', args)
}