← Public packages
@kentcdodds/kody-issue-triage
Loop-safe triage for Kody run errors and fleet package-runtime error-rate elevations. Wakes Cole (Grok Bot) to decide; Cursor agents only when Cole escalates.
src/peer-context.ts
352 lines · 10.4 KB · TypeScriptimport { agentsWithOpenGithubPrs, listAgents } from 'kody:@kentcdodds/cursor/agents'
import githubRequest from 'kody:@kentcdodds/github/request'
import { kody } from 'kody:runtime'
import {
agentCreatedRecently,
agentMatchesRecord,
agentUrlFrom,
emptyPeerContext,
liveSiblingForRecord,
PEER_AGENT_LIMIT,
PEER_PR_LIMIT,
PEER_PUBLISH_LIMIT,
PEER_SIBLING_LIMIT,
prMatchesRecord,
repoSlugFromUrl,
siblingMatchesRecord,
siblingWhy,
type PeerAgent,
type PeerContext,
type PeerPublish,
type PeerPullRequest,
type PeerSibling,
} from './peer-match.ts'
import type { FingerprintRecord } from './shared.ts'
import { activityUrlFor } from './shared.ts'
import {
getFingerprint,
initSchema,
listRecentFingerprints,
listSiblingFingerprints,
} from './storage.ts'
export type { PeerContext } from './peer-match.ts'
export { formatPeerContextForPrompt } from './peer-match.ts'
export type GetPeerContextInput = {
fingerprint?: string
record?: FingerprintRecord
}
type GithubPull = {
number?: number
title?: string
html_url?: string
draft?: boolean
updated_at?: string
head?: { ref?: string }
base?: { repo?: { full_name?: string } }
}
type ListedAgent = {
id?: string
name?: string
status?: string
url?: string
createdAt?: string
}
type AgentPrRow = {
agentId?: string
prUrl?: string
pr_url?: string
url?: string
title?: string
prTitle?: string
prState?: string
repo?: string
number?: number
draft?: boolean
updatedAt?: string
updated_at?: string
}
async function settled<T>(work: () => Promise<T>, fallback: T): Promise<T> {
try {
return await work()
} catch {
return fallback
}
}
function summarizeAgents(items: ListedAgent[], record: FingerprintRecord): PeerAgent[] {
const matched: PeerAgent[] = []
for (const item of items) {
if (!agentMatchesRecord(item, record)) continue
const createdAt = item.createdAt ? String(item.createdAt) : null
matched.push({
id: String(item.id || ''),
name: String(item.name || 'unnamed'),
status: String(item.status || 'UNKNOWN'),
url: agentUrlFrom(item),
createdAt,
why: agentCreatedRecently(createdAt)
? 'recently created triage agent matching owner or error family'
: 'matching triage agent',
})
if (matched.length >= PEER_AGENT_LIMIT) break
}
return matched.filter((agent) => agent.id)
}
function pullFromGithub(row: GithubPull): PeerPullRequest | null {
const url = String(row.html_url || '')
if (!url) return null
return {
repo: String(row.base?.repo?.full_name || repoSlugFromUrl(url) || 'unknown'),
number: typeof row.number === 'number' ? row.number : null,
title: String(row.title || 'untitled'),
url,
draft: row.draft === true,
updatedAt: row.updated_at ? String(row.updated_at) : null,
agentId: null,
why: 'open PR on a Kody repo matching owner or error family',
}
}
function pullFromAgentRow(row: AgentPrRow): PeerPullRequest | null {
const url = String(row.prUrl || row.pr_url || row.url || '')
if (!url) return null
return {
repo: String(row.repo || repoSlugFromUrl(url) || 'unknown'),
number: typeof row.number === 'number' ? row.number : null,
title: String(row.title || row.prTitle || 'untitled'),
url,
draft: row.draft === true,
updatedAt: row.updatedAt || row.updated_at ? String(row.updatedAt || row.updated_at) : null,
agentId: row.agentId ? String(row.agentId) : null,
why: 'open PR attached to a Cloud agent',
}
}
function dedupePulls(items: Array<PeerPullRequest | null>) {
const seen = new Set<string>()
const next: PeerPullRequest[] = []
for (const item of items) {
if (!item?.url || seen.has(item.url)) continue
seen.add(item.url)
next.push(item)
if (next.length >= PEER_PR_LIMIT) break
}
return next
}
async function listOpenRepoPulls(repo: 'kody' | 'use-kody') {
const response = await githubRequest({
account: 'bot',
path: `/repos/kentcdodds/${repo}/pulls`,
query: { state: 'open', per_page: 20, sort: 'updated', direction: 'desc' },
timeoutMs: 8000,
})
const data = Array.isArray(response?.data) ? (response.data as GithubPull[]) : []
return data
}
async function gatherPullRequests(
record: FingerprintRecord,
matchedAgentIds: Set<string>,
): Promise<PeerPullRequest[]> {
const [agentPrs, kodyPulls, useKodyPulls] = await Promise.all([
settled(async () => {
const result = await agentsWithOpenGithubPrs({ limit: 20 })
const rows = Array.isArray(result?.pullRequests)
? (result.pullRequests as AgentPrRow[])
: []
return rows
}, []),
settled(() => listOpenRepoPulls('kody'), []),
settled(() => listOpenRepoPulls('use-kody'), []),
])
const fromAgents = agentPrs
.map((row) => {
const pr = pullFromAgentRow(row)
if (!pr) return null
const linked = Boolean(pr.agentId && matchedAgentIds.has(pr.agentId))
if (!prMatchesRecord(pr, record, { linkedToMatchedAgent: linked })) {
return null
}
return {
...pr,
why: linked
? 'open PR attached to a matching triage agent'
: pr.why,
}
})
const fromGithub = [...kodyPulls, ...useKodyPulls]
.map(pullFromGithub)
.filter((pr): pr is PeerPullRequest => Boolean(pr && prMatchesRecord(pr, record)))
return dedupePulls([...fromAgents, ...fromGithub])
}
async function gatherPublishes(record: FingerprintRecord): Promise<PeerPublish[]> {
const items: PeerPublish[] = []
if (record.package_id) {
const pkg = await settled(
() => kody.packageGet({ package_id: record.package_id as string }),
null as { kody_id?: string; updated_at?: string } | null,
)
if (pkg?.updated_at) {
items.push({
kodyId: pkg.kody_id || record.kody_id,
packageId: record.package_id,
name: 'package_get.updated_at',
status: 'updated',
startedAt: String(pkg.updated_at),
url: null,
why: 'saved package last updated',
})
}
const runs = await settled(
() =>
kody.runList({
package_id: record.package_id as string,
status: 'success',
limit: 5,
}),
{ runs: [] as Array<Record<string, unknown>> },
)
for (const run of runs.runs || []) {
const name = String(run.name || run.surface || 'run')
items.push({
kodyId: record.kody_id,
packageId: record.package_id,
name,
status: String(run.status || 'success'),
startedAt: run.started_at ? String(run.started_at) : null,
url: activityUrlFor(run.id ? String(run.id) : null),
why: 'recent successful owner-package run',
})
if (items.length >= PEER_PUBLISH_LIMIT) break
}
}
if (record.kody_id && items.length < PEER_PUBLISH_LIMIT) {
const workflows = await settled(
() => kody.workflowRunList({ limit: 25 }),
{ workflows: [] as Array<Record<string, unknown>> },
)
for (const workflow of workflows.workflows || []) {
if (String(workflow.kody_id || '') !== record.kody_id) continue
if (String(workflow.status || '') !== 'complete') continue
items.push({
kodyId: record.kody_id,
packageId: record.package_id,
name: String(workflow.workflow_name || workflow.export_name || 'workflow'),
status: 'complete',
startedAt: workflow.completed_at
? String(workflow.completed_at)
: workflow.created_at
? String(workflow.created_at)
: null,
url: null,
why: 'recent completed owner workflow',
})
if (items.length >= PEER_PUBLISH_LIMIT) break
}
}
return items.slice(0, PEER_PUBLISH_LIMIT)
}
function siblingsFromRecent(
recent: FingerprintRecord[],
record: FingerprintRecord,
): PeerSibling[] {
const items: PeerSibling[] = []
for (const sibling of recent) {
if (!siblingMatchesRecord(sibling, record)) continue
items.push({
fingerprint: sibling.fingerprint,
owner: sibling.owner,
error_family: sibling.error_family,
sample_message: sibling.sample_message,
status: sibling.status,
outcome: sibling.outcome,
completedAt: sibling.completed_at,
agentUrl: sibling.agent_url || sibling.kody_agent_url,
summary: sibling.summary,
why: siblingWhy(sibling, record),
})
if (items.length >= PEER_SIBLING_LIMIT) break
}
return items
}
/**
* Refresh the spawn-time peer snapshot: matching Cloud agents, related PRs,
* owner-package activity, and stored sibling fingerprints.
*
* @param input.fingerprint - Fingerprint string to look up when `record` is omitted.
* @param input.record - Optional already-loaded fingerprint record.
* @returns Peer context (`agents`, `pullRequests`, `publishes`, `siblings`, ...) plus `ok`.
*
* @example
* import getPeerContext from 'kody:@kentcdodds/kody-issue-triage/peer-context'
* const peers = await getPeerContext({ fingerprint: 'abc123' })
* // => { ok: true, fingerprint: 'abc123', agents: [...], ... }
*/
export async function gatherPeerContext(record: FingerprintRecord): Promise<PeerContext> {
const listed = await settled(async () => {
const page = await listAgents({ limit: 40 })
return Array.isArray(page?.items) ? (page.items as ListedAgent[]) : []
}, [] as ListedAgent[])
const agents = summarizeAgents(listed, record)
const matchedAgentIds = new Set(agents.map((agent) => agent.id))
const [pullRequests, publishes, storedSiblings, recent] = await Promise.all([
settled(() => gatherPullRequests(record, matchedAgentIds), []),
settled(() => gatherPublishes(record), []),
settled(() => listSiblingFingerprints(record, 20), [] as FingerprintRecord[]),
settled(() => listRecentFingerprints(40), [] as FingerprintRecord[]),
])
const siblings = siblingsFromRecent(
storedSiblings.length ? storedSiblings : recent,
record,
)
return {
agents,
pullRequests,
publishes,
siblings,
highConfidenceLiveSibling: liveSiblingForRecord(agents, record),
}
}
/**
* Refresh the spawn-time peer snapshot: matching Cloud agents, related PRs,
* owner-package activity, and stored sibling fingerprints.
*
* @param input.fingerprint - Fingerprint string to look up when `record` is omitted.
* @param input.record - Optional already-loaded fingerprint record.
* @returns Peer context (`agents`, `pullRequests`, `publishes`, `siblings`, ...) plus `ok`.
*
* @example
* import getPeerContext from 'kody:@kentcdodds/kody-issue-triage/peer-context'
* const peers = await getPeerContext({ fingerprint: 'abc123' })
* // => { ok: true, fingerprint: 'abc123', agents: [...], ... }
*/
export default async function getPeerContext(input: GetPeerContextInput = {}) {
await initSchema()
const record =
input.record ||
(input.fingerprint ? await getFingerprint(String(input.fingerprint).trim()) : null)
if (!record) {
return { ...emptyPeerContext(), ok: false as const, error: 'unknown fingerprint' }
}
const context = await gatherPeerContext(record)
return { ok: true as const, fingerprint: record.fingerprint, ...context }
}