fix(rectification): keep choice options and scoring on the server
Independent Staging Quality Gate / validate (push) Successful in 9m30s
Independent Staging Quality Gate / publish (push) Successful in 1m50s

Agent set-focus was writing or dropping choice schema, so probes never scored. Force server-owned options, fail closed when focus list RPC errors, and require the probe year in spokenPrompt.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Jesse_Chen
2026-09-02 20:47:16 +08:00
co-authored by Cursor
parent 679d773b28
commit 8dfe457f21
17 changed files with 942 additions and 117 deletions
@@ -120,6 +120,12 @@ function currentQuestionFromSnapshot(value: unknown): CurrentQuestionModel | nul
};
}
function questionSourceFromSnapshot(value: unknown): "focus" | "unavailable" | null {
if (value === "unavailable") return "unavailable";
if (value === "focus") return "focus";
return null;
}
function RectificationCandidateCards({
result,
acceptingCandidateId,
@@ -380,6 +386,7 @@ export function RectificationAgenticChat(props: RectificationAgenticChatProps) {
const [candidateResult, setCandidateResult] = useState<CandidateResult>(null);
const [choiceCard, setChoiceCard] = useState<ChoiceCardModel | null>(null);
const [currentQuestion, setCurrentQuestion] = useState<CurrentQuestionModel | null>(null);
const [questionSource, setQuestionSource] = useState<"focus" | "unavailable" | null>(null);
const [caseStatus, setCaseStatus] = useState<RectificationCaseStatus | null>(null);
const [caseSnapshotLoaded, setCaseSnapshotLoaded] = useState(false);
const [acceptingCandidateId, setAcceptingCandidateId] = useState<string | null>(null);
@@ -447,6 +454,7 @@ export function RectificationAgenticChat(props: RectificationAgenticChatProps) {
current_question?: unknown;
choice_card?: unknown;
turns?: unknown;
question_source?: unknown;
case?: {
status?: unknown;
accepted_time?: unknown;
@@ -464,6 +472,7 @@ export function RectificationAgenticChat(props: RectificationAgenticChatProps) {
const confirmedTime = typeof payload.case?.confirmed_time === "string" ? payload.case.confirmed_time : null;
setCandidateResult(nextCandidate);
setCurrentQuestion(nextQuestion);
setQuestionSource(questionSourceFromSnapshot(payload.question_source));
setChoiceCard(nextChoice);
setCaseStatus(nextCaseStatus);
setCaseSnapshotLoaded(true);
@@ -1139,12 +1148,14 @@ export function RectificationAgenticChat(props: RectificationAgenticChatProps) {
&& message.question.focus_id === currentQuestion.focus_id
&& !questionIsAnswered(message.question)
));
const questionLoadFailed = questionSource === "unavailable";
const showMissingQuestion = Boolean(
caseSnapshotLoaded
&& resumableCase
&& !readonly
&& !busy
&& currentQuestion === null,
&& currentQuestion === null
&& !questionLoadFailed,
);
const showUnavailableQuestion = Boolean(
caseSnapshotLoaded
@@ -1152,7 +1163,15 @@ export function RectificationAgenticChat(props: RectificationAgenticChatProps) {
&& !readonly
&& !busy
&& currentQuestion !== null
&& !liveQuestionOnMessages,
&& !liveQuestionOnMessages
&& !questionLoadFailed,
);
const showQuestionLoadFailed = Boolean(
caseSnapshotLoaded
&& resumableCase
&& !readonly
&& !busy
&& questionLoadFailed,
);
const canSend = !busy && !readonly && !regeneratingMessageKey;
@@ -1321,11 +1340,13 @@ export function RectificationAgenticChat(props: RectificationAgenticChatProps) {
{!conversationAnchor.anchored && (
<JumpToLatestButton onClick={conversationAnchor.anchorToLatest} />
)}
{(showMissingQuestion || showUnavailableQuestion) && (
{(showMissingQuestion || showUnavailableQuestion || showQuestionLoadFailed) && (
<p className="rectification-composer-status" role="status">
{showMissingQuestion
? "当前没有可回答的问题,正在等待服务端更新。"
: "当前问题暂时无法显示,请等待服务端更新。"}
{showQuestionLoadFailed
? "题目加载失败,请刷新。"
: showMissingQuestion
? "当前没有可回答的问题,正在等待服务端更新。"
: "当前问题暂时无法显示,请等待服务端更新。"}
</p>
)}
<ChatComposer