fix(rectification): simplify delivery card and guard double turns (BUG-595, BUG-596)
Independent Staging Quality Gate / validate (push) Successful in 25m9s
Independent Staging Quality Gate / publish (push) Successful in 37m19s

Make the range card row-select, drop the composer 先这样 control and adopt status bar, keep delivery copy to three sentences with a folded verification report, and skip a second no-message agent run after a terminal delivery turn.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Jesse_Chen
2026-09-08 19:56:48 +08:00
co-authored by Cursor
parent 6368ebba0f
commit 7e3b6cdc7a
60 changed files with 1561 additions and 1105 deletions
@@ -50,6 +50,7 @@ import {
RECTIFICATION_ACTION_EXECUTION,
type RectificationRouteAction,
} from "@/lib/rectification-agentic/v9/turn-exit";
import { logRectificationDeliveryTurn } from "@/lib/rectification-agentic/v9/delivery-turn-guard";
export const runtime = "nodejs";
export const maxDuration = 240;
@@ -779,6 +780,11 @@ export async function POST(request: Request) {
try {
if (action === "opening") {
logRectificationDeliveryTurn({
trigger: "opening",
caseId,
terminalNote: false,
});
try {
await persistNextInterviewIfIdle({
accounting: accounting as never,
@@ -842,7 +848,9 @@ export async function POST(request: Request) {
},
});
if (!result.ok) {
if (result.errorCode === "already_delivered") {
send({ type: "done", emitted: true });
} else if (!result.ok) {
send({ type: "error", message: "生时校正暂时不可用,请稍后重试。" });
} else {
// Shared gate owns persistNextInterviewIfIdle then ensureNonTerminalTurnExit;
@@ -23,6 +23,7 @@ import { publicDecisionFields } from "@/lib/rectification-agentic/core/rectifica
import { collectionProgressFromReceipt } from "@/lib/rectification-agentic/v9/evidence-model";
import { slimDecisionReceipt } from "@/lib/rectification-agentic/v9/case-receipt-projection";
import { rangeDeliveryForSnapshot } from "@/lib/rectification-agentic/v9/divergence-panel";
import { latestResultToolProjection } from "@/mastra/rectification-v9-tools";
export const runtime = "nodejs";
@@ -166,6 +167,7 @@ function publicLatestResult(
) {
if (!latest) return null;
const projected = overlayPublicDecision(latest, decision);
const toolProjection = latestResultToolProjection(latest, decision);
const rangeDelivery = rangeDeliveryForSnapshot({
decisionReceipt: projected.decisionReceipt,
candidates: projected.candidates,
@@ -173,6 +175,7 @@ function publicLatestResult(
representative_time: projected.representativeTime ?? decision.representativeTime,
credibleRange: projected.credible_range ?? decision.credibleRange,
credible_range: projected.credible_range ?? decision.credibleRange,
skill_verification_report: toolProjection.skill_verification_report,
});
const withDelivery = {
...projected,
@@ -11,6 +11,7 @@ import { jsonForSupabaseSetupFailure } from "@/lib/api/service-unavailable";
import { createAdminSupabaseClient } from "@/lib/supabase/admin";
import { createServerSupabaseClient } from "@/lib/supabase/server";
import { getRectificationV9RegenerationAgent } from "@/mastra/agentic-rectification";
import { logRectificationDeliveryTurn } from "@/lib/rectification-agentic/v9/delivery-turn-guard";
export const runtime = "nodejs";
export const maxDuration = 240;
@@ -128,6 +129,12 @@ export async function POST(request: Request, context: RouteContext) {
turnId,
accounting,
}, skillPackage);
logRectificationDeliveryTurn({
trigger: "regenerate",
caseId,
resultId: turnId,
terminalNote: false,
});
const result = await regenerateV9AssistantTurn({
userId: user.id,
caseId,
+57 -158
View File
@@ -3083,24 +3083,6 @@ input:not([type="radio"]):not([type="checkbox"]):not([class^="ant-"]):not([class
.rectification-choice-why-user p {
margin: var(--space-2) 0 0;
}
.rectification-composer-meta {
display: grid;
gap: var(--space-3);
margin: 0 0 var(--space-3);
}
.rectification-composer-meta .rectification-collect-stop {
margin: 0;
}
.rectification-continue-hint {
margin: 0;
color: var(--color-ink-secondary);
font-size: var(--type-caption);
line-height: 1.5;
}
.rectification-continue-hint strong {
color: var(--color-ink);
font-weight: 600;
}
.rectification-candidates-heading { display: grid; gap: var(--space-1); }
.rectification-candidates-heading strong { font-size: var(--type-title-sm); font-family: var(--font-display); font-weight: 600; letter-spacing: -.2px; }
.rectification-candidates-heading span,
@@ -3152,55 +3134,21 @@ input:not([type="radio"]):not([type="checkbox"]):not([class^="ant-"]):not([class
display: grid;
gap: var(--space-4);
}
.rectification-range-delivery__range {
font-size: var(--type-title-md);
font-family: var(--font-display);
font-weight: 600;
font-variant-numeric: tabular-nums;
letter-spacing: -.2px;
}
.rectification-range-delivery__facts {
display: grid;
grid-template-columns: auto minmax(0, 1fr);
gap: var(--space-2) var(--space-3);
margin: 0;
}
.rectification-range-delivery__facts dt {
color: var(--color-ink-secondary);
font-size: var(--type-caption);
}
.rectification-range-delivery__facts dd {
margin: 0;
font-size: var(--type-body-sm);
}
.rectification-range-delivery__boundary {
margin: 0;
color: var(--color-ink-secondary);
font-size: var(--type-caption);
line-height: 1.5;
}
.rectification-divergence {
display: grid;
gap: var(--space-3);
}
.rectification-divergence__title {
margin: 0;
color: var(--color-ink-secondary);
font-size: var(--type-caption);
}
.rectification-divergence-list {
display: grid;
width: 100%;
min-width: 0;
grid-template-columns: repeat(3, minmax(0, 1fr));
gap: var(--space-3);
}
.rectification-divergence-column {
.rectification-range-delivery__rows {
display: grid;
gap: var(--space-2);
min-width: 0;
margin: 0;
padding: 0;
list-style: none;
}
.rectification-range-delivery__row {
display: grid;
grid-template-columns: auto minmax(0, 1fr) auto;
gap: var(--space-3);
align-items: center;
width: 100%;
min-height: 44px;
padding: var(--space-4);
padding: 10px 12px;
border: 1px solid var(--color-border);
border-radius: var(--radius-lg);
color: var(--color-ink);
@@ -3209,59 +3157,60 @@ input:not([type="radio"]):not([type="checkbox"]):not([class^="ant-"]):not([class
text-align: left;
cursor: pointer;
}
.rectification-divergence-column strong {
font-size: var(--type-body-sm);
font-variant-numeric: tabular-nums;
.rectification-range-delivery__row.is-representative,
.rectification-range-delivery__row[aria-pressed="true"] {
border-color: color-mix(in srgb, var(--color-action) 55%, var(--color-border));
background: color-mix(in srgb, var(--color-action-soft) 60%, var(--color-canvas));
}
.rectification-divergence-column span {
.rectification-range-delivery__row:focus-visible {
outline: 2px solid var(--color-focus);
outline-offset: 2px;
}
.rectification-range-delivery__time {
font-variant-numeric: tabular-nums;
font-weight: 600;
}
.rectification-range-delivery__diff {
color: var(--color-ink-secondary);
font-size: var(--type-caption);
line-height: 1.5;
}
.rectification-divergence-column.is-selected {
border-color: color-mix(in srgb, var(--color-action) 55%, var(--color-border));
background: color-mix(in srgb, var(--color-action-soft) 60%, var(--color-canvas));
}
.rectification-divergence-column:focus-visible { outline: 2px solid var(--color-focus); outline-offset: 2px; }
.rectification-divergence-clear,
.rectification-range-delivery__actions {
display: flex;
flex-wrap: wrap;
gap: var(--space-3);
}
.rectification-range-delivery__adopt,
.rectification-range-delivery__continue,
.rectification-range-delivery__quiet {
min-height: 44px;
padding: 0 var(--space-4);
border: 1px solid var(--color-border);
border-radius: var(--radius-lg);
background: var(--color-canvas);
color: var(--color-ink);
font: inherit;
.rectification-range-delivery__status {
color: var(--color-action);
font-size: var(--type-caption);
font-weight: 600;
cursor: pointer;
}
.rectification-range-delivery__adopt {
color: var(--color-on-dark);
background: var(--color-surface-dark);
border-color: var(--color-surface-dark);
}
.rectification-range-delivery__adopt:hover:not(:disabled),
.rectification-range-delivery__continue:hover:not(:disabled),
.rectification-range-delivery__quiet:hover:not(:disabled) {
border-color: color-mix(in srgb, var(--color-action) 45%, var(--color-border));
}
.rectification-range-delivery__adopt:focus-visible,
.rectification-range-delivery__continue:focus-visible,
.rectification-range-delivery__quiet:focus-visible {
outline: 2px solid var(--color-focus);
outline-offset: 2px;
}
.rectification-range-delivery__adopted {
.rectification-range-delivery__boundary {
margin: 0;
color: var(--color-ink-secondary);
font-size: var(--type-body-sm);
font-size: var(--type-caption);
line-height: 1.5;
}
.rectification-verification-report {
margin: 0;
}
.rectification-verification-report summary {
cursor: pointer;
min-height: 44px;
display: flex;
align-items: center;
color: var(--color-ink-secondary);
font-size: var(--type-caption);
}
.rectification-verification-report__body {
margin-top: var(--space-3);
overflow-x: auto;
}
.rectification-verification-report__body table {
width: 100%;
border-collapse: collapse;
}
.rectification-verification-report__body th,
.rectification-verification-report__body td {
padding: 6px 8px;
border: 1px solid var(--color-border);
text-align: left;
font-size: var(--type-caption);
}
.rectification-saved {
width: calc(100% - var(--assistant-content-inset));
@@ -3322,58 +3271,11 @@ input:not([type="radio"]):not([type="checkbox"]):not([class^="ant-"]):not([class
line-height: 1.5;
text-align: left;
}
.rectification-collect-stop {
display: flex;
align-items: center;
justify-content: center;
min-height: 44px;
width: 100%;
margin: 0 0 var(--space-3);
padding: 0 12px;
border: 1px solid var(--color-border);
border-radius: var(--radius-lg);
background: var(--color-canvas);
color: var(--color-ink-secondary);
font: inherit;
font-size: var(--type-body-sm);
cursor: pointer;
}
.rectification-question-skipped {
margin: var(--space-2) 0 0;
color: var(--color-ink-secondary);
font-size: var(--type-caption);
}
.rectification-adopt-status {
display: flex;
flex-wrap: wrap;
align-items: center;
gap: var(--space-3);
margin: 0 0 var(--space-3);
padding: 10px 12px;
border: 1px solid var(--color-border);
border-radius: var(--radius-lg);
background: var(--color-canvas-soft);
color: var(--color-ink);
font-size: var(--type-body-sm);
}
.rectification-adopt-status__link {
padding: 0;
border: 0;
background: transparent;
color: var(--color-action);
font: inherit;
font-weight: 600;
cursor: pointer;
}
.rectification-adopt-status__link:focus-visible {
outline: 2px solid var(--color-focus);
outline-offset: 2px;
}
.rectification-adopt-status__hint {
flex: 1 1 100%;
color: var(--color-ink-secondary);
font-size: var(--type-caption);
}
.rectification-house-table {
display: grid;
gap: var(--space-3);
@@ -3407,9 +3309,6 @@ input:not([type="radio"]):not([type="checkbox"]):not([class^="ant-"]):not([class
scroll-snap-type: x proximity;
}
.rectification-candidate { min-height: 122px; padding: 12px; scroll-snap-align: start; }
.rectification-divergence-list {
grid-template-columns: minmax(0, 1fr);
}
}
/* The QR stays on literal white in both themes: scanners need the light modules