Show host scope for provider API budgets

Co-authored-by: Orca <help@stably.ai>
This commit is contained in:
Jinwoo-H
2026-06-12 20:48:18 -07:00
co-authored by Orca
parent 88d71b9950
commit 44f61b1aee
6 changed files with 101 additions and 3 deletions
@@ -221,6 +221,7 @@ Done means: reviewers and future agents can understand the vision, implementatio
- [x] 2026-06-12: Threaded GitHub Tasks row-level mutations through `TaskSourceContext` for issue state, assignees, reviewer requests/removals, PR merge/auto-merge, and new issue creation/follow-up reads; verified with focused GitHub/TaskPage tests, focused oxlint, `pnpm run typecheck`, and `git diff --check`.
- [x] 2026-06-12: Added provider account-scope diagnostics to GitHub/GitLab CLI integration cards so code-host credential checks show whether they belong to the local desktop client or selected remote server, matching the existing Linear/Jira ownership copy; verified with focused integration-card scope tests and oxlint.
- [x] 2026-06-12: Extended task-detail navigation state to GitLab and fixed Linear replay so saved `TaskSourceContext` is restored through back/forward and direct detail reopens; GitLab detail history keys now include source scope so identical MRs from different hosts stay distinct. Verified with focused UI/history/static-boundary tests and oxlint.
- [x] 2026-06-12: Added host-scoped budget diagnostics to the GitHub and GitLab API Budget settings panels so rate-limit views explicitly say whether they are fetched from the local desktop CLI or selected remote-server CLI; verified with focused provider scope/panel render tests and oxlint.
- [x] 2026-06-12: Updated PR #5071 with the current verified scope, evidence docs, validation commands, explicit out-of-scope cloud/onboarding items, and remaining provider/version-skew risk.
- [x] 2026-06-12: Added load-time backfill for legacy automations and automation runs that predate `runContext`/`sourceContext`, deriving explicit project host and provider source contexts from the saved repo id/path; verified with focused persistence migration coverage, automation service/precheck tests, daemon/settings merge-repair tests, and full `pnpm run typecheck`.
- [x] 2026-06-12: Repaired the merge-repair lint gate without disabling max-lines by extracting automation run-target/usage modules and settings subcomponents, fixed stale source-context hook dependencies, and tightened GitHub PR checks cache ownership so `noCache` bypasses cache/in-flight requests while head-specific checks are reused during refresh events; verified targeted oxlint, focused automation/settings/GitHub cache tests (10 files / 157 tests), `github-checks-cache.test.ts`, `git diff --check`, and full `pnpm run typecheck`.
@@ -5,6 +5,7 @@ import { installWindowVisibilityInterval } from '@/lib/window-visibility-interva
import { useAppStore } from '@/store'
import { callRuntimeRpc, getActiveRuntimeTarget } from '@/runtime/runtime-rpc-client'
import type { GetRateLimitResult, GitHubRateLimitSnapshot } from '../../../../shared/types'
import { getProviderRateLimitScope } from '@/components/settings/provider-account-scope'
import { translate } from '@/i18n/i18n'
const REFRESH_INTERVAL_MS = 60_000
@@ -169,6 +170,8 @@ function GitHubRateLimitRows({
export function GitHubRateLimitPanel({ className }: { className?: string }): React.JSX.Element {
const { snapshot, hasError, isFetching, refresh } = useGitHubRateLimitSnapshot()
const settings = useAppStore((s) => s.settings)
const budgetScope = getProviderRateLimitScope(settings, 'GitHub')
return (
<div className={cn('space-y-3 rounded-md border border-border/60 p-3', className)}>
@@ -187,6 +190,16 @@ export function GitHubRateLimitPanel({ className }: { className?: string }): Rea
'Orca uses REST, Search, and GraphQL through the GitHub CLI.'
)}
</p>
<p className="text-xs text-muted-foreground">
<span className="font-medium text-foreground">
{translate(
'auto.components.github.github.rate.limit.display.budget_scope',
'Budget scope: {{value0}}',
{ value0: budgetScope.label }
)}
</span>{' '}
{budgetScope.description}
</p>
</div>
<button
type="button"
@@ -6,6 +6,7 @@ import { installWindowVisibilityInterval } from '@/lib/window-visibility-interva
import { useAppStore } from '@/store'
import { callRuntimeRpc, getActiveRuntimeTarget } from '@/runtime/runtime-rpc-client'
import type { GetGitLabRateLimitResult, GitLabRateLimitSnapshot } from '../../../../shared/types'
import { getProviderRateLimitScope } from '@/components/settings/provider-account-scope'
import { translate } from '@/i18n/i18n'
const REFRESH_INTERVAL_MS = 60_000
@@ -146,6 +147,8 @@ function GitLabRateLimitRows({
export function GitLabRateLimitPanel({ className }: { className?: string }): React.JSX.Element {
const { snapshot, hasError, isFetching, refresh } = useGitLabRateLimitSnapshot()
const settings = useAppStore((s) => s.settings)
const budgetScope = getProviderRateLimitScope(settings, 'GitLab')
return (
<div className={cn('space-y-3 rounded-md border border-border/60 p-3', className)}>
@@ -164,6 +167,16 @@ export function GitLabRateLimitPanel({ className }: { className?: string }): Rea
'Orca uses REST through the GitLab CLI.'
)}
</p>
<p className="text-xs text-muted-foreground">
<span className="font-medium text-foreground">
{translate(
'auto.components.gitlab.gitlab.rate.limit.display.budget_scope',
'Budget scope: {{value0}}',
{ value0: budgetScope.label }
)}
</span>{' '}
{budgetScope.description}
</p>
</div>
<Button
type="button"
@@ -1,5 +1,5 @@
import { describe, expect, it } from 'vitest'
import { getProviderAccountScope } from './provider-account-scope'
import { getProviderAccountScope, getProviderRateLimitScope } from './provider-account-scope'
describe('getProviderAccountScope', () => {
it('describes provider accounts as client-owned without an active runtime', () => {
@@ -17,4 +17,15 @@ describe('getProviderAccountScope', () => {
'Credentials and account checks for this provider are owned by this remote server.'
})
})
it('describes provider API budgets as host-scoped', () => {
expect(getProviderRateLimitScope({ activeRuntimeEnvironmentId: null }, 'GitHub')).toEqual({
label: 'Local Mac',
description: 'GitHub API budget is fetched from the CLI on this desktop client.'
})
expect(getProviderRateLimitScope({ activeRuntimeEnvironmentId: ' env-1 ' }, 'GitLab')).toEqual({
label: 'Remote server: env-1',
description: 'GitLab API budget is fetched from the CLI on this remote server.'
})
})
})
@@ -5,6 +5,11 @@ export type ProviderAccountScope = {
description: string
}
export type ProviderRateLimitScope = {
label: string
description: string
}
export function getProviderAccountScope(
settings: Pick<GlobalSettings, 'activeRuntimeEnvironmentId'> | null | undefined
): ProviderAccountScope {
@@ -12,11 +17,30 @@ export function getProviderAccountScope(
if (runtimeId) {
return {
label: `Remote server: ${runtimeId}`,
description: 'Credentials and account checks for this provider are owned by this remote server.'
description:
'Credentials and account checks for this provider are owned by this remote server.'
}
}
return {
label: 'Local Mac',
description: 'Credentials and account checks for this provider are owned by this desktop client.'
description:
'Credentials and account checks for this provider are owned by this desktop client.'
}
}
export function getProviderRateLimitScope(
settings: Pick<GlobalSettings, 'activeRuntimeEnvironmentId'> | null | undefined,
providerLabel: string
): ProviderRateLimitScope {
const runtimeId = settings?.activeRuntimeEnvironmentId?.trim()
if (runtimeId) {
return {
label: `Remote server: ${runtimeId}`,
description: `${providerLabel} API budget is fetched from the CLI on this remote server.`
}
}
return {
label: 'Local Mac',
description: `${providerLabel} API budget is fetched from the CLI on this desktop client.`
}
}
@@ -0,0 +1,36 @@
import { renderToStaticMarkup } from 'react-dom/server'
import { describe, expect, it, vi } from 'vitest'
import { GitHubRateLimitPanel } from '@/components/github/github-rate-limit-display'
import { GitLabRateLimitPanel } from '@/components/gitlab/gitlab-rate-limit-display'
type StoreState = {
settings: { activeRuntimeEnvironmentId: string | null }
}
const mocks = vi.hoisted(() => ({
store: { current: { settings: { activeRuntimeEnvironmentId: null } } as StoreState }
}))
vi.mock('@/store', () => ({
useAppStore: (selector: (state: StoreState) => unknown) => selector(mocks.store.current)
}))
describe('provider rate-limit panels account scope', () => {
it('shows the local host scope for GitHub API budget', () => {
mocks.store.current = { settings: { activeRuntimeEnvironmentId: null } }
const markup = renderToStaticMarkup(<GitHubRateLimitPanel />)
expect(markup).toContain('Budget scope: Local Mac')
expect(markup).toContain('GitHub API budget is fetched from the CLI on this desktop client.')
})
it('shows the remote server scope for GitLab API budget', () => {
mocks.store.current = { settings: { activeRuntimeEnvironmentId: 'runtime-1' } }
const markup = renderToStaticMarkup(<GitLabRateLimitPanel />)
expect(markup).toContain('Budget scope: Remote server: runtime-1')
expect(markup).toContain('GitLab API budget is fetched from the CLI on this remote server.')
})
})