From 27dd2d5cc78191fc10eb935e315588c78ef3d82c Mon Sep 17 00:00:00 2001 From: Dotta Date: Sun, 12 Jul 2026 16:46:34 +0000 Subject: [PATCH] fix(adapters): preserve issue reports through delivery Co-Authored-By: Paperclip --- .../adapter-utils/src/server-utils.test.ts | 30 +++++++++++ packages/adapter-utils/src/server-utils.ts | 54 ++++++++++++++++++- server/src/services/heartbeat.ts | 1 - 3 files changed, 83 insertions(+), 2 deletions(-) diff --git a/packages/adapter-utils/src/server-utils.test.ts b/packages/adapter-utils/src/server-utils.test.ts index 7c7071030b4..a61adaaebce 100644 --- a/packages/adapter-utils/src/server-utils.test.ts +++ b/packages/adapter-utils/src/server-utils.test.ts @@ -821,6 +821,36 @@ describe("renderPaperclipWakePrompt", () => { ); }); + it("preserves cross-issue reports in adapter payloads and prompts", () => { + const payload = { + reports: [ + { + id: "report-1", + targetIssueId: "issue-target", + originIssueId: "issue-origin", + originRunId: "run-origin", + originAgentId: "agent-origin", + fingerprint: "audit-result", + payload: { + type: "audit.result", + summary: "Audit passed", + data: { checks: 9 }, + }, + wakeRequested: true, + createdAt: "2026-07-12T16:00:00.000Z", + }, + ], + }; + + const serialized = stringifyPaperclipWakePayload(payload); + expect(JSON.parse(serialized ?? "{}")).toMatchObject(payload); + + const prompt = renderPaperclipWakePrompt(payload); + expect(prompt).toContain("Cross-issue reports in delivery order:"); + expect(prompt).toContain("report report-1 from issue issue-origin run run-origin"); + expect(prompt).toContain('"summary":"Audit passed"'); + }); + it("escapes backticks and strips control characters in the branch guard", () => { const prompt = renderPaperclipWakePrompt({ reason: "issue_assigned", diff --git a/packages/adapter-utils/src/server-utils.ts b/packages/adapter-utils/src/server-utils.ts index 5a4c1e04cfa..ddeda74f3a3 100644 --- a/packages/adapter-utils/src/server-utils.ts +++ b/packages/adapter-utils/src/server-utils.ts @@ -615,6 +615,18 @@ type PaperclipWakeExecutionWorkspace = { branchName: string | null; }; +type PaperclipWakeReport = { + id: string; + targetIssueId: string; + originIssueId: string; + originRunId: string; + originAgentId: string; + fingerprint: string; + payload: Record; + wakeRequested: boolean; + createdAt: string | null; +}; + type PaperclipWakePayload = { reason: string | null; issue: PaperclipWakeIssue | null; @@ -639,6 +651,7 @@ type PaperclipWakePayload = { commentIds: string[]; latestCommentId: string | null; comments: PaperclipWakeComment[]; + reports: PaperclipWakeReport[]; requestedCount: number; includedCount: number; missingCount: number; @@ -681,6 +694,28 @@ function normalizePaperclipWakeComment(value: unknown): PaperclipWakeComment | n }; } +function normalizePaperclipWakeReport(value: unknown): PaperclipWakeReport | null { + const report = parseObject(value); + const id = asString(report.id, "").trim(); + const targetIssueId = asString(report.targetIssueId, "").trim(); + const originIssueId = asString(report.originIssueId, "").trim(); + const originRunId = asString(report.originRunId, "").trim(); + const originAgentId = asString(report.originAgentId, "").trim(); + const fingerprint = asString(report.fingerprint, "").trim(); + if (!id || !targetIssueId || !originIssueId || !originRunId || !originAgentId || !fingerprint) return null; + return { + id, + targetIssueId, + originIssueId, + originRunId, + originAgentId, + fingerprint, + payload: parseObject(report.payload), + wakeRequested: asBoolean(report.wakeRequested, false), + createdAt: asString(report.createdAt, "").trim() || null, + }; +} + function normalizePaperclipWakePlanReviewAuthor(value: unknown): PaperclipWakePlanReviewAuthor | null { const author = parseObject(value); const type = asString(author.type, "").trim() || null; @@ -1175,6 +1210,11 @@ export function normalizePaperclipWakePayload(value: unknown): PaperclipWakePayl .map((entry) => normalizePaperclipWakeComment(entry)) .filter((entry): entry is PaperclipWakeComment => Boolean(entry)) : []; + const reports = Array.isArray(payload.reports) + ? payload.reports + .map((entry) => normalizePaperclipWakeReport(entry)) + .filter((entry): entry is PaperclipWakeReport => Boolean(entry)) + : []; const commentWindow = parseObject(payload.commentWindow); const commentIds = Array.isArray(payload.commentIds) ? payload.commentIds @@ -1210,7 +1250,7 @@ export function normalizePaperclipWakePayload(value: unknown): PaperclipWakePayl const activeTreeHold = normalizePaperclipWakeTreeHoldSummary(payload.activeTreeHold); const checkboxSelection = normalizePaperclipWakeCheckboxSelection(payload.checkboxSelection); const executionWorkspace = normalizePaperclipWakeExecutionWorkspace(payload.executionWorkspace); - if (comments.length === 0 && commentIds.length === 0 && annotationDeltas.length === 0 && childIssueSummaries.length === 0 && unresolvedBlockerIssueIds.length === 0 && unresolvedBlockerSummaries.length === 0 && !activeTreeHold && !executionStage && !continuationSummary && !planReviewContext && !livenessContinuation && !taskWatchdog && !checkboxSelection && !executionWorkspace && !normalizePaperclipWakeIssue(payload.issue)) { + if (comments.length === 0 && reports.length === 0 && commentIds.length === 0 && annotationDeltas.length === 0 && childIssueSummaries.length === 0 && unresolvedBlockerIssueIds.length === 0 && unresolvedBlockerSummaries.length === 0 && !activeTreeHold && !executionStage && !continuationSummary && !planReviewContext && !livenessContinuation && !taskWatchdog && !checkboxSelection && !executionWorkspace && !normalizePaperclipWakeIssue(payload.issue)) { return null; } @@ -1238,6 +1278,7 @@ export function normalizePaperclipWakePayload(value: unknown): PaperclipWakePayl commentIds, latestCommentId: asString(payload.latestCommentId, "").trim() || null, comments, + reports, requestedCount: asNumber(commentWindow.requestedCount, comments.length || commentIds.length), includedCount: asNumber(commentWindow.includedCount, comments.length), missingCount: asNumber(commentWindow.missingCount, 0), @@ -1667,6 +1708,17 @@ export function renderPaperclipWakePrompt( lines.push(""); } + if (normalized.reports.length > 0) { + lines.push("Cross-issue reports in delivery order:"); + for (const [index, report] of normalized.reports.entries()) { + lines.push( + `${index + 1}. report ${report.id} from issue ${report.originIssueId} run ${report.originRunId}`, + JSON.stringify(report.payload), + "", + ); + } + } + return lines.join("\n").trim(); } diff --git a/server/src/services/heartbeat.ts b/server/src/services/heartbeat.ts index 063096cc660..83bc83053f5 100644 --- a/server/src/services/heartbeat.ts +++ b/server/src/services/heartbeat.ts @@ -12830,7 +12830,6 @@ export function heartbeatService(db: Db, options: HeartbeatServiceOptions = {}) : null, startedAt: meta.startedAt, }); - await acknowledgePendingReports(); }, authToken: authToken ?? undefined, });