Files
orca/src/main/linear/issue-context-relations.ts
T
mirror 34fa2cd5ca
Skill update round trip / roundtrip (false, macos-latest, copy, 1.5.17) (push) Canceled after 0s
Skill update round trip / roundtrip (false, macos-latest, symlink, 1.5.17) (push) Canceled after 0s
Skill update round trip / roundtrip (false, ubuntu-latest, copy, 1.5.17) (push) Canceled after 0s
Skill update round trip / roundtrip (false, ubuntu-latest, symlink, 1.5.17) (push) Canceled after 0s
Skill update round trip / roundtrip (false, ubuntu-latest, symlink, latest) (push) Canceled after 0s
Skill update round trip / roundtrip (false, windows-latest, copy, 1.5.17) (push) Canceled after 0s
Skill update round trip / roundtrip (false, windows-latest, symlink, 1.5.17) (push) Canceled after 0s
Skill update round trip / roundtrip (true, macos-latest, copy, 1.5.17) (push) Canceled after 0s
Skill update round trip / roundtrip (true, macos-latest, symlink, 1.5.17) (push) Canceled after 0s
Skill update round trip / roundtrip (true, ubuntu-latest, copy, 1.5.17) (push) Canceled after 0s
Skill update round trip / roundtrip (true, ubuntu-latest, symlink, 1.5.17) (push) Canceled after 0s
Skill update round trip / roundtrip (true, windows-latest, copy, 1.5.17) (push) Canceled after 0s
Skill update round trip / roundtrip (true, windows-latest, symlink, 1.5.17) (push) Canceled after 0s
source snapshot 2026-09-02
2026-09-02 02:03:16 +08:00

96 lines
3.2 KiB
TypeScript

import type { LinearCollectionMeta, LinearIssueRelation } from '../../shared/linear/agent-access'
import { LINEAR_RELATIONS_CAP } from '../../shared/linear/agent-access'
import type { ResolvedIssue } from './issue-context-client'
import { getRequiredEntry, withLinearRead } from './issue-context-client'
import { readConnectionPages } from './issue-context-pagination'
import {
INVERSE_RELATIONS_QUERY,
RELATIONS_QUERY,
collectionMeta,
type RawIssue,
type RawRelationNode,
type RawRelationsResponse
} from './issue-context-raw'
export async function readIssueRelations(
resolved: ResolvedIssue
): Promise<{ items: LinearIssueRelation[]; meta: LinearCollectionMeta }> {
const entry = getRequiredEntry(resolved.workspace.id)
const response = await readConnectionPages(LINEAR_RELATIONS_CAP, async (page) => {
return await withLinearRead(entry, async () => {
const raw = await entry.client.client.rawRequest<
RawRelationsResponse,
Record<string, unknown>
>(RELATIONS_QUERY, { id: resolved.issue.id, ...page })
return raw.data?.issue?.relations ?? null
})
})
const outbound = response.nodes
.slice(0, LINEAR_RELATIONS_CAP)
.map((node) => mapRelation(node, 'outbound', node.relatedIssue))
const remaining = LINEAR_RELATIONS_CAP - outbound.length
// Why: when outbound relations exactly fill the cap, probe inverse relations so
// the response cannot claim completeness while silently omitting inbound ones.
const inverseReadLimit = Math.max(1, remaining)
const inverse = await readConnectionPages(inverseReadLimit, async (page) => {
return await withLinearRead(entry, async () => {
const raw = await entry.client.client.rawRequest<
RawRelationsResponse,
Record<string, unknown>
>(INVERSE_RELATIONS_QUERY, { id: resolved.issue.id, ...page })
return raw.data?.issue?.inverseRelations ?? null
})
})
const inbound = inverse.nodes
.slice(0, remaining)
.map((node) => mapRelation(node, 'inbound', node.issue))
const items = [...outbound, ...inbound]
const inverseOverflow = inverse.nodes.length > remaining
return {
items,
meta: collectionMeta(
items.length,
LINEAR_RELATIONS_CAP,
response.hasMore || inverse.hasMore || inverseOverflow
)
}
}
function mapRelation(
node: RawRelationNode,
direction: LinearIssueRelation['direction'],
relatedIssue: RawIssue | null | undefined
): LinearIssueRelation {
const type = node.type ?? null
return {
id: node.id,
type,
direction,
relationship: relationPerspective(type, direction),
relatedIssue: relatedIssue
? {
id: relatedIssue.id,
identifier: relatedIssue.identifier,
title: relatedIssue.title,
url: relatedIssue.url
}
: null
}
}
function relationPerspective(
type: string | null,
direction: LinearIssueRelation['direction']
): LinearIssueRelation['relationship'] {
if (type === 'blocks') {
return direction === 'outbound' ? 'blocks' : 'blockedBy'
}
if (type === 'duplicate') {
return direction === 'outbound' ? 'duplicateOf' : 'duplicatedBy'
}
if (type === 'similar') {
return 'similar'
}
return 'relatedTo'
}