Preserve PR generation host ownership

Co-authored-by: Orca <help@stably.ai>
This commit is contained in:
Jinwoo-H
2026-06-13 01:29:57 -07:00
co-authored by Orca
parent c73587aecd
commit 64b726f33d
3 changed files with 51 additions and 13 deletions
@@ -284,3 +284,4 @@ Done means: reviewers and future agents can understand the vision, implementatio
- [x] 2026-06-13: Extended Tasks source availability reasons so missing provider auth, unavailable source tooling, and unsupported providers can surface as distinct source-host states instead of generic connection/version failures. The checklist row remains partial until runtime/provider preflight feeds these reasons end to end. Verified with focused source-summary tests and targeted oxlint.
- [x] 2026-06-13: Fed desktop-owned GitHub/GitLab preflight into Tasks source availability for local and SSH project sources, so missing `gh`/`glab` or missing CLI auth disables the affected provider with source-tool/auth-specific copy. Runtime-owned sources intentionally skip desktop preflight and continue to rely on runtime capability/fetch ownership. Verified with provider availability tests, Tasks source summary tests, TaskPage source-boundary tests, targeted oxlint, and full `pnpm run typecheck`.
- [x] 2026-06-13: Electron-verified a local project with local GitHub credentials: added the local Orca worktree to the running dev app, confirmed `gh` auth for `Jinwoo-H`, fetched 10 GitHub work items through explicit `TaskSourceContext { provider: github, hostId: local }`, verified the local source-scoped cache key, and captured the rendered Tasks page showing GitHub issues for the local project.
- [x] 2026-06-13: Snapshotted Source Control PR-generation runtime ownership and reused that owner for generation, cancellation, and post-generation git refresh, preventing async PR generation from following a later focused-host switch. Verified with focused Source Control host-boundary tests, targeted oxlint, and `git diff --check`.
@@ -0,0 +1,40 @@
import { readFileSync } from 'node:fs'
import { join } from 'node:path'
import { describe, expect, it } from 'vitest'
const SOURCE_CONTROL_SOURCE = readFileSync(join(__dirname, 'SourceControl.tsx'), 'utf8')
function sourceBetween(source: string, startPattern: string, endPattern: string): string {
const start = source.indexOf(startPattern)
expect(start).toBeGreaterThanOrEqual(0)
const end = source.indexOf(endPattern, start + startPattern.length)
expect(end).toBeGreaterThan(start)
return source.slice(start, end)
}
describe('SourceControl host-context boundaries', () => {
it('snapshots PR generation host ownership and reuses it after async branch preparation', () => {
const generateSection = sourceBetween(
SOURCE_CONTROL_SOURCE,
'const handleGeneratePullRequestFieldsForActive = useCallback(',
'const handleCancelGeneratePullRequestFieldsForActive = useCallback('
)
expect(generateSection).toContain('runtimeTargetSettings: activeRepoSettings')
expect(generateSection).toContain('settings: context.runtimeTargetSettings')
const cancelSection = sourceBetween(
SOURCE_CONTROL_SOURCE,
'const handleCancelGeneratePullRequestFieldsForActive = useCallback(',
'const {'
)
expect(cancelSection).toContain('settings: record.context.runtimeTargetSettings')
const refreshSection = sourceBetween(
SOURCE_CONTROL_SOURCE,
'const refreshGitStatusAfterPullRequestGeneration = useCallback(',
'useEffect(() => {'
)
expect(refreshSection).toContain('settings: context.runtimeTargetSettings')
expect(refreshSection).not.toContain('settings: activeRepoSettings')
})
})
@@ -969,8 +969,9 @@ function SourceControlInner(): React.JSX.Element {
}
try {
await refreshGitStatusForWorktree({
// Why: route git status by the repo OWNER host, not the focused runtime.
settings: activeRepoSettings,
// Why: generation can finish after the user switches hosts; refresh
// the same host that owned the generation request.
settings: context.runtimeTargetSettings,
worktreeId: context.worktreeId,
worktreePath: context.worktreePath,
connectionId: context.connectionId,
@@ -987,7 +988,6 @@ function SourceControlInner(): React.JSX.Element {
}
},
[
activeRepoSettings,
fetchUpstreamStatus,
isFolder,
setGitStatus,
@@ -1953,7 +1953,8 @@ function SourceControlInner(): React.JSX.Element {
connectionId: getConnectionId(activeWorktreeId) ?? undefined,
requestId,
repoId: activeRepo.id,
branch: branchName
branch: branchName,
runtimeTargetSettings: activeRepoSettings
}
const seed = { ...fields }
// Why: SourceControl can unmount on tab switches; persisting the running
@@ -1967,7 +1968,7 @@ function SourceControlInner(): React.JSX.Element {
const result = await generateRuntimePullRequestFields(
{
// Why: route generation by the repo OWNER host, not the focused runtime.
settings: activeRepoSettings,
settings: context.runtimeTargetSettings,
worktreeId: context.worktreeId,
worktreePath: context.worktreePath,
connectionId: context.connectionId
@@ -2050,8 +2051,9 @@ function SourceControlInner(): React.JSX.Element {
return resolvePullRequestGenerationCancel(current)
})
void cancelRuntimeGeneratePullRequestFields({
// Why: route the cancel by the repo OWNER host, not the focused runtime.
settings: activeRepoSettings,
// Why: the user can switch hosts while generation runs; cancel the
// original request owner instead of the current focused host.
settings: record.context.runtimeTargetSettings,
worktreeId: record.context.worktreeId,
worktreePath: record.context.worktreePath,
connectionId: record.context.connectionId
@@ -2068,12 +2070,7 @@ function SourceControlInner(): React.JSX.Element {
}
})
})
}, [
activeRepoSettings,
activePullRequestGenerationKey,
prGenerationRecords,
updatePullRequestGenerationRecord
])
}, [activePullRequestGenerationKey, prGenerationRecords, updatePullRequestGenerationRecord])
const {
aiGenerationEnabled: prAiGenerationEnabled,