← Public packages
@kentcdodds/sentry-triage
Sentry triage wakes Cole (Grok Bot) per issue: one active wake per repo (lease + queue), loop-safe Discord. Cole may spawn Cursor for isolated repo work.
src/prefetch-sentry-context.ts
76 lines · 2.3 KB · TypeScriptimport getLatestIssueEvent from 'kody:@kentcdodds/sentry/get-latest-issue-event'
import searchIssues from 'kody:@kentcdodds/sentry/search-issues'
import { truncate } from './shared.ts'
import { readTriageSettings } from './settings.ts'
/**
* Prefetch Sentry context once so every agent does not spend its opening
* round trips on the same lookups (observed: even fast agents made ~24 MCP
* calls, mostly Sentry fetches). Failures degrade to 'unavailable' — the
* prompt tells agents how to fetch anything missing themselves. API access
* goes through @kentcdodds/sentry so auth, host approval, and response
* parsing live in one place.
*/
export async function prefetchSentryContext(issueId, project) {
const context = { eventSummary: null, siblings: null, issueStats: null }
try {
const event = await getLatestIssueEvent({ issueId })
const exception = (event.entries ?? []).find(
(entry) => entry.type === 'exception',
)
const frames = (exception?.data?.values?.[0]?.stacktrace?.frames ?? [])
.filter((frame) => frame.inApp)
.slice(-6)
.map(
(frame) =>
`${frame.filename ?? '?'} in ${frame.function ?? '?'}:${frame.lineNo ?? '?'}`,
)
context.eventSummary = {
level: event.level ?? null,
environment:
(event.tags ?? []).find((t) => t.key === 'environment')?.value ??
null,
tags: Object.fromEntries(
(event.tags ?? [])
.filter((t) =>
[
'url',
'transaction',
'mcp.tool',
'mcp.capability',
'mcp.sandbox_error',
'runtime',
'mechanism',
].includes(t.key),
)
.map((t) => [t.key, t.value]),
),
topFrames: frames,
message: truncate(event.message ?? '', 300),
}
} catch {
// Prompt instructs agents to fetch this themselves when missing.
}
try {
const issues = await searchIssues({
orgSlug: (await readTriageSettings()).sentryOrgSlug,
project: project.projectId,
query: 'is:unresolved',
statsPeriod: '14d',
limit: 12,
})
context.siblings = (Array.isArray(issues) ? issues : [])
.filter((sibling) => String(sibling.id) !== String(issueId))
.slice(0, 10)
.map((sibling) => ({
id: sibling.id,
shortId: sibling.shortId,
count: sibling.count,
culprit: truncate(sibling.culprit ?? '', 80),
title: truncate(sibling.title ?? '', 90),
}))
} catch {
// Same degradation as above.
}
return context
}