fix(rectification): host-fallback empty evidence turns and choice range narration (BUG-633, BUG-634)

Evidence turns that already recorded a batch no longer fail the whole run when the model emits no text; unchanged ranges now name which clock spans lead or lag, and the timeline no longer says 收窄.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Jesse_Chen
2026-09-10 11:57:07 +08:00
co-authored by Cursor
parent 31f2a722aa
commit 0263658477
28 changed files with 817 additions and 49 deletions
@@ -27,6 +27,7 @@ export type PersistedRectificationTurn = Readonly<{
methods?: readonly string[];
skill_name?: string;
skill_version?: string;
answer_origin?: "host_fallback";
}> | null;
}>;
@@ -60,6 +60,8 @@ import {
rectificationConversationState,
rectificationInitialLiveLabel,
rectificationQuestionGapState,
rectificationReadonlyRangeCopy,
contrastProbesFromReceipt,
searchWindowFromSnapshot,
caseStageFromSnapshot,
type RectificationCaseSnapshotPayload,
@@ -177,12 +179,23 @@ function questionSourceFromSnapshot(value: unknown): "focus" | "unavailable" | n
function RectificationReadonlyRange({
range,
sessionOutcome,
discriminatingEventProbes,
contrastProbes,
}: Readonly<{
range: readonly [string, string];
sessionOutcome: string | null;
discriminatingEventProbes: unknown;
contrastProbes: unknown;
}>) {
return (
<p className="rectification-readonly-range" role="status">
{range[0]}{range[1]}
{rectificationReadonlyRangeCopy({
range,
sessionOutcome,
discriminatingEventProbes,
contrastProbes,
})}
</p>
);
}
@@ -986,6 +999,12 @@ export function RectificationAgenticChat(props: RectificationAgenticChatProps) {
{ role: "assistant", text: parsed.text },
]);
onCompleted?.();
} else {
await loadCaseSnapshot((turns) => {
if (turns.length) {
setMessages((current) => mergeTurnQuestions(current, turns));
}
});
}
} catch (caught) {
frames.settle();
@@ -1487,6 +1506,7 @@ export function RectificationAgenticChat(props: RectificationAgenticChatProps) {
liveQuestionVisible: liveQuestionOnMessages,
questionMissing: currentQuestion === null,
questionLoadFailed: questionSource === "unavailable",
questionPersisted: Boolean(currentQuestion?.prompt && questionSource === "focus"),
offerAwaitingReader: showSelectionCards && !candidateResult?.selectedTime,
nextUserActionId,
busy,
@@ -1726,11 +1746,21 @@ export function RectificationAgenticChat(props: RectificationAgenticChatProps) {
{showReadonlyRange && message.renderKey === latestSettledAssistant?.renderKey && candidateResult?.credibleRange && (
<RectificationReadonlyRange
range={candidateResult.credibleRange}
sessionOutcome={candidateResult.sessionOutcome}
discriminatingEventProbes={candidateResult.decisionReceipt?.discriminating_event_probes}
contrastProbes={contrastProbesFromReceipt(candidateResult.decisionReceipt)}
/>
)}
</div>
);
})}
{questionGap === "persisted_question" && currentQuestion?.prompt && (
<div className="rectification-message-wrap rectification-message-entry" data-testid="persisted-question">
<div className="rectification-message-question">
<p className="rectification-message-question__prompt">{currentQuestion.prompt}</p>
</div>
</div>
)}
{questionGap === "preparing" && (
<div className="rectification-message-wrap rectification-message-entry rectification-question-gap">
<ConsultationTimelineLiveRow id="question-preparing" label={RECTIFICATION_QUESTION_PREPARING_LABEL} />
@@ -1777,7 +1807,7 @@ export function RectificationAgenticChat(props: RectificationAgenticChatProps) {
? "该校正已结束,只能查看历史;需要再次校正请新建。"
: showLiveChoiceCard
? "点上面的选项即可;想补一句细节再写"
: collectSpokenPrompt
: (liveQuestionOnMessages || questionGap === "persisted_question") && collectSpokenPrompt
? "请回答上面的问题…"
: "继续说你记得的人生经历,或回答刚才的问题…"}
maxLength={RECTIFICATION_COMPOSER_MAX_LENGTH}