fix(rectification): invite-first collect, holdout at 4 events, Skill 10.0.23 (BUG-646–648)
Stop domain-wheel collecting and age-band years in prompts. Ask until the training gate, then discriminate until convergence, then deliver a range plus a concrete follow-up. Reserve holdout only with four dated events. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -57,6 +57,7 @@ import {
|
||||
RECTIFICATION_QUESTION_RETRY_INTERVAL_MS,
|
||||
RECTIFICATION_QUESTION_RETRY_LIMIT,
|
||||
RECTIFICATION_QUESTION_UNAVAILABLE_COPY,
|
||||
RECTIFICATION_COLLECT_WAITING_PLACEHOLDER,
|
||||
RECTIFICATION_STOPPED_NOTICE,
|
||||
isAbortError,
|
||||
rectificationConversationState,
|
||||
@@ -66,6 +67,9 @@ import {
|
||||
contrastProbesFromReceipt,
|
||||
searchWindowFromSnapshot,
|
||||
caseStageFromSnapshot,
|
||||
interviewCollectWaiting,
|
||||
interviewStopReasonFromSnapshot,
|
||||
interviewSessionOutcomeFromSnapshot,
|
||||
type RectificationCaseSnapshotPayload,
|
||||
} from "@/lib/rectification-surface-state";
|
||||
import { RectificationTimeline } from "@/components/rectification-timeline";
|
||||
@@ -418,6 +422,8 @@ type CaseSnapshotState = Readonly<{
|
||||
nextUserActionId: string | null;
|
||||
searchWindow: readonly [string, string] | null;
|
||||
stage: RectificationTimelineStage | null;
|
||||
interviewStopReason: string | null;
|
||||
interviewSessionOutcome: string | null;
|
||||
}>;
|
||||
|
||||
function nextUserActionIdFromSnapshot(payload: RectificationCaseSnapshotPayload | null): string | null {
|
||||
@@ -441,6 +447,8 @@ function caseSnapshotState(payload: RectificationCaseSnapshotPayload | null): Ca
|
||||
nextUserActionId: nextUserActionIdFromSnapshot(payload),
|
||||
searchWindow: searchWindowFromSnapshot(payload.case?.candidate_range),
|
||||
stage: caseStageFromSnapshot(payload.case?.stage),
|
||||
interviewStopReason: interviewStopReasonFromSnapshot(payload),
|
||||
interviewSessionOutcome: interviewSessionOutcomeFromSnapshot(payload),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -484,6 +492,8 @@ export function RectificationAgenticChat(props: RectificationAgenticChatProps) {
|
||||
const [searchWindow, setSearchWindow] = useState<readonly [string, string] | null>(() => caseSnapshotState(initialSnapshot)?.searchWindow ?? null);
|
||||
const [caseStage, setCaseStage] = useState<RectificationTimelineStage | null>(() => caseSnapshotState(initialSnapshot)?.stage ?? null);
|
||||
const [nextUserActionId, setNextUserActionId] = useState<string | null>(() => caseSnapshotState(initialSnapshot)?.nextUserActionId ?? null);
|
||||
const [interviewStopReason, setInterviewStopReason] = useState<string | null>(() => caseSnapshotState(initialSnapshot)?.interviewStopReason ?? null);
|
||||
const [interviewSessionOutcome, setInterviewSessionOutcome] = useState<string | null>(() => caseSnapshotState(initialSnapshot)?.interviewSessionOutcome ?? null);
|
||||
const [caseSnapshotLoaded, setCaseSnapshotLoaded] = useState(initialSnapshot !== null);
|
||||
const [questionRetryAttempts, setQuestionRetryAttempts] = useState(0);
|
||||
const [questionRepairAttempts, setQuestionRepairAttempts] = useState(0);
|
||||
@@ -612,7 +622,7 @@ export function RectificationAgenticChat(props: RectificationAgenticChatProps) {
|
||||
|
||||
// Candidate snapshot comes from the persisted Candidate Snapshot API, never
|
||||
// from parsing agent text or hidden sentinels.
|
||||
const applyCaseSnapshot = useCallback((payload: {
|
||||
const applyCaseSnapshot = useCallback((payload: RectificationCaseSnapshotPayload | {
|
||||
latest_result?: unknown;
|
||||
current_question?: unknown;
|
||||
choice_card?: unknown;
|
||||
@@ -620,6 +630,7 @@ export function RectificationAgenticChat(props: RectificationAgenticChatProps) {
|
||||
turns?: unknown;
|
||||
question_source?: unknown;
|
||||
next_user_action?: { id?: unknown };
|
||||
interview?: { stop_reason?: unknown; session_outcome?: unknown };
|
||||
case?: {
|
||||
status?: unknown;
|
||||
accepted_time?: unknown;
|
||||
@@ -629,6 +640,7 @@ export function RectificationAgenticChat(props: RectificationAgenticChatProps) {
|
||||
};
|
||||
} | null) => {
|
||||
if (!payload) return;
|
||||
const snapshot = payload as RectificationCaseSnapshotPayload;
|
||||
const nextCandidate = parseRectificationCandidateResult(payload.latest_result);
|
||||
const nextQuestion = currentQuestionFromSnapshot(payload.current_question);
|
||||
const nextChoice = parseRectificationChoiceCard(payload.choice_card);
|
||||
@@ -653,6 +665,8 @@ export function RectificationAgenticChat(props: RectificationAgenticChatProps) {
|
||||
setChoiceCard(nextChoice);
|
||||
setCaseStatus(nextCaseStatus);
|
||||
setNextUserActionId(nextActionId || null);
|
||||
setInterviewStopReason(interviewStopReasonFromSnapshot(snapshot));
|
||||
setInterviewSessionOutcome(interviewSessionOutcomeFromSnapshot(snapshot));
|
||||
setCaseSnapshotLoaded(true);
|
||||
if (nextQuestion || nextChoice || acceptedTime || confirmedTime) {
|
||||
setQuestionRepairAttempts(0);
|
||||
@@ -1508,7 +1522,14 @@ export function RectificationAgenticChat(props: RectificationAgenticChatProps) {
|
||||
// The gap between a settled turn and its next question has two visible
|
||||
// states: `preparing` (one live timeline row, timed refetches) and
|
||||
// `unavailable` (copy and a repair button) — never bare copy telling the
|
||||
// reader to wait for the server.
|
||||
// reader to wait for the server. Collect-waiting is the training-gate gap:
|
||||
// no next card, keep the case open, do not show 「没有拿到下一个问题」.
|
||||
const collectWaiting = interviewCollectWaiting({
|
||||
stopReason: interviewStopReason,
|
||||
sessionOutcome: interviewSessionOutcome ?? candidateResult?.sessionOutcome ?? null,
|
||||
questionMissing: currentQuestion === null,
|
||||
offeringCards: showSelectionCards,
|
||||
});
|
||||
const questionGap = rectificationQuestionGapState({
|
||||
liveQuestionVisible: liveQuestionOnMessages,
|
||||
questionMissing: currentQuestion === null,
|
||||
@@ -1516,6 +1537,7 @@ export function RectificationAgenticChat(props: RectificationAgenticChatProps) {
|
||||
questionPersisted: Boolean(currentQuestion?.prompt && questionSource === "focus"),
|
||||
offerAwaitingReader: showSelectionCards && !candidateResult?.selectedTime,
|
||||
nextUserActionId,
|
||||
collectWaiting,
|
||||
busy,
|
||||
readonly,
|
||||
regenerating: regeneratingMessageKey !== null,
|
||||
@@ -1853,7 +1875,9 @@ export function RectificationAgenticChat(props: RectificationAgenticChatProps) {
|
||||
? "点上面的选项即可;想补一句细节再写"
|
||||
: (liveQuestionOnMessages || questionGap === "persisted_question") && collectSpokenPrompt
|
||||
? "请回答上面的问题…"
|
||||
: "继续说你记得的人生经历,或回答刚才的问题…"}
|
||||
: questionGap === "collect_waiting"
|
||||
? RECTIFICATION_COLLECT_WAITING_PLACEHOLDER
|
||||
: "继续说你记得的人生经历,或回答刚才的问题…"}
|
||||
maxLength={RECTIFICATION_COMPOSER_MAX_LENGTH}
|
||||
inputDisabled={readonly}
|
||||
submitLabel="发送"
|
||||
|
||||
Reference in New Issue
Block a user