Address attention queue review feedback

Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
DottaandPaperclip committed 2026-07-09 18:43:27 +00:00
1 parent aa5aa8ec6d
commit 4148ed5ca7
3 files changed
+92 -15

No files matched your search

@@ -569,6 +569,61 @@ describeEmbeddedPostgres("attention service", () => {
)).toBe(false);
});
it("suppresses exhausted failed runs when a newer run exists for the same agent and issue", async () => {
const { companyId, workerId } = await seedCompany("ATF");
const issueId = await insertIssue({
companyId,
identifier: "ATF-1",
title: "Retry target",
status: "in_progress",
assigneeAgentId: workerId,
updatedAt: new Date("2026-07-09T12:00:00.000Z"),
});
const exhaustedRunId = randomUUID();
const newerRunId = randomUUID();
await db.insert(heartbeatRuns).values([
{
id: exhaustedRunId,
companyId,
agentId: workerId,
invocationSource: "automation",
status: "failed",
error: "adapter failed",
errorCode: "adapter_failed",
contextSnapshot: { issueId },
createdAt: new Date("2026-07-09T12:01:00.000Z"),
updatedAt: new Date("2026-07-09T12:01:00.000Z"),
finishedAt: new Date("2026-07-09T12:01:00.000Z"),
},
{
id: newerRunId,
companyId,
agentId: workerId,
invocationSource: "automation",
status: "succeeded",
contextSnapshot: { issueId },
createdAt: new Date("2026-07-09T12:02:00.000Z"),
updatedAt: new Date("2026-07-09T12:02:00.000Z"),
finishedAt: new Date("2026-07-09T12:02:00.000Z"),
},
]);
await db.insert(heartbeatRunEvents).values({
companyId,
runId: exhaustedRunId,
agentId: workerId,
seq: 1,
eventType: "lifecycle",
message: "Bounded retry exhausted after 4 scheduled attempts; no further automatic retry will be queued",
payload: { retryReason: "transient_failure", maxAttempts: 4 },
createdAt: new Date("2026-07-09T12:01:01.000Z"),
});
const feed = await attentionService(db).list(companyId, { userId: "board-user" });
expect(feed.countsBySourceKind.failed_run).toBe(0);
expect(feed.items.some((item) => item.sourceKind === "failed_run" && item.subject.id === exhaustedRunId)).toBe(false);
});
it("uses inbox_dismissals with attention-prefixed dedup keys and resurfaces newer activity", async () => {
const { companyId } = await seedCompany("ATD");
const approvalId = randomUUID();
+36 -15
View File
@@ -253,6 +253,10 @@ function readRunIssueId(contextSnapshot: Record<string, unknown> | null) {
return typeof issueId === "string" && issueId.length > 0 ? issueId : null;
}
function runAttentionPairKey(agentId: string, issueId: string | null | undefined) {
return `${agentId}:${issueId ?? ""}`;
}
export function attentionService(db: Db) {
return {
list: async (companyId: string, options: AttentionListOptions = {}): Promise<AttentionFeed> => {
@@ -666,24 +670,41 @@ export function attentionService(db: Db) {
if (!latestExhaustedByRunId.has(row.id)) latestExhaustedByRunId.set(row.id, row);
}
const failedRows = [...latestExhaustedByRunId.values()];
const failedIssueMap = await issueSummaryMap(
db,
companyId,
failedRows.map((row) => readRunIssueId(row.contextSnapshot)),
);
for (const run of failedRows) {
const issueId = readRunIssueId(run.contextSnapshot);
const newer = await db
.select({ id: heartbeatRuns.id })
const failedIssueIdByRunId = new Map(failedRows.map((row) => [row.id, readRunIssueId(row.contextSnapshot)]));
const failedIssueMap = await issueSummaryMap(db, companyId, [...failedIssueIdByRunId.values()]);
const failedAgentIds = [...new Set(failedRows.map((row) => row.agentId))];
const earliestFailedAt = failedRows.reduce<Date | null>((earliest, row) => {
if (earliest == null || row.createdAt.getTime() < earliest.getTime()) return row.createdAt;
return earliest;
}, null);
const latestRunCreatedAtByPair = new Map<string, Date>();
if (earliestFailedAt && failedAgentIds.length > 0) {
const newerRunRows = await db
.select({
agentId: heartbeatRuns.agentId,
issueKey: sql<string>`coalesce(${heartbeatRuns.contextSnapshot} ->> 'issueId', ${heartbeatRuns.contextSnapshot} ->> 'taskId', '')`,
createdAt: heartbeatRuns.createdAt,
})
.from(heartbeatRuns)
.where(and(
eq(heartbeatRuns.companyId, companyId),
eq(heartbeatRuns.agentId, run.agentId),
gt(heartbeatRuns.createdAt, run.createdAt),
sql`coalesce(${heartbeatRuns.contextSnapshot} ->> 'issueId', ${heartbeatRuns.contextSnapshot} ->> 'taskId', '') = ${issueId ?? ""}`,
))
.limit(1);
if (newer.length > 0) continue;
inArray(heartbeatRuns.agentId, failedAgentIds),
gt(heartbeatRuns.createdAt, earliestFailedAt),
));
for (const row of newerRunRows) {
const key = runAttentionPairKey(row.agentId, row.issueKey);
const current = latestRunCreatedAtByPair.get(key);
if (!current || row.createdAt.getTime() > current.getTime()) {
latestRunCreatedAtByPair.set(key, row.createdAt);
}
}
}
for (const run of failedRows) {
const issueId = failedIssueIdByRunId.get(run.id) ?? null;
const latestRunCreatedAt = latestRunCreatedAtByPair.get(runAttentionPairKey(run.agentId, issueId));
if (latestRunCreatedAt && latestRunCreatedAt.getTime() > run.createdAt.getTime()) continue;
const issue = issueId ? failedIssueMap.get(issueId) ?? null : null;
const dedupKey = `run:${run.id}`;
+1
View File
@@ -90,6 +90,7 @@ export function useInboxDismissals(companyId: string | null | undefined) {
if (!companyId) return;
queryClient.invalidateQueries({ queryKey });
queryClient.invalidateQueries({ queryKey: queryKeys.sidebarBadges(companyId) });
queryClient.invalidateQueries({ queryKey: queryKeys.attention(companyId) });
},
});