fix(rectification): dedupe post-adopt verify and skip this probe only
Adopted reverse-verify repeated already-asked collect stems, treated 「这题跳过」 as stopping the case, and promised holdout/OOS checks that never ran. Collect spoken stems no longer prefix a year. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -70,12 +70,14 @@ import {
|
||||
import { userFacingRunFailure, isIncompleteRunBanner } from "@/lib/rectification-agentic/v9/run-diagnostic";
|
||||
import {
|
||||
CHOICE_ACTION,
|
||||
SKIP_PROBE_ACTION,
|
||||
STOP_ACTION,
|
||||
isStructuredChoiceUserText,
|
||||
shouldContinueAfterStructuredChoice,
|
||||
stableChoiceActionKey,
|
||||
type ChoiceOptionId,
|
||||
} from "@/lib/rectification-agentic/v9/choice-action";
|
||||
import { RECTIFICATION_USER_COPY } from "@/lib/rectification-agentic/user-copy";
|
||||
import { isRectificationCaseStatus, isResumableStatus, type RectificationCaseStatus } from "@/lib/rectification-agentic/v9/case-status";
|
||||
import { CHOICE_MODE, CHOICE_SKIP_QUESTION_LABEL, CHOICE_SKIP_QUESTION_MESSAGE, CHOICE_STOP_LABEL, CHOICE_STOP_MESSAGE, isPersistedFocusId, parseRectificationChoiceCard, type ChoiceKey, type RectificationChoiceCard as ChoiceCardModel } from "@/lib/rectification-agentic/v9/choice-card";
|
||||
import type { PublicLanguageModel } from "@/lib/public-models";
|
||||
@@ -272,7 +274,7 @@ function mergeTurnQuestions(current: RenderMessage[], turns: readonly unknown[])
|
||||
function markQuestionAnswered(
|
||||
current: RenderMessage[],
|
||||
focusId: string,
|
||||
selected: ChoiceKey | "stop" | "typed",
|
||||
selected: ChoiceKey | "stop" | "skip_probe" | "typed",
|
||||
): RenderMessage[] {
|
||||
return current.map((message) => {
|
||||
const question = message.question;
|
||||
@@ -329,6 +331,7 @@ function choiceCardFromQuestion(
|
||||
probe_id: question.probe_id,
|
||||
case_revision: null,
|
||||
focus_id: question.focus_id,
|
||||
...(live?.skip_this_probe ? { skip_this_probe: true } : {}),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -418,8 +421,14 @@ type CaseSnapshotState = Readonly<{
|
||||
caseStatus: RectificationCaseStatus | null;
|
||||
savedTime: string | null;
|
||||
savedStatus: "accepted" | "confirmed" | null;
|
||||
nextUserActionId: string | null;
|
||||
}>;
|
||||
|
||||
function nextUserActionIdFromSnapshot(payload: RectificationCaseSnapshotPayload | null): string | null {
|
||||
const id = payload?.next_user_action?.id;
|
||||
return typeof id === "string" && id.trim() ? id.trim() : null;
|
||||
}
|
||||
|
||||
/** The snapshot that arrived with the reveal, as initial state; nothing is fetched on mount. */
|
||||
function caseSnapshotState(payload: RectificationCaseSnapshotPayload | null): CaseSnapshotState | null {
|
||||
if (!payload) return null;
|
||||
@@ -433,6 +442,7 @@ function caseSnapshotState(payload: RectificationCaseSnapshotPayload | null): Ca
|
||||
caseStatus: isRectificationCaseStatus(payload.case?.status) ? payload.case.status : null,
|
||||
savedTime: confirmedTime ?? acceptedTime,
|
||||
savedStatus: confirmedTime ? "confirmed" : acceptedTime ? "accepted" : null,
|
||||
nextUserActionId: nextUserActionIdFromSnapshot(payload),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -470,6 +480,7 @@ export function RectificationAgenticChat(props: RectificationAgenticChatProps) {
|
||||
const [currentQuestion, setCurrentQuestion] = useState<CurrentQuestionModel | null>(() => caseSnapshotState(initialSnapshot)?.question ?? null);
|
||||
const [questionSource, setQuestionSource] = useState<"focus" | "unavailable" | null>(() => caseSnapshotState(initialSnapshot)?.questionSource ?? null);
|
||||
const [caseStatus, setCaseStatus] = useState<RectificationCaseStatus | null>(() => caseSnapshotState(initialSnapshot)?.caseStatus ?? null);
|
||||
const [nextUserActionId, setNextUserActionId] = useState<string | null>(() => caseSnapshotState(initialSnapshot)?.nextUserActionId ?? null);
|
||||
const [caseSnapshotLoaded, setCaseSnapshotLoaded] = useState(initialSnapshot !== null);
|
||||
const [questionRetryAttempts, setQuestionRetryAttempts] = useState(0);
|
||||
const [openingRequested, setOpeningRequested] = useState(false);
|
||||
@@ -628,6 +639,7 @@ export function RectificationAgenticChat(props: RectificationAgenticChatProps) {
|
||||
choice_card?: unknown;
|
||||
turns?: unknown;
|
||||
question_source?: unknown;
|
||||
next_user_action?: { id?: unknown };
|
||||
case?: {
|
||||
status?: unknown;
|
||||
accepted_time?: unknown;
|
||||
@@ -643,11 +655,15 @@ export function RectificationAgenticChat(props: RectificationAgenticChatProps) {
|
||||
: null;
|
||||
const acceptedTime = typeof payload.case?.accepted_time === "string" ? payload.case.accepted_time : null;
|
||||
const confirmedTime = typeof payload.case?.confirmed_time === "string" ? payload.case.confirmed_time : null;
|
||||
const nextActionId = typeof payload.next_user_action?.id === "string"
|
||||
? payload.next_user_action.id.trim()
|
||||
: "";
|
||||
setCandidateResult(nextCandidate);
|
||||
setCurrentQuestion(nextQuestion);
|
||||
setQuestionSource(questionSourceFromSnapshot(payload.question_source));
|
||||
setChoiceCard(nextChoice);
|
||||
setCaseStatus(nextCaseStatus);
|
||||
setNextUserActionId(nextActionId || null);
|
||||
setCaseSnapshotLoaded(true);
|
||||
if (confirmedTime) {
|
||||
setSavedTime(confirmedTime);
|
||||
@@ -1051,8 +1067,8 @@ export function RectificationAgenticChat(props: RectificationAgenticChatProps) {
|
||||
}, []);
|
||||
|
||||
const submitStructuredChoice = useCallback(async (
|
||||
action: typeof CHOICE_ACTION | typeof STOP_ACTION,
|
||||
optionId: ChoiceKey | "stop",
|
||||
action: typeof CHOICE_ACTION | typeof STOP_ACTION | typeof SKIP_PROBE_ACTION,
|
||||
optionId: ChoiceKey | "stop" | "skip_probe",
|
||||
override?: Readonly<{
|
||||
focusId: string;
|
||||
questionId: string | null;
|
||||
@@ -1124,6 +1140,10 @@ export function RectificationAgenticChat(props: RectificationAgenticChatProps) {
|
||||
return;
|
||||
}
|
||||
const willContinue = shouldContinueAfterStructuredChoice(payload?.nextAction, payload);
|
||||
const payloadNextActionId = typeof payload?.next_user_action?.id === "string"
|
||||
? payload.next_user_action.id.trim()
|
||||
: "";
|
||||
if (payloadNextActionId) setNextUserActionId(payloadNextActionId);
|
||||
onCompleted?.();
|
||||
const snapshot = await loadCaseSnapshot();
|
||||
const turns = snapshot?.turns ?? [];
|
||||
@@ -1392,6 +1412,7 @@ export function RectificationAgenticChat(props: RectificationAgenticChatProps) {
|
||||
questionMissing: currentQuestion === null,
|
||||
questionLoadFailed: questionSource === "unavailable",
|
||||
offerAwaitingReader: showSelectionCards && !candidateResult?.selectedTime,
|
||||
nextUserActionId,
|
||||
busy,
|
||||
readonly,
|
||||
regenerating: regeneratingMessageKey !== null,
|
||||
@@ -1447,6 +1468,10 @@ export function RectificationAgenticChat(props: RectificationAgenticChatProps) {
|
||||
|
||||
function submitStop() {
|
||||
if (!choiceCard) return;
|
||||
if (choiceCard.skip_this_probe) {
|
||||
void submitStructuredChoice(SKIP_PROBE_ACTION, "skip_probe");
|
||||
return;
|
||||
}
|
||||
void submitStructuredChoice(STOP_ACTION, "stop");
|
||||
}
|
||||
|
||||
@@ -1622,6 +1647,11 @@ export function RectificationAgenticChat(props: RectificationAgenticChatProps) {
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
{questionGap === "verified_idle" && (
|
||||
<p className="rectification-pending-note" role="status">
|
||||
{RECTIFICATION_USER_COPY.postAdoptVerifyDone}
|
||||
</p>
|
||||
)}
|
||||
{savedTime && savedStatus === "confirmed" && (
|
||||
<p className="rectification-saved" role="status">
|
||||
已确认校正时间:{savedTime}
|
||||
|
||||
@@ -12,7 +12,7 @@ type RectificationChoiceCardProps = Readonly<{
|
||||
card: ChoiceCard;
|
||||
pending: boolean;
|
||||
disabled: boolean;
|
||||
selectedKey?: ChoiceKey | "stop" | "";
|
||||
selectedKey?: ChoiceKey | "stop" | "skip_probe" | "";
|
||||
onSelect: (key: ChoiceKey) => void;
|
||||
onStop: () => void;
|
||||
variant?: "card" | "embedded";
|
||||
@@ -20,7 +20,7 @@ type RectificationChoiceCardProps = Readonly<{
|
||||
}>;
|
||||
|
||||
export function RectificationChoiceCard(props: RectificationChoiceCardProps) {
|
||||
const [localSelected, setLocalSelected] = useState<ChoiceKey | "stop" | "">("");
|
||||
const [localSelected, setLocalSelected] = useState<ChoiceKey | "stop" | "skip_probe" | "">("");
|
||||
const selectedKey = props.selectedKey || localSelected;
|
||||
const answered = Boolean(selectedKey);
|
||||
|
||||
@@ -77,7 +77,7 @@ export function RectificationChoiceCard(props: RectificationChoiceCardProps) {
|
||||
<button
|
||||
type="button"
|
||||
className="birth-time-choice-option is-primary"
|
||||
data-selected={selectedKey === "stop" ? "true" : "false"}
|
||||
data-selected={selectedKey === "stop" || selectedKey === "skip_probe" ? "true" : "false"}
|
||||
onClick={stop}
|
||||
>
|
||||
{props.card.stop_label}
|
||||
|
||||
Reference in New Issue
Block a user