← 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-match.ts
290 lines · 8.1 KB · TypeScriptimport {
IN_FLIGHT_STATUSES,
SKIP_SPAWN_OUTCOMES,
coarseErrorFamily,
matchFamiliesOverlap,
sanitizeForPrompt,
truncate,
type FingerprintRecord,
} from './shared.ts'
export type PeerAgent = {
id: string
name: string
status: string
url: string
createdAt: string | null
why: string
}
export type PeerPullRequest = {
repo: string
number: number | null
title: string
url: string
draft: boolean
updatedAt: string | null
agentId: string | null
why: string
}
export type PeerPublish = {
kodyId: string | null
packageId: string | null
name: string
status: string
startedAt: string | null
url: string | null
why: string
}
export type PeerSibling = {
fingerprint: string
owner: string
error_family: string
sample_message?: string | null
status: string
outcome: string | null
completedAt: string | null
agentUrl: string | null
summary: string | null
why: string
}
export type PeerContext = {
agents: PeerAgent[]
pullRequests: PeerPullRequest[]
publishes: PeerPublish[]
siblings: PeerSibling[]
highConfidenceLiveSibling: PeerAgent | null
}
export const PEER_AGENT_LIMIT = 5
export const PEER_PR_LIMIT = 5
export const PEER_PUBLISH_LIMIT = 3
export const PEER_SIBLING_LIMIT = 5
/** Cursor listAgents often reports finished agents as ACTIVE. */
export const PEER_LIVE_CREATED_WITHIN_MS = 2 * 60 * 60 * 1000
const LIVE_STATUSES = new Set([
'RUNNING',
'ACTIVE',
'CREATING',
'WAITING',
'WAITING_FOR_BACKGROUND_WORK',
'NOT_YET_STARTED',
])
const RECENT_STATUSES = new Set([...LIVE_STATUSES, 'IDLE', 'FINISHED', 'COMPLETED'])
const ALLOWED_PR_REPOS = new Set(['kentcdodds/kody', 'kentcdodds/use-kody'])
export function recordMatchNeedles(
record: Pick<FingerprintRecord, 'owner' | 'kody_id' | 'error_family' | 'package_id'>,
) {
const coarse = coarseErrorFamily(record.error_family)
return [
coarse,
record.error_family,
record.owner,
record.kody_id,
record.package_id,
]
.filter((value): value is string => Boolean(value && String(value).length >= 4))
.map((value) => value.toLowerCase())
}
export function textMatchesRecord(
text: string,
record: Pick<FingerprintRecord, 'owner' | 'kody_id' | 'error_family' | 'package_id'>,
) {
const hay = String(text || '').toLowerCase()
if (!hay) return false
return recordMatchNeedles(record).some((needle) => hay.includes(needle))
}
export function isTriageAgentName(name: string) {
const value = String(name || '').toLowerCase()
return value.startsWith('kody-issue') || value.startsWith('kody-platform')
}
export function normalizeAgentStatus(status: string) {
return String(status || '')
.toUpperCase()
.replace(/[\s-]+/g, '_')
}
export function isLiveAgentStatus(status: string) {
return LIVE_STATUSES.has(normalizeAgentStatus(status))
}
export function agentCreatedRecently(
createdAt: string | null | undefined,
now = Date.now(),
) {
if (!createdAt) return false
const created = Date.parse(createdAt)
if (Number.isNaN(created)) return false
return now - created <= PEER_LIVE_CREATED_WITHIN_MS
}
export function isPeerCandidateStatus(status: string) {
return RECENT_STATUSES.has(normalizeAgentStatus(status))
}
export function agentUrlFrom(agent: { id?: string; url?: string } | null) {
if (!agent?.id && !agent?.url) return ''
return agent.url || (agent.id ? `https://cursor.com/agents/${agent.id}` : '')
}
export function agentMatchesRecord(
agent: { name?: string; status?: string },
record: Pick<FingerprintRecord, 'owner' | 'kody_id' | 'error_family' | 'package_id'>,
) {
const name = String(agent.name || '')
if (!isTriageAgentName(name)) return false
if (!isPeerCandidateStatus(String(agent.status || ''))) return false
return textMatchesRecord(name, record)
}
export function liveSiblingForRecord(
agents: Array<PeerAgent>,
record: Pick<FingerprintRecord, 'owner' | 'kody_id' | 'error_family' | 'package_id'>,
now = Date.now(),
) {
return (
agents.find(
(agent) =>
agentCreatedRecently(agent.createdAt, now) &&
isTriageAgentName(agent.name) &&
textMatchesRecord(agent.name, record),
) ?? null
)
}
export function repoSlugFromUrl(url: string) {
const match = String(url || '').match(/github\.com\/([^/]+\/[^/#?]+)/i)
return match ? match[1].replace(/\.git$/, '') : ''
}
export function prMatchesRecord(
pr: { title?: string; repo?: string; url?: string; headRef?: string },
record: Pick<FingerprintRecord, 'owner' | 'kody_id' | 'error_family' | 'package_id'>,
options: { linkedToMatchedAgent?: boolean } = {},
) {
const repo = (pr.repo || repoSlugFromUrl(String(pr.url || ''))).toLowerCase()
const hay = [pr.title, pr.headRef, repo, pr.url].filter(Boolean).join(' ')
if (options.linkedToMatchedAgent && repo) return true
if (ALLOWED_PR_REPOS.has(repo) && textMatchesRecord(hay, record)) return true
return textMatchesRecord(hay, record) && Boolean(repo)
}
export function siblingMatchesRecord(
sibling: Pick<
FingerprintRecord,
'fingerprint' | 'owner' | 'error_family' | 'kody_id' | 'sample_message'
>,
record: Pick<
FingerprintRecord,
'fingerprint' | 'owner' | 'error_family' | 'kody_id' | 'sample_message'
>,
) {
if (sibling.fingerprint === record.fingerprint) return false
return (
sibling.owner === record.owner ||
sibling.error_family === record.error_family ||
matchFamiliesOverlap(sibling, record) ||
Boolean(record.kody_id && sibling.kody_id === record.kody_id)
)
}
export function siblingWhy(
sibling: Pick<FingerprintRecord, 'owner' | 'error_family'>,
record: Pick<FingerprintRecord, 'owner' | 'error_family'>,
) {
if (
coarseErrorFamily(sibling.error_family) ===
coarseErrorFamily(record.error_family)
) {
return sibling.error_family === record.error_family
? 'same error family'
: 'same coarse family'
}
return 'same owner'
}
export function handledSiblingForRecord(
siblings: PeerSibling[],
record: Pick<FingerprintRecord, 'owner' | 'error_family' | 'sample_message'>,
) {
return (
siblings.find((sibling) => {
const outcome = sibling.outcome || sibling.status
if (!SKIP_SPAWN_OUTCOMES.has(outcome)) return false
if (sibling.owner !== record.owner) return false
return matchFamiliesOverlap(sibling, record)
}) ?? null
)
}
export function inFlightSiblingForRecord(
siblings: PeerSibling[],
record: Pick<
FingerprintRecord,
'owner' | 'error_family' | 'fingerprint' | 'sample_message'
>,
) {
return (
siblings.find((sibling) => {
if (sibling.fingerprint === record.fingerprint) return false
if (!IN_FLIGHT_STATUSES.has(sibling.status)) return false
if (sibling.owner !== record.owner) return false
return matchFamiliesOverlap(sibling, record)
}) ?? null
)
}
export function emptyPeerContext(): PeerContext {
return {
agents: [],
pullRequests: [],
publishes: [],
siblings: [],
highConfidenceLiveSibling: null,
}
}
function line(label: string, items: Array<string>) {
if (!items.length) return `- ${label}: none found`
return [`- ${label}:`, ...items.map((item) => ` - ${item}`)].join('\n')
}
export function formatPeerContextForPrompt(context: PeerContext) {
const agents = context.agents.slice(0, PEER_AGENT_LIMIT).map((agent) =>
sanitizeForPrompt(
`${agent.status} ${truncate(agent.name, 80)} · ${agent.id} · ${agent.url} · ${agent.why}`,
),
)
const prs = context.pullRequests.slice(0, PEER_PR_LIMIT).map((pr) =>
sanitizeForPrompt(
`${pr.repo}${pr.number != null ? `#${pr.number}` : ''} ${truncate(pr.title, 80)} · ${pr.url}${pr.draft ? ' · draft' : ''}${pr.agentId ? ` · agent ${pr.agentId}` : ''} · ${pr.why}`,
),
)
const publishes = context.publishes.slice(0, PEER_PUBLISH_LIMIT).map((item) =>
sanitizeForPrompt(
`${item.status} ${truncate(item.name, 80)} · ${item.kodyId || item.packageId || 'unknown'} · ${item.startedAt || 'unknown time'} · ${item.why}`,
),
)
const siblings = context.siblings.slice(0, PEER_SIBLING_LIMIT).map((item) =>
sanitizeForPrompt(
`${item.fingerprint} · ${item.owner} · ${item.status}${item.outcome ? `/${item.outcome}` : ''} · ${item.agentUrl || 'no agent'} · ${truncate(item.summary || item.why, 120)}`,
),
)
return [
line('Peer agents', agents),
line('Related pull requests', prs),
line('Owner package activity', publishes),
line('Stored sibling fingerprints', siblings),
].join('\n')
}