mirror of
https://github.com/paperclipai/paperclip.git
synced 2026-10-24 18:19:05 +02:00
38 lines
2.8 KiB
TypeScript
38 lines
2.8 KiB
TypeScript
import { createHash } from "node:crypto";
|
|
import { pollUntil } from "./api.js";
|
|
import type { FinalizationCrashBinding } from "./finalization-crash-boundary.js";
|
|
|
|
type Row = Record<string, any>;
|
|
export function inspectFinalizationDelivery(input: { binding: FinalizationCrashBinding; agentId: string; run: Row; comments: Row[] }) {
|
|
const { run, binding } = input, presentation = run.resultJson?.presentationDecision;
|
|
const replies = input.comments.filter(comment => comment.companyId === binding.companyId && comment.issueId === binding.issueId &&
|
|
comment.authorAgentId === input.agentId && comment.createdByRunId === binding.runId && !comment.deletedAt);
|
|
const receiptMatches = run.id === binding.runId && run.companyId === binding.companyId && run.nativeIssueId === binding.issueId &&
|
|
run.agentId === input.agentId && run.issueCommentStatus === "satisfied" && replies.length === 1 &&
|
|
typeof replies[0]!.body === "string" && Boolean(replies[0]!.body.trim()) && run.issueCommentSatisfiedByCommentId === replies[0]!.id &&
|
|
presentation?.schema === "paperclip.run_presentation_decision.v1" && presentation.commentId === replies[0]!.id &&
|
|
["create", "reuse"].includes(presentation.commentAction) &&
|
|
["existing_issue_comment", "final_agent_message", "semantic_result_summary", "adapter_final_response"].includes(presentation.chosenSource);
|
|
return { finalDeliveryCount: replies.length, deliveryReceiptMatches: receiptMatches,
|
|
presentation: { commentId: typeof presentation?.commentId === "string" ? presentation.commentId : null,
|
|
chosenSource: typeof presentation?.chosenSource === "string" ? presentation.chosenSource : null },
|
|
replies: replies.map(comment => ({ id: comment.id, authorAgentId: comment.authorAgentId, createdByRunId: comment.createdByRunId,
|
|
bodySha256: createHash("sha256").update(String(comment.body ?? "").trim()).digest("hex") })) };
|
|
}
|
|
|
|
export async function waitForFinalizationDelivery(input: {
|
|
binding: FinalizationCrashBinding; agentId: string; deadlineAt: number; intervalMs?: number;
|
|
load(): Promise<{ run: Row; comments: Row[]; runCount: number }>;
|
|
}) {
|
|
return pollUntil({ label: "one persisted final reply and its matching delivery receipt", deadlineAt: input.deadlineAt,
|
|
intervalMs: input.intervalMs ?? 500,
|
|
load: async () => {
|
|
const state = await input.load();
|
|
return { ...state, delivery: inspectFinalizationDelivery({ ...input, ...state }) };
|
|
},
|
|
accept: state => state.runCount === 1 && state.delivery.deliveryReceiptMatches,
|
|
reject: state => state.runCount !== 1 ? "Final delivery repeated provider admission" :
|
|
state.delivery.finalDeliveryCount > 1 ? "Recovery produced duplicate final replies" :
|
|
["failed", "cancelled", "timed_out"].includes(state.run.status) ? "Recovered run failed before delivery" : undefined });
|
|
}
|