Add read-only orca linear CLI with trusted launch-prompt pointer (V1) (#5126)

Co-authored-by: Orca <help@stably.ai>
This commit is contained in:
Brennan Benson
2026-06-10 20:20:50 -07:00
committed by GitHub
co-authored by Orca
parent 519cc5d1c3
commit cdc0ca5e53
68 changed files with 4379 additions and 283 deletions
+128
View File
@@ -82,6 +82,10 @@ import type {
} from '../../shared/types'
import type { RuntimeClientEvent } from '../../shared/runtime-client-events'
import { toRuntimeActivateWorktreeEvent } from '../../shared/runtime-client-events'
import type {
LinearCurrentIssueContextHints,
LinearIssueRequest
} from '../../shared/linear-agent-access'
import type { FeatureInteractionId } from '../../shared/feature-interactions'
import type { TerminalPaneSplitSource } from '../../shared/feature-education-telemetry'
import { FOLDER_WORKSPACE_INSTANCE_SEPARATOR, splitWorktreeId } from '../../shared/worktree-id'
@@ -306,6 +310,13 @@ import {
updateIssue as updateLinearIssue,
type LinearListFilter
} from '../linear/issues'
import {
LinearAgentAccessError,
getLinearCurrentIssueFromWorktree,
readLinearIssueContext,
resolveLegacyLinearLinkWorkspace,
searchLinearIssuesForAgents
} from '../linear/issue-context'
import {
createProject as createLinearProject,
getCustomView as getLinearCustomView,
@@ -972,6 +983,8 @@ function mergeRuntimeFolderWorkspace(repo: Repo, worktreeId: string, meta: Workt
linkedIssue: meta.linkedIssue ?? null,
linkedPR: meta.linkedPR ?? null,
linkedLinearIssue: meta.linkedLinearIssue ?? null,
linkedLinearIssueWorkspaceId: meta.linkedLinearIssueWorkspaceId ?? null,
linkedLinearIssueOrganizationUrlKey: meta.linkedLinearIssueOrganizationUrlKey ?? null,
linkedGitLabMR: meta.linkedGitLabMR ?? null,
linkedGitLabIssue: meta.linkedGitLabIssue ?? null,
isArchived: meta.isArchived ?? false,
@@ -8919,6 +8932,8 @@ export class OrcaRuntimeService {
linkedIssue?: number | null
linkedPR?: number | null
linkedLinearIssue?: string
linkedLinearIssueWorkspaceId?: string | null
linkedLinearIssueOrganizationUrlKey?: string | null
linkedGitLabMR?: number | null
linkedGitLabIssue?: number | null
comment?: string
@@ -8998,6 +9013,12 @@ export class OrcaRuntimeService {
...(args.linkedLinearIssue !== undefined
? { linkedLinearIssue: args.linkedLinearIssue }
: {}),
...(args.linkedLinearIssueWorkspaceId !== undefined
? { linkedLinearIssueWorkspaceId: args.linkedLinearIssueWorkspaceId }
: {}),
...(args.linkedLinearIssueOrganizationUrlKey !== undefined
? { linkedLinearIssueOrganizationUrlKey: args.linkedLinearIssueOrganizationUrlKey }
: {}),
...(args.linkedGitLabIssue !== undefined
? { linkedGitLabIssue: args.linkedGitLabIssue }
: {}),
@@ -9385,6 +9406,12 @@ export class OrcaRuntimeService {
...(args.linkedLinearIssue !== undefined
? { linkedLinearIssue: args.linkedLinearIssue }
: {}),
...(args.linkedLinearIssueWorkspaceId !== undefined
? { linkedLinearIssueWorkspaceId: args.linkedLinearIssueWorkspaceId }
: {}),
...(args.linkedLinearIssueOrganizationUrlKey !== undefined
? { linkedLinearIssueOrganizationUrlKey: args.linkedLinearIssueOrganizationUrlKey }
: {}),
...(args.linkedGitLabIssue !== undefined
? { linkedGitLabIssue: args.linkedGitLabIssue }
: {}),
@@ -9627,6 +9654,8 @@ export class OrcaRuntimeService {
linkedIssue?: number | null
linkedPR?: number | null
linkedLinearIssue?: string
linkedLinearIssueWorkspaceId?: string | null
linkedLinearIssueOrganizationUrlKey?: string | null
linkedGitLabMR?: number | null
linkedGitLabIssue?: number | null
comment?: string
@@ -9670,6 +9699,12 @@ export class OrcaRuntimeService {
...(args.linkedIssue != null ? { linkedIssue: args.linkedIssue } : {}),
...(args.linkedPR != null ? { linkedPR: args.linkedPR } : {}),
...(args.linkedLinearIssue ? { linkedLinearIssue: args.linkedLinearIssue } : {}),
...(args.linkedLinearIssueWorkspaceId !== undefined
? { linkedLinearIssueWorkspaceId: args.linkedLinearIssueWorkspaceId }
: {}),
...(args.linkedLinearIssueOrganizationUrlKey !== undefined
? { linkedLinearIssueOrganizationUrlKey: args.linkedLinearIssueOrganizationUrlKey }
: {}),
...(args.linkedGitLabMR != null ? { linkedGitLabMR: args.linkedGitLabMR } : {}),
...(args.linkedGitLabIssue != null ? { linkedGitLabIssue: args.linkedGitLabIssue } : {}),
...(args.pushTarget ? { pushTarget: args.pushTarget } : {}),
@@ -14275,6 +14310,99 @@ export class OrcaRuntimeService {
return searchLinearIssues(query, Math.min(Math.max(1, limit), 50), workspaceId)
}
linearSearchForAgents(args: {
query: string
limit?: number
workspaceId?: string | 'all'
}): ReturnType<typeof searchLinearIssuesForAgents> {
return searchLinearIssuesForAgents(args)
}
linearIssueContext(request: LinearIssueRequest): ReturnType<typeof readLinearIssueContext> {
return readLinearIssueContext(request, (context) => this.linearResolveCurrentIssue(context))
}
async linearResolveCurrentIssue(
context?: LinearCurrentIssueContextHints
): Promise<ReturnType<typeof getLinearCurrentIssueFromWorktree>> {
if (!this.store) {
throw new Error('runtime_unavailable')
}
let worktree: ResolvedWorktree | null = null
if (context?.terminalHandle) {
try {
const terminal = await this.showTerminal(context.terminalHandle)
if (context.worktreeId && context.worktreeId !== terminal.worktreeId) {
throw new LinearAgentAccessError(
'linear_permission_denied',
'The provided Linear worktree context does not match the caller terminal.'
)
}
worktree = await this.resolveWorktreeSelector(`id:${terminal.worktreeId}`)
} catch (error) {
if (error instanceof LinearAgentAccessError) {
throw error
}
if (context.remote === true || context.worktreeId) {
throw new LinearAgentAccessError(
'linear_issue_required',
'Could not verify the current Linear-linked worktree.'
)
}
}
}
if (!worktree && context?.remote !== true && context?.cwd) {
worktree = await this.resolveWorktreeForContainedPath(context.cwd)
if (!worktree) {
throw new LinearAgentAccessError(
'linear_issue_required',
'Run --current from inside an Orca-managed worktree or pass an issue id.'
)
}
}
if (!worktree) {
throw new LinearAgentAccessError(
'linear_issue_required',
'Run --current from inside an Orca-managed worktree or pass an issue id.'
)
}
const link = getLinearCurrentIssueFromWorktree(worktree)
if (!link.workspaceId) {
const backfill = resolveLegacyLinearLinkWorkspace(worktree.linkedLinearIssue ?? '')
if (backfill?.workspaceId) {
this.store.setWorktreeMeta(worktree.id, {
linkedLinearIssueWorkspaceId: backfill.workspaceId,
linkedLinearIssueOrganizationUrlKey: backfill.organizationUrlKey ?? null
})
return {
...link,
workspaceId: backfill.workspaceId,
organizationUrlKey: backfill.organizationUrlKey ?? link.organizationUrlKey,
backfill
}
}
}
return link
}
private async resolveWorktreeForContainedPath(cwd: string): Promise<ResolvedWorktree | null> {
const currentPath = resolve(cwd)
let best: ResolvedWorktree | null = null
for (const candidate of await this.listResolvedWorktrees()) {
if (!isPathInsideOrEqual(candidate.path, currentPath)) {
continue
}
if (!best || candidate.path.length > best.path.length) {
best = candidate
}
}
return best
}
linearListIssues(
filter?: LinearListFilter,
limit = 20,