Files
orca/src/shared/linear-agent-access.ts
T
Brennan Benson a10a2ba53c feat(linear): add MCP-style save issue (#9670)
* feat(linear): add MCP-style save issue

* fix(linear): harden save issue parity

* fix(linear): close save issue contract gaps

* docs(linear): bundle project discovery with save issue
2026-07-21 13:25:22 -07:00

212 lines
5.4 KiB
TypeScript

export const LINEAR_SEARCH_DEFAULT_LIMIT = 20
export const LINEAR_SEARCH_MAX_LIMIT = 50
export const LINEAR_COMMENTS_CAP = 500
export const LINEAR_COMMENT_BODY_CAP = 20_000
export const LINEAR_CHILDREN_DEFAULT_DEPTH = 2
export const LINEAR_CHILDREN_MAX_DEPTH = 5
export const LINEAR_CHILDREN_NODE_CAP = 200
export const LINEAR_ATTACHMENTS_CAP = 100
export const LINEAR_RELATIONS_CAP = 100
export const LINEAR_ACTIVITY_CAP = 250
export const LINEAR_WRITE_BODY_CAP = 65_000
export const LINEAR_ERROR_CODES = [
'linear_not_connected',
'linear_issue_required',
'linear_no_linked_issue',
'linear_current_ambiguous',
'linear_issue_not_found',
'linear_workspace_ambiguous',
'linear_invalid_workspace',
'linear_invalid_state',
'linear_invalid_assignee',
'linear_invalid_label',
'linear_invalid_parent',
'linear_invalid_project',
'linear_team_required',
'linear_invalid_url',
'linear_body_too_large',
'linear_invalid_write_id',
'linear_write_failed',
'linear_write_unconfirmed',
'linear_rate_limited',
'linear_timeout',
'linear_permission_denied',
'linear_auth_expired',
'linear_network_error',
'linear_partial'
] as const
export type LinearErrorCode = (typeof LINEAR_ERROR_CODES)[number]
export type LinearIssueInclude = 'comments' | 'children' | 'attachments' | 'relations' | 'activity'
export type LinearIncludeErrorCode =
| 'linear_timeout'
| 'linear_rate_limited'
| 'linear_permission_denied'
| 'linear_auth_expired'
| 'linear_network_error'
| 'linear_include_failed'
export type LinearIssueRequest = {
input?: string
current?: boolean
workspaceId?: string
include: Record<LinearIssueInclude, boolean>
depth: number
context?: LinearCurrentIssueContextHints
}
export type LinearCurrentIssueContextHints = {
worktreeId?: string
terminalHandle?: string
cwd?: string
remote?: boolean
}
export type {
LinearAttachResult,
LinearCollectionMeta,
LinearCommentAddResult,
LinearCreateResult,
LinearIssueAttachment,
LinearIssueChildNode,
LinearIssueCommentNode,
LinearIssueContextResult,
LinearIssueListResult,
LinearProjectListResult,
LinearIssueRelation,
LinearIssueSummary,
LinearNamedEntity,
LinearSearchIssueSummary,
LinearSearchResult,
LinearStatusSetResult,
LinearTeamLabelsResult,
LinearTeamListResult,
LinearTeamMembersResult,
LinearTeamStatesResult,
LinearTeamSummary,
LinearUserSummary,
LinearWorkspaceCandidate,
LinearWriteIssueRef,
LinearIssueTaskUpdateResult,
LinearSaveIssueResult
} from './linear-agent-result-types'
export type { LinearIssueActivityEntry, LinearIssueActivityValue } from './linear-issue-activity'
export type { LinearInlineMedia } from './linear-inline-media'
export type { LinearMcpIssueListRequest, LinearMcpIssueListResult } from './linear-mcp-issue-list'
export type {
LinearIssueRelationship,
LinearIssueRelationWriteRequest,
LinearIssueRelationWriteResult
} from './linear-issue-relation-write'
export type LinearWriteTargetRequest = {
input?: string
current?: boolean
workspaceId?: string
context?: LinearCurrentIssueContextHints
}
export type LinearTeamDiscoveryRequest = {
teamInput?: string
workspaceId?: string | 'all'
}
export type LinearIssueListFilter = 'assigned' | 'created' | 'all' | 'completed' | 'open'
export type LinearIssueListRequest = {
filter?: LinearIssueListFilter
teamInput?: string
limit?: number
workspaceId?: string | 'all'
}
export type LinearProjectListRequest = {
query?: string
limit?: number
workspaceId?: string | 'all'
}
export type LinearStatusSetRequest = LinearWriteTargetRequest & {
to: string
}
export type LinearIssueTaskUpdateRequest = LinearWriteTargetRequest & {
operation: 'assignee' | 'priority' | 'estimate' | 'dueDate' | 'labels'
assigneeId?: string | null
assigneeMe?: boolean
priority?: number
estimate?: number | null
dueDate?: string | null
labelMode?: 'add' | 'remove' | 'set'
labels?: string[]
}
export type LinearCommentAddRequest = LinearWriteTargetRequest & {
body: string
replyTo?: string
writeId?: string
}
export type LinearAttachRequest = LinearWriteTargetRequest & {
url: string
title?: string
writeId?: string
}
export type LinearCreateRequest = {
title: string
body?: string
teamInput?: string
teamKey?: string
state?: string
assignee?: string
priority?: number
estimate?: number
dueDate?: string
labels?: string[]
projectInput?: string
parentInput?: string
parentCurrent?: boolean
workspaceId?: string
writeId?: string
context?: LinearCurrentIssueContextHints
}
export type LinearSaveIssueRequest = LinearWriteTargetRequest & {
team?: string
title?: string
description?: string
state?: string
assignee?: string | null
priority?: number
estimate?: number | null
dueDate?: string | null
labels?: string[]
project?: string | null
parentId?: string | null
writeId?: string
}
export function clampLinearSearchLimit(limit: number | undefined): number {
if (limit === undefined) {
return LINEAR_SEARCH_DEFAULT_LIMIT
}
if (!Number.isFinite(limit)) {
return LINEAR_SEARCH_DEFAULT_LIMIT
}
return Math.min(Math.max(1, Math.floor(limit)), LINEAR_SEARCH_MAX_LIMIT)
}
export function clampLinearIssueDepth(depth: number | undefined): number {
if (depth === undefined) {
return LINEAR_CHILDREN_DEFAULT_DEPTH
}
if (!Number.isFinite(depth)) {
return LINEAR_CHILDREN_DEFAULT_DEPTH
}
return Math.min(Math.max(0, Math.floor(depth)), LINEAR_CHILDREN_MAX_DEPTH)
}