fix(web): exit rectification when the probe pool is exhausted (BUG-558)

Stop falling through to "say another event" after dated and occupation collect are done. Deliver an adopt path or a gate sentence, and add a spoken-collect stop control.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Jesse_Chen
2026-09-06 23:03:11 +08:00
co-authored by Cursor
parent 3c30a579a9
commit 150d7ef189
21 changed files with 1179 additions and 121 deletions
@@ -167,11 +167,26 @@ function questionSourceFromSnapshot(value: unknown): "focus" | "unavailable" | n
function RectificationReadonlyRange({
range,
}: Readonly<{ range: readonly [string, string] }>) {
onStop,
}: Readonly<{
range: readonly [string, string];
onStop?: () => void;
}>) {
if (!onStop) {
return (
<p className="rectification-readonly-range" role="status">
{range[0]}{range[1]}
</p>
);
}
return (
<p className="rectification-readonly-range" role="status">
<button
type="button"
className="rectification-readonly-range"
onClick={onStop}
>
{range[0]}{range[1]}
</p>
</button>
);
}
@@ -1080,23 +1095,27 @@ export function RectificationAgenticChat(props: RectificationAgenticChatProps) {
action: typeof CHOICE_ACTION | typeof STOP_ACTION | typeof SKIP_PROBE_ACTION,
optionId: ChoiceKey | "stop" | "skip_probe",
override?: Readonly<{
focusId: string;
questionId: string | null;
focusId?: string;
questionId?: string | null;
probeId?: string | null;
caseRevision?: number | null;
}>,
) => {
const focusId = override?.focusId ?? choiceCard?.focus_id;
if (!focusId || busy || readonly) return;
if (!override && !choiceCard) return;
if (!isPersistedFocusId(focusId)) {
const focusId = override?.focusId ?? choiceCard?.focus_id ?? (
action === STOP_ACTION && currentQuestion?.kind === "collect_spoken"
? currentQuestion.focus_id
: null
);
if (busy || readonly) return;
if (action !== STOP_ACTION && (!focusId || (!override && !choiceCard))) return;
if (focusId && !isPersistedFocusId(focusId)) {
setError("当前选择题已失效,请等待下一问。");
return;
}
const questionId = override?.questionId ?? choiceCard?.question_id;
const questionId = override?.questionId ?? choiceCard?.question_id ?? currentQuestion?.question_id ?? null;
const probeId = override?.probeId ?? choiceCard?.probe_id;
const expectedRevision = override?.caseRevision ?? choiceCard?.case_revision ?? 0;
const actionId = actionIdForChoice(focusId, optionId);
const actionId = actionIdForChoice(focusId ?? caseId, optionId);
setError("");
keyCounter.current += 1;
const turnKey = keyCounter.current;
@@ -1104,7 +1123,7 @@ export function RectificationAgenticChat(props: RectificationAgenticChatProps) {
const recordingLabel = RECTIFICATION_ACTIVITY_PROGRESS_LABELS.recording_answer;
beginLiveRun(recordingLabel);
setMessages((current) => [
...markQuestionAnswered(current, focusId, optionId),
...markQuestionAnswered(current, focusId ?? "", optionId),
{
role: "assistant",
text: "",
@@ -1132,8 +1151,8 @@ export function RectificationAgenticChat(props: RectificationAgenticChatProps) {
requestId: actionId,
action,
actionId,
focusId,
questionId,
...(focusId ? { focusId } : {}),
...(questionId ? { questionId } : {}),
probeId: probeId ?? null,
optionId: optionId === "stop" ? undefined : optionId,
expectedRevision,
@@ -1219,6 +1238,7 @@ export function RectificationAgenticChat(props: RectificationAgenticChatProps) {
beginLiveRun,
caseId,
choiceCard,
currentQuestion,
loadCaseSnapshot,
onCompleted,
onMessagesChange,
@@ -1523,9 +1543,19 @@ export function RectificationAgenticChat(props: RectificationAgenticChatProps) {
}
function submitStop() {
if (!choiceCard) return;
if (choiceCard.skip_this_probe) {
void submitStructuredChoice(SKIP_PROBE_ACTION, "skip_probe");
if (choiceCard) {
if (choiceCard.skip_this_probe) {
void submitStructuredChoice(SKIP_PROBE_ACTION, "skip_probe");
return;
}
void submitStructuredChoice(STOP_ACTION, "stop");
return;
}
if (currentQuestion?.kind === "collect_spoken" && currentQuestion.focus_id) {
void submitStructuredChoice(STOP_ACTION, "stop", {
focusId: currentQuestion.focus_id,
questionId: currentQuestion.question_id,
});
return;
}
void submitStructuredChoice(STOP_ACTION, "stop");
@@ -1686,7 +1716,10 @@ export function RectificationAgenticChat(props: RectificationAgenticChatProps) {
/>
)}
{showReadonlyRange && message.renderKey === latestSettledAssistant?.renderKey && candidateResult?.credibleRange && (
<RectificationReadonlyRange range={candidateResult.credibleRange} />
<RectificationReadonlyRange
range={candidateResult.credibleRange}
onStop={readonly || busy ? undefined : submitStop}
/>
)}
</div>
);
@@ -1746,6 +1779,15 @@ export function RectificationAgenticChat(props: RectificationAgenticChatProps) {
<span className="rectification-adopt-status__hint"></span>
</div>
)}
{currentQuestion?.kind === "collect_spoken" && !readonly && !busy && (
<button
type="button"
className="rectification-collect-stop"
onClick={submitStop}
>
{CHOICE_STOP_LABEL}
</button>
)}
<ChatComposer
inputRef={composer}
value={draft}