fix(server): recover lost human feedback delivery

Replay feedback lost before process launch, reconcile stranded review feedback, and project durable delivery state onto source comments.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
DottaandPaperclip committed 2026-08-16 20:05:21 +00:00
1 parent 623f7aa555
commit a6c28fa414
16 files changed
+3762 -33

No files matched your search

+18
View File
@@ -376,6 +376,7 @@ export const ISSUE_SURFACE_VISIBILITIES = ["default", "plugin_operation"] as con
export type IssueSurfaceVisibility = (typeof ISSUE_SURFACE_VISIBILITIES)[number];
export const ISSUE_RECOVERY_ACTION_KINDS = [
"feedback_delivery",
"missing_disposition",
"stranded_assigned_issue",
"workspace_validation",
@@ -401,6 +402,23 @@ export const ISSUE_RECOVERY_ACTION_OWNER_TYPES = [
] as const;
export type IssueRecoveryActionOwnerType = (typeof ISSUE_RECOVERY_ACTION_OWNER_TYPES)[number];
/**
* Delivery state of one human feedback comment, as projected onto that comment
* for operator surfaces. This is deliberately *not* a task status: the task
* keeps its own workflow status while a delivery attempt is in flight.
*
* - `retrying` — automatic replay is still in flight; no operator action.
* - `exhausted_needs_attention` — automatic replay gave up; a human must act.
* - `recovered_after_attention` — delivery succeeded after it had needed
* attention, so the resolution stays visible in thread history.
*/
export const ISSUE_FEEDBACK_DELIVERY_STATES = [
"retrying",
"exhausted_needs_attention",
"recovered_after_attention",
] as const;
export type IssueFeedbackDeliveryState = (typeof ISSUE_FEEDBACK_DELIVERY_STATES)[number];
export const ISSUE_RECOVERY_ACTION_OUTCOMES = [
"restored",
"handed_back",
+4
View File
@@ -299,6 +299,7 @@ export {
ONBOARDING_FIRST_TASK_ORIGIN_KIND,
ISSUE_WATCHDOG_DISCOVERY_KINDS,
ISSUE_SURFACE_VISIBILITIES,
ISSUE_FEEDBACK_DELIVERY_STATES,
ISSUE_RECOVERY_ACTION_KINDS,
ISSUE_RECOVERY_ACTION_STATUSES,
ISSUE_RECOVERY_ACTION_OWNER_TYPES,
@@ -492,6 +493,7 @@ export {
type IssueOriginKind,
type IssueWatchdogDiscoveryKind,
type IssueSurfaceVisibility,
type IssueFeedbackDeliveryState,
type IssueRecoveryActionKind,
type IssueRecoveryActionStatus,
type IssueRecoveryActionOwnerType,
@@ -1008,6 +1010,8 @@ export type {
IssueExecutionStagePrincipal,
IssueExecutionDecision,
IssueComment,
IssueCommentFeedbackDelivery,
IssueFeedbackDeliveryRetryResponse,
IssueCommentDerivedAuthorSource,
IssueCommentMetadata,
IssueCommentMetadataSection,
+2
View File
@@ -649,6 +649,8 @@ export type {
IssueReviewRequest,
IssueExecutionDecision,
IssueComment,
IssueCommentFeedbackDelivery,
IssueFeedbackDeliveryRetryResponse,
IssueCommentDerivedAuthorSource,
IssueCommentMetadata,
IssueCommentMetadataSection,
+55
View File
@@ -14,6 +14,7 @@ import type {
IssueReferenceSourceKind,
IssueExecutionStageType,
IssueExecutionStateStatus,
IssueFeedbackDeliveryState,
IssueHarnessKind,
IssueOriginKind,
IssuePriority,
@@ -941,6 +942,54 @@ export type IssueCommentDerivedAuthorSource =
| "run_id"
| "run_log_comment_post";
/**
* Delivery state projected onto a single human feedback comment.
*
* Every field here is safe to render to any operator who can already read the
* comment: raw adapter errors, stack traces, request bodies, credentials, and
* filesystem paths are mapped to a short `safeFailureReason` code server-side
* and never passed through verbatim.
*/
export interface IssueCommentFeedbackDelivery {
state: IssueFeedbackDeliveryState;
/** The human comment this delivery obligation belongs to. */
sourceCommentId: string;
/** Completed automatic replay generations, or `null` when unknown. */
attempt: number | null;
/** Automatic replay budget, or `null` when unknown. */
maxAttempts: number | null;
nextAttemptAt: string | null;
lastAttemptAt: string | null;
deliveredAt: string | null;
/**
* Run the operator may open for context. Omitted (`null`) when no run exists
* or the viewer is not authorized to read it — never rendered as a disabled
* mystery action.
*/
targetRunId: string | null;
/** True when the viewer may ask for one more explicit delivery attempt. */
canRetry: boolean;
/**
* True when a replay is already queued for this obligation. Keeps the
* "Retrying…" affordance durable across reloads instead of living only in
* button-local component state.
*/
retryInFlight: boolean;
/** Short operator-safe reason code, never a raw adapter error. */
safeFailureReason: string | null;
/**
* How many human comments share this one delivery obligation (a batched wake
* can cover several). Used for the thread rollup copy.
*/
batchedCommentCount: number;
}
export interface IssueFeedbackDeliveryRetryResponse {
outcome: "queued" | "already_queued" | "not_exhausted" | "no_invokable_assignee";
message: string;
feedbackDelivery: IssueCommentFeedbackDelivery | null;
}
export interface IssueComment {
id: string;
companyId: string;
@@ -964,6 +1013,12 @@ export interface IssueComment {
deletedByRunId?: string | null;
sourceTrust?: SourceTrustMetadata | null;
followUpRequested?: boolean;
/**
* Present only while this human comment's delivery is retrying, exhausted, or
* freshly recovered. Absent for the overwhelming majority of comments, which
* were delivered on the first attempt.
*/
feedbackDelivery?: IssueCommentFeedbackDelivery | null;
createdAt: Date;
updatedAt: Date;
}