mirror of
https://github.com/stablyai/orca.git
synced 2026-10-16 08:03:09 +00:00
Route typed GitHub lookups by source host
Co-authored-by: Orca <help@stably.ai>
This commit is contained in:
@@ -294,5 +294,6 @@ Done means: reviewers and future agents can understand the vision, implementatio
|
||||
- [x] 2026-06-13: Fed saved automation source contexts into Run Now availability so internal Orca automations now disable before dispatch when the saved source host is unavailable, still checking capabilities, missing task-source support, missing GitHub/GitLab auth, or missing provider tooling. Verified with focused automation availability tests, provider availability tests, targeted oxlint, `git diff --check`, and full `pnpm run typecheck`.
|
||||
- [x] 2026-06-13: Closed another request/cache ownership slice by routing right-sidebar search, create-review field generation, browser tab default profile selection, Smart Workspace GitHub/GitLab/Linear lookups, GitHub Project browse cache, Project slug comments, and standalone GitHub issue comments through explicit worktree/repo/source ownership instead of ambient focused runtime state. Verified with focused ownership/browser/smart-submit tests, targeted oxlint, `git diff --check`, and full `pnpm run typecheck`.
|
||||
- [x] 2026-06-13: Routed Quick Open file listing through the selected worktree runtime owner instead of ambient focused settings, matching File Explorer and sidebar search ownership. Verified with focused Quick Open and file-owner boundary tests, targeted oxlint, `git diff --check`, and full `pnpm run typecheck`.
|
||||
- [x] 2026-06-13: Routed typed GitHub issue/PR lookup entry points in Cmd-J, Smart Workspace cross-repo accept, and Composer submit fallback through source-aware local/SSH/runtime lookup helpers instead of direct preload calls. Verified with focused composer/palette/smart-submit tests, targeted oxlint, `git diff --check`, and full `pnpm run typecheck`.
|
||||
- [~] 2026-06-13: Remaining source-error gap: unavailable hosts, version-skewed runtimes, and GitHub/GitLab missing auth/tooling are surfaced for desktop-owned and runtime-owned source hosts, but unsupported-provider still has no production emitter per source host.
|
||||
- [~] 2026-06-13: Remaining Automation gap: run-host/source-host availability and missing project-host setup are handled for manual/internal/external actions, but `Automation.projectId` remains the legacy repo-id compatibility field.
|
||||
|
||||
@@ -81,9 +81,14 @@ import {
|
||||
getComposerEligibleRepos,
|
||||
resolveComposerGitRepoId
|
||||
} from '@/lib/new-workspace-composer-repo'
|
||||
import {
|
||||
lookupGitHubWorkItemByOwnerRepoForSource,
|
||||
lookupGitHubWorkItemForSource
|
||||
} from '@/lib/github-work-item-source-lookup'
|
||||
import type { SettingsNavTarget } from '@/lib/settings-navigation-types'
|
||||
import type { BrowserPage, BrowserWorkspace, Worktree } from '../../../shared/types'
|
||||
import { isGitRepoKind } from '../../../shared/repo-kind'
|
||||
import { buildTaskSourceContextFromRepo } from '../../../shared/task-source-context'
|
||||
import { translate } from '@/i18n/i18n'
|
||||
|
||||
type WorktreePaletteItem = {
|
||||
@@ -1183,6 +1188,11 @@ export default function WorktreeJumpPalette(): React.JSX.Element | null {
|
||||
}
|
||||
|
||||
prefetchCreateWorkspaceBaseForComposer(repoForLookup.id)
|
||||
const sourceContext = buildTaskSourceContextFromRepo({
|
||||
provider: 'github',
|
||||
projectId: repoForLookup.id,
|
||||
repo: repoForLookup
|
||||
})
|
||||
// Why: awaiting inside the user gesture would leave the palette open
|
||||
// indefinitely on slow networks. Close immediately and populate the
|
||||
// composer once the lookup returns.
|
||||
@@ -1190,15 +1200,15 @@ export default function WorktreeJumpPalette(): React.JSX.Element | null {
|
||||
preserveCreateLookupOnCloseRef.current = true
|
||||
recordFeatureInteraction('cmd-j-create-workspace')
|
||||
closeModal()
|
||||
void window.api.gh
|
||||
.workItemByOwnerRepo({
|
||||
repoPath: repoForLookup.path,
|
||||
repoId: repoForLookup.id,
|
||||
owner: slug.owner,
|
||||
repo: slug.repo,
|
||||
number,
|
||||
type: ghLink.type
|
||||
})
|
||||
void lookupGitHubWorkItemByOwnerRepoForSource({
|
||||
repoPath: repoForLookup.path,
|
||||
repoId: repoForLookup.id,
|
||||
sourceContext,
|
||||
owner: slug.owner,
|
||||
repo: slug.repo,
|
||||
number,
|
||||
type: ghLink.type
|
||||
})
|
||||
.then((item) => {
|
||||
if (!createLookupGuard.isCurrent(lookupToken)) {
|
||||
return
|
||||
@@ -1260,12 +1270,21 @@ export default function WorktreeJumpPalette(): React.JSX.Element | null {
|
||||
}
|
||||
|
||||
prefetchCreateWorkspaceBaseForComposer(repoForLookup.id)
|
||||
const sourceContext = buildTaskSourceContextFromRepo({
|
||||
provider: 'github',
|
||||
projectId: repoForLookup.id,
|
||||
repo: repoForLookup
|
||||
})
|
||||
const lookupToken = createLookupGuard.start()
|
||||
preserveCreateLookupOnCloseRef.current = true
|
||||
recordFeatureInteraction('cmd-j-create-workspace')
|
||||
closeModal()
|
||||
void window.api.gh
|
||||
.workItem({ repoPath: repoForLookup.path, repoId: repoForLookup.id, number: ghNumber })
|
||||
void lookupGitHubWorkItemForSource({
|
||||
repoPath: repoForLookup.path,
|
||||
repoId: repoForLookup.id,
|
||||
sourceContext,
|
||||
number: ghNumber
|
||||
})
|
||||
.then((item) => {
|
||||
if (!createLookupGuard.isCurrent(lookupToken)) {
|
||||
return
|
||||
|
||||
@@ -37,6 +37,10 @@ import {
|
||||
parseGitHubIssueOrPRLink,
|
||||
type RepoSlug
|
||||
} from '@/lib/github-links'
|
||||
import {
|
||||
lookupGitHubWorkItemByOwnerRepoForSource,
|
||||
lookupGitHubWorkItemForSource
|
||||
} from '@/lib/github-work-item-source-lookup'
|
||||
import { lookupSmartGitHubSubmitItem } from '@/lib/smart-github-submit'
|
||||
import { parseGitLabIssueOrMRLink } from '@/lib/gitlab-links'
|
||||
import { getLocalPreflightContext, localPreflightContextKey } from '@/lib/local-preflight-context'
|
||||
@@ -401,9 +405,8 @@ export default function SmartWorkspaceNameField({
|
||||
number: directLink.number,
|
||||
type: directLink.type
|
||||
},
|
||||
workItem: (args) => window.api.gh.workItem(args) as Promise<GitHubWorkItem | null>,
|
||||
workItemByOwnerRepo: (args) =>
|
||||
window.api.gh.workItemByOwnerRepo(args) as Promise<GitHubWorkItem | null>
|
||||
workItem: lookupGitHubWorkItemForSource,
|
||||
workItemByOwnerRepo: lookupGitHubWorkItemByOwnerRepoForSource
|
||||
})
|
||||
if (!stale) {
|
||||
setGithubItems(item ? [item] : [])
|
||||
@@ -447,9 +450,8 @@ export default function SmartWorkspaceNameField({
|
||||
repoId: selectedRepo.id,
|
||||
sourceContext: githubSourceContext,
|
||||
intent,
|
||||
workItem: (args) => window.api.gh.workItem(args) as Promise<GitHubWorkItem | null>,
|
||||
workItemByOwnerRepo: (args) =>
|
||||
window.api.gh.workItemByOwnerRepo(args) as Promise<GitHubWorkItem | null>
|
||||
workItem: lookupGitHubWorkItemForSource,
|
||||
workItemByOwnerRepo: lookupGitHubWorkItemByOwnerRepoForSource
|
||||
})
|
||||
void request
|
||||
.then((item) => {
|
||||
@@ -869,9 +871,15 @@ export default function SmartWorkspaceNameField({
|
||||
handledCrossRepoUrlRef.current = debouncedQuery.trim()
|
||||
setGithubLoading(true)
|
||||
try {
|
||||
const item = await window.api.gh.workItemByOwnerRepo({
|
||||
const sourceContext = buildTaskSourceContextFromRepo({
|
||||
provider: 'github',
|
||||
projectId: targetRepo.id,
|
||||
repo: targetRepo
|
||||
})
|
||||
const item = await lookupGitHubWorkItemByOwnerRepoForSource({
|
||||
repoPath: targetRepo.path,
|
||||
repoId: targetRepo.id,
|
||||
sourceContext,
|
||||
owner: crossRepoPrompt.link.slug.owner,
|
||||
repo: crossRepoPrompt.link.slug.repo,
|
||||
number: crossRepoPrompt.link.number,
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
import { readFileSync } from 'node:fs'
|
||||
import { join } from 'node:path'
|
||||
import { describe, expect, it } from 'vitest'
|
||||
|
||||
const source = readFileSync(join(__dirname, 'WorktreeJumpPalette.tsx'), 'utf8')
|
||||
|
||||
function sourceBetween(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('WorktreeJumpPalette source-context boundaries', () => {
|
||||
it('resolves typed GitHub issue/PR entries through the lookup repo source host', () => {
|
||||
expect(source).toContain('buildTaskSourceContextFromRepo')
|
||||
|
||||
const githubLinkSection = sourceBetween(
|
||||
'void lookupGitHubWorkItemByOwnerRepoForSource({',
|
||||
'// Case 2: user typed a raw issue number.'
|
||||
)
|
||||
expect(githubLinkSection).toContain('sourceContext')
|
||||
|
||||
const rawNumberSection = sourceBetween(
|
||||
'void lookupGitHubWorkItemForSource({',
|
||||
'.then((item) => {'
|
||||
)
|
||||
expect(rawNumberSection).toContain('sourceContext')
|
||||
})
|
||||
})
|
||||
@@ -80,4 +80,22 @@ describe('useComposerState host-context boundaries', () => {
|
||||
expect(section).toContain('hostId: initialRunSeed.hostId')
|
||||
expect(section).toContain('projectHostSetupId: initialRunSeed.projectHostSetupId')
|
||||
})
|
||||
|
||||
it('resolves typed GitHub issue/PR input through the selected repo source context', () => {
|
||||
expect(HOOK_SOURCE).toContain('const selectedRepoGitHubSourceContext = useMemo')
|
||||
|
||||
const directLookup = sourceBetween(
|
||||
HOOK_SOURCE,
|
||||
'void window.api.gh',
|
||||
'const applyLinkedWorkItem = useCallback'
|
||||
)
|
||||
expect(directLookup).toContain('sourceContext: selectedRepoGitHubSourceContext')
|
||||
|
||||
const submitLookup = sourceBetween(
|
||||
HOOK_SOURCE,
|
||||
'const resolvePendingSmartGitHubSubmit',
|
||||
'const resolution = getSmartGitHubSubmitResolution(item)'
|
||||
)
|
||||
expect(submitLookup).toContain('sourceContext: selectedRepoGitHubSourceContext')
|
||||
})
|
||||
})
|
||||
|
||||
@@ -83,6 +83,10 @@ import {
|
||||
getSmartGitHubSubmitResolution,
|
||||
type SmartGitHubSubmitResolution
|
||||
} from '@/lib/smart-github-submit'
|
||||
import {
|
||||
lookupGitHubWorkItemByOwnerRepoForSource,
|
||||
lookupGitHubWorkItemForSource
|
||||
} from '@/lib/github-work-item-source-lookup'
|
||||
import { isWorkItemLookupText } from '@/lib/work-item-lookup-text'
|
||||
import {
|
||||
canUseRepoBackedComposerSources,
|
||||
@@ -566,6 +570,34 @@ export function useComposerState(options: UseComposerStateOptions): UseComposerS
|
||||
selectedRepo,
|
||||
selectedWorkspaceTarget
|
||||
])
|
||||
const selectedRepoGitHubSourceContext = useMemo(() => {
|
||||
if (!selectedRepo || !selectedRepoIsGit) {
|
||||
return null
|
||||
}
|
||||
if (taskSourceContext?.provider === 'github') {
|
||||
return taskSourceContext
|
||||
}
|
||||
if (selectedWorkspaceTarget.status === 'ready') {
|
||||
const selectedProject = projects.find(
|
||||
(project) => project.id === selectedWorkspaceTarget.target.projectId
|
||||
)
|
||||
return buildTaskSourceContextFromRepo({
|
||||
provider: 'github',
|
||||
projectId: selectedWorkspaceTarget.target.projectId,
|
||||
repo: selectedRepo,
|
||||
projectHostSetupId: selectedWorkspaceTarget.target.projectHostSetupId,
|
||||
providerIdentity:
|
||||
selectedProject?.providerIdentity?.provider === 'github'
|
||||
? selectedProject.providerIdentity
|
||||
: null
|
||||
})
|
||||
}
|
||||
return buildTaskSourceContextFromRepo({
|
||||
provider: 'github',
|
||||
projectId: selectedRepo.id,
|
||||
repo: selectedRepo
|
||||
})
|
||||
}, [projects, selectedRepo, selectedRepoIsGit, selectedWorkspaceTarget, taskSourceContext])
|
||||
const [linkedIssue, setLinkedIssue] = useState<string>(() => {
|
||||
if (persistDraft && newWorkspaceDraft?.linkedIssue) {
|
||||
return newWorkspaceDraft.linkedIssue
|
||||
@@ -1326,12 +1358,12 @@ export function useComposerState(options: UseComposerStateOptions): UseComposerS
|
||||
// resolving direct lookups against the selected repo instead of requiring a
|
||||
// text match in the recent-items list.
|
||||
const lookupRepoId = selectedRepo.id
|
||||
void window.api.gh
|
||||
.workItem({
|
||||
repoPath: selectedRepo.path,
|
||||
repoId: selectedRepo.id,
|
||||
number: normalizedLinkQuery.directNumber
|
||||
})
|
||||
void lookupGitHubWorkItemForSource({
|
||||
repoPath: selectedRepo.path,
|
||||
repoId: selectedRepo.id,
|
||||
sourceContext: selectedRepoGitHubSourceContext,
|
||||
number: normalizedLinkQuery.directNumber
|
||||
})
|
||||
.then((item) => {
|
||||
if (!cancelled) {
|
||||
setLinkDirectItem(
|
||||
@@ -1353,7 +1385,13 @@ export function useComposerState(options: UseComposerStateOptions): UseComposerS
|
||||
return () => {
|
||||
cancelled = true
|
||||
}
|
||||
}, [linkPopoverOpen, normalizedLinkQuery.directNumber, selectedRepo, selectedRepoIsGit])
|
||||
}, [
|
||||
linkPopoverOpen,
|
||||
normalizedLinkQuery.directNumber,
|
||||
selectedRepo,
|
||||
selectedRepoGitHubSourceContext,
|
||||
selectedRepoIsGit
|
||||
])
|
||||
|
||||
const applyLinkedWorkItem = useCallback(
|
||||
(item: GitHubWorkItem, options: { preserveBranchNameOverride?: boolean } = {}): void => {
|
||||
@@ -1404,10 +1442,10 @@ export function useComposerState(options: UseComposerStateOptions): UseComposerS
|
||||
const item = await lookupSmartGitHubSubmitItem({
|
||||
repoPath: selectedRepo.path,
|
||||
repoId: selectedRepo.id,
|
||||
sourceContext: selectedRepoGitHubSourceContext,
|
||||
intent,
|
||||
workItem: (args) => window.api.gh.workItem(args) as Promise<GitHubWorkItem | null>,
|
||||
workItemByOwnerRepo: (args) =>
|
||||
window.api.gh.workItemByOwnerRepo(args) as Promise<GitHubWorkItem | null>
|
||||
workItem: lookupGitHubWorkItemForSource,
|
||||
workItemByOwnerRepo: lookupGitHubWorkItemByOwnerRepoForSource
|
||||
})
|
||||
if (!item) {
|
||||
throw new Error('Could not resolve the GitHub item before creating the workspace.')
|
||||
@@ -1430,7 +1468,7 @@ export function useComposerState(options: UseComposerStateOptions): UseComposerS
|
||||
branchAutoNameRef.current = ''
|
||||
setStartFromResetHint(null)
|
||||
return resolution
|
||||
}, [linkedWorkItem, name, selectedRepo, selectedRepoIsGit])
|
||||
}, [linkedWorkItem, name, selectedRepo, selectedRepoGitHubSourceContext, selectedRepoIsGit])
|
||||
|
||||
// Why: parallel of applyLinkedWorkItem for GitLab. Touches the GitLab
|
||||
// state slots only — the GitHub linkedIssue/linkedPR remain unchanged
|
||||
|
||||
@@ -0,0 +1,76 @@
|
||||
import type { GitHubWorkItem } from '../../../shared/types'
|
||||
import type { TaskSourceContext } from '../../../shared/task-source-context'
|
||||
import { getTaskSourceRuntimeSettings } from '../../../shared/task-source-context'
|
||||
import { callRuntimeRpc, getActiveRuntimeTarget } from '@/runtime/runtime-rpc-client'
|
||||
|
||||
type GitHubWorkItemLookupArgs = {
|
||||
repoPath: string
|
||||
repoId: string
|
||||
sourceContext?: TaskSourceContext | null
|
||||
number: number
|
||||
type?: 'issue' | 'pr'
|
||||
}
|
||||
|
||||
type GitHubWorkItemByOwnerRepoLookupArgs = GitHubWorkItemLookupArgs & {
|
||||
owner: string
|
||||
repo: string
|
||||
type: 'issue' | 'pr'
|
||||
}
|
||||
|
||||
function runtimeRepoId(args: Pick<GitHubWorkItemLookupArgs, 'repoId' | 'sourceContext'>): string {
|
||||
return args.sourceContext?.repoId ?? args.repoId
|
||||
}
|
||||
|
||||
export async function lookupGitHubWorkItemForSource(
|
||||
args: GitHubWorkItemLookupArgs
|
||||
): Promise<GitHubWorkItem | null> {
|
||||
const target = getActiveRuntimeTarget(getTaskSourceRuntimeSettings(args.sourceContext))
|
||||
const item =
|
||||
target.kind === 'environment'
|
||||
? await callRuntimeRpc<Omit<GitHubWorkItem, 'repoId'> | null>(
|
||||
target,
|
||||
'github.workItem',
|
||||
{
|
||||
repo: runtimeRepoId(args),
|
||||
number: args.number,
|
||||
type: args.type
|
||||
},
|
||||
{ timeoutMs: 30_000 }
|
||||
)
|
||||
: await window.api.gh.workItem({
|
||||
repoPath: args.repoPath,
|
||||
repoId: args.repoId,
|
||||
number: args.number,
|
||||
type: args.type
|
||||
})
|
||||
return item ? ({ ...item, repoId: args.repoId } as GitHubWorkItem) : null
|
||||
}
|
||||
|
||||
export async function lookupGitHubWorkItemByOwnerRepoForSource(
|
||||
args: GitHubWorkItemByOwnerRepoLookupArgs
|
||||
): Promise<GitHubWorkItem | null> {
|
||||
const target = getActiveRuntimeTarget(getTaskSourceRuntimeSettings(args.sourceContext))
|
||||
const item =
|
||||
target.kind === 'environment'
|
||||
? await callRuntimeRpc<Omit<GitHubWorkItem, 'repoId'> | null>(
|
||||
target,
|
||||
'github.workItemByOwnerRepo',
|
||||
{
|
||||
repo: runtimeRepoId(args),
|
||||
owner: args.owner,
|
||||
ownerRepo: args.repo,
|
||||
number: args.number,
|
||||
type: args.type
|
||||
},
|
||||
{ timeoutMs: 30_000 }
|
||||
)
|
||||
: await window.api.gh.workItemByOwnerRepo({
|
||||
repoPath: args.repoPath,
|
||||
repoId: args.repoId,
|
||||
owner: args.owner,
|
||||
repo: args.repo,
|
||||
number: args.number,
|
||||
type: args.type
|
||||
})
|
||||
return item ? ({ ...item, repoId: args.repoId } as GitHubWorkItem) : null
|
||||
}
|
||||
Reference in New Issue
Block a user