fix(rectification): replay answered probes after evidence rescore (BUG-587, BUG-588)
Independent Staging Quality Gate / validate (push) Successful in 10m56s
Independent Staging Quality Gate / publish (push) Successful in 9m1s

Carry answered probe defs into the new candidate set, replay them by minute, and announce range changes on evidence turns.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Jesse_Chen
2026-09-08 10:38:51 +08:00
co-authored by Cursor
parent fec316b3e3
commit df182c16d6
15 changed files with 542 additions and 27 deletions
@@ -1,6 +1,6 @@
"use client";
import { useCallback, useEffect, useId, useLayoutEffect, useRef, useState } from "react";
import { startTransition, useCallback, useEffect, useId, useLayoutEffect, useRef, useState } from "react";
import { useQueuedMessage } from "@/hooks/use-queued-message";
import { appendQueuedText, queuedDraftSettleAction } from "@/lib/queued-draft";
import { createPortal } from "react-dom";
@@ -635,7 +635,9 @@ export function RectificationAgenticChat(props: RectificationAgenticChatProps) {
}
}, []);
const loadCaseSnapshot = useCallback(async (): Promise<{
const loadCaseSnapshot = useCallback(async (
mergeMessages?: (turns: readonly unknown[]) => void,
): Promise<{
question: CurrentQuestionModel | null;
turns: readonly unknown[];
} | null | undefined> => {
@@ -650,10 +652,14 @@ export function RectificationAgenticChat(props: RectificationAgenticChatProps) {
if (!response.ok) return undefined;
const payload = await response.json().catch(() => null);
if (controller.signal.aborted) return undefined;
applyCaseSnapshot(payload);
const turns = snapshotTurns(payload);
startTransition(() => {
applyCaseSnapshot(payload);
mergeMessages?.(turns);
});
return {
question: currentQuestionFromSnapshot(payload?.current_question),
turns: snapshotTurns(payload),
turns,
};
} catch {
// Snapshot refresh is best-effort; the durable Case remains on the server.
@@ -960,10 +966,11 @@ export function RectificationAgenticChat(props: RectificationAgenticChatProps) {
}
if (succeeded) {
runOutcome = "succeeded";
const snapshot = await loadCaseSnapshot();
if (snapshot?.turns.length) {
setMessages((current) => mergeTurnQuestions(current, snapshot.turns));
}
await loadCaseSnapshot((turns) => {
if (turns.length) {
setMessages((current) => mergeTurnQuestions(current, turns));
}
});
onMessagesChange?.([
...(action === "message" ? [{ role: "user" as const, text: trimmed }] : []),
{ role: "assistant", text: parsed.text },
@@ -1121,15 +1128,11 @@ export function RectificationAgenticChat(props: RectificationAgenticChatProps) {
: "";
if (payloadNextActionId) setNextUserActionId(payloadNextActionId);
onCompleted?.();
const snapshot = await loadCaseSnapshot();
const turns = snapshot?.turns ?? [];
if (willContinue) {
// The follow-up turn continues on the row already in place: no removed
// row, no effect hop, and `busy` never drops in between (so the next
// card cannot flash before the turn hides it).
setMessages((current) => mergeTurnQuestions(current, turns));
await send("read_only", "", { reuseAssistantRenderKey: assistantRenderKey, label: recordingLabel });
} else {
await loadCaseSnapshot((turns) => {
if (willContinue) {
setMessages((current) => mergeTurnQuestions(current, turns));
return;
}
const narration = typeof payload?.narration === "string" && payload.narration.trim()
? payload.narration.trim()
: "已记录你的选择。";
@@ -1151,6 +1154,16 @@ export function RectificationAgenticChat(props: RectificationAgenticChatProps) {
},
];
});
});
if (willContinue) {
// The follow-up turn continues on the row already in place: no removed
// row, no effect hop, and `busy` never drops in between (so the next
// card cannot flash before the turn hides it).
await send("read_only", "", { reuseAssistantRenderKey: assistantRenderKey, label: recordingLabel });
} else {
const narration = typeof payload?.narration === "string" && payload.narration.trim()
? payload.narration.trim()
: "已记录你的选择。";
onMessagesChange?.([
{ role: "assistant", text: narration },
]);
@@ -1400,6 +1413,8 @@ export function RectificationAgenticChat(props: RectificationAgenticChatProps) {
?? (canOfferCards && !candidateResult?.selectedTime ? latestSettledAssistant?.renderKey : undefined);
const showSelectionCards = Boolean(
candidateResult
&& caseSnapshotLoaded
&& !busy
&& selectionCardMessageKey
&& (canOfferCards || Boolean(candidateResult.selectedTime))
&& !messages.some((message) => (