mirror of
https://github.com/stablyai/orca.git
synced 2026-10-21 16:03:26 +00:00
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:
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user