mirror of
https://github.com/paperclipai/paperclip.git
synced 2026-10-26 18:31:34 +01:00
fix: combine partial feedback receipts
Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
1 parent
d3366b1a8e
commit
b49c032da2
4 files changed
+49
-10
No files matched your search
@@ -238,6 +238,7 @@ import {
|
||||
buildFeedbackDeliveryFingerprint,
|
||||
buildFeedbackDeliveryRetryIdempotencyKey,
|
||||
carriesExplicitFeedbackResume,
|
||||
collectFeedbackDispositionHandledCommentIds,
|
||||
isEligibleFeedbackDeliveryWake,
|
||||
isSuccessfulFeedbackDeliveryRecoveryMatch,
|
||||
remainingFeedbackDispositionCommentIds,
|
||||
@@ -10211,7 +10212,7 @@ export function heartbeatService(db: Db, options: HeartbeatServiceOptions = {})
|
||||
run: typeof heartbeatRuns.$inferSelect,
|
||||
delivery: FeedbackDeliveryContext,
|
||||
) {
|
||||
const comment = await db
|
||||
const comments = await db
|
||||
.select({ id: issueComments.id, metadata: issueComments.metadata })
|
||||
.from(issueComments)
|
||||
.where(and(
|
||||
@@ -10221,7 +10222,7 @@ export function heartbeatService(db: Db, options: HeartbeatServiceOptions = {})
|
||||
isNull(issueComments.deletedAt),
|
||||
))
|
||||
.orderBy(desc(issueComments.createdAt), desc(issueComments.id))
|
||||
.then((rows) => rows.find((row) => {
|
||||
.then((rows) => rows.filter((row) => {
|
||||
const disposition = row.metadata?.feedbackDisposition;
|
||||
if (
|
||||
disposition?.kind !== "feedback_delivery"
|
||||
@@ -10231,17 +10232,21 @@ export function heartbeatService(db: Db, options: HeartbeatServiceOptions = {})
|
||||
deliveryCommentIds: delivery.commentIds,
|
||||
handledCommentIds: disposition.handledCommentIds,
|
||||
}) !== null;
|
||||
}) ?? null);
|
||||
if (!comment) return null;
|
||||
const disposition = comment.metadata?.feedbackDisposition;
|
||||
if (!disposition) return null;
|
||||
}));
|
||||
if (comments.length === 0) return null;
|
||||
const handledCommentIds = collectFeedbackDispositionHandledCommentIds({
|
||||
deliveryCommentIds: delivery.commentIds,
|
||||
rootWakeupRequestId: delivery.rootWakeupRequestId,
|
||||
dispositions: comments.map((comment) => comment.metadata?.feedbackDisposition),
|
||||
});
|
||||
// Only an explicit, persisted disposition can acknowledge feedback. Ordinary
|
||||
// run comments and other same-run issue activity can be unrelated.
|
||||
// run comments and other same-run issue activity can be unrelated. A run may
|
||||
// post several partial receipts, so combine all valid receipts for this root.
|
||||
return {
|
||||
kind: "comment",
|
||||
id: comment.id,
|
||||
rootWakeupRequestId: disposition.rootWakeupRequestId,
|
||||
sourceCommentIds: disposition.handledCommentIds,
|
||||
id: comments[0]!.id,
|
||||
rootWakeupRequestId: delivery.rootWakeupRequestId,
|
||||
sourceCommentIds: handledCommentIds,
|
||||
} as const;
|
||||
}
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@ import {
|
||||
STRANDED_FEEDBACK_DELIVERY_BACKSTOP_SOURCE,
|
||||
buildFeedbackDeliveryFingerprint,
|
||||
buildFeedbackDeliveryRetryIdempotencyKey,
|
||||
collectFeedbackDispositionHandledCommentIds,
|
||||
decideStrandedFeedbackDeliveryBackstop,
|
||||
hasCompleteFeedbackDispositionCoverage,
|
||||
isSuccessfulFeedbackDeliveryRecoveryMatch,
|
||||
@@ -203,6 +204,18 @@ describe("feedback disposition coverage", () => {
|
||||
handledCommentIds,
|
||||
})).toBeNull();
|
||||
});
|
||||
|
||||
it("combines complementary partial receipts from one run", () => {
|
||||
expect(collectFeedbackDispositionHandledCommentIds({
|
||||
deliveryCommentIds: ["comment-1", "comment-2", "comment-3"],
|
||||
rootWakeupRequestId: "wake-root",
|
||||
dispositions: [
|
||||
{ rootWakeupRequestId: "wake-root", handledCommentIds: ["comment-1"] },
|
||||
{ rootWakeupRequestId: "wake-root", handledCommentIds: ["comment-3"] },
|
||||
{ rootWakeupRequestId: "other-root", handledCommentIds: ["comment-2"] },
|
||||
],
|
||||
})).toEqual(["comment-1", "comment-3"]);
|
||||
});
|
||||
});
|
||||
|
||||
describe("manual feedback retry attempts", () => {
|
||||
|
||||
@@ -130,6 +130,26 @@ export function remainingFeedbackDispositionCommentIds(input: {
|
||||
return [...deliveryIds].filter((commentId) => !handledIds.has(commentId));
|
||||
}
|
||||
|
||||
export function collectFeedbackDispositionHandledCommentIds(input: {
|
||||
deliveryCommentIds: string[];
|
||||
rootWakeupRequestId: string;
|
||||
dispositions: Array<{
|
||||
rootWakeupRequestId: string;
|
||||
handledCommentIds: string[];
|
||||
} | null | undefined>;
|
||||
}) {
|
||||
const handledIds = new Set<string>();
|
||||
for (const disposition of input.dispositions) {
|
||||
if (!disposition || disposition.rootWakeupRequestId !== input.rootWakeupRequestId) continue;
|
||||
if (remainingFeedbackDispositionCommentIds({
|
||||
deliveryCommentIds: input.deliveryCommentIds,
|
||||
handledCommentIds: disposition.handledCommentIds,
|
||||
}) === null) continue;
|
||||
for (const commentId of disposition.handledCommentIds) handledIds.add(commentId);
|
||||
}
|
||||
return input.deliveryCommentIds.filter((commentId) => handledIds.has(commentId));
|
||||
}
|
||||
|
||||
export function isSuccessfulFeedbackDeliveryRecoveryMatch(input: {
|
||||
handlingEvidence: {
|
||||
rootWakeupRequestId: string;
|
||||
|
||||
@@ -40,6 +40,7 @@ export {
|
||||
buildFeedbackDeliveryFingerprint,
|
||||
buildFeedbackDeliveryRetryIdempotencyKey,
|
||||
carriesExplicitFeedbackResume,
|
||||
collectFeedbackDispositionHandledCommentIds,
|
||||
decideStrandedFeedbackDeliveryBackstop,
|
||||
hasCompleteFeedbackDispositionCoverage,
|
||||
isEligibleFeedbackDeliveryWake,
|
||||
|
||||
Reference in new issue
Block a user