fix(rectification): put each question inside the assistant message
Independent Staging Quality Gate / validate (push) Successful in 25m7s
Independent Staging Quality Gate / publish (push) Successful in 2m8s

Focuses now carry asked_turn_id so GET rebuilds stem and options on the
same turn. Agent writes spokenPrompt; the live question slot is gone.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Jesse_Chen
2026-09-02 19:55:20 +08:00
co-authored by Cursor
parent a594990a16
commit d94049769e
42 changed files with 1760 additions and 475 deletions
@@ -365,7 +365,7 @@ test("completed Agent replies restore feedback, copy and safe in-place regenerat
}
assert.match(chat, /<ChatMessageActions/);
assert.match(chat, /toggleRectificationFeedback/);
assert.match(chat, /navigator\.clipboard\.writeText\(message\.text\)/);
assert.match(chat, /navigator\.clipboard\.writeText\(copyTextForMessage\(message\.text, message\.question\)\)/);
assert.match(chat, /\/turns\/\$\{encodeURIComponent\(message\.turnId\)\}\/regenerate/);
assert.match(chat, /requestId: globalThis\.crypto\.randomUUID\(\)/);
@@ -535,7 +535,7 @@ test("time-selection cards use server adoption state and stay mutually exclusive
const cardsIndex = messageLoop.indexOf("<RectificationCandidateCards");
assert.ok(actionsIndex >= 0 && cardsIndex > actionsIndex);
assert.match(chat, /candidateResult\?\.selectionAllowed/);
assert.match(chat, /showLiveChoiceCard = Boolean\([\s\S]*currentQuestion\?\.kind === "choice"[\s\S]*choiceCard[\s\S]*answeredQuestionIds[\s\S]*!busy/);
assert.match(chat, /showLiveChoiceCard = Boolean\([\s\S]*latestLiveQuestion[\s\S]*options\?\.length === 4[\s\S]*!busy/);
assert.match(chat, /showSelectionCards = Boolean\(\s*candidateResult\?\.selectionAllowed[\s\S]*candidateResult\?\.canAdopt[\s\S]*!showLiveChoiceCard[\s\S]*!busy[\s\S]*!readonly/);
assert.doesNotMatch(
chat.slice(chat.indexOf("const showSelectionCards"), chat.indexOf("const selectionCardMessageKey")),
@@ -547,9 +547,10 @@ test("time-selection cards use server adoption state and stay mutually exclusive
);
assert.match(messageLoop, /showSelectionCards && message\.renderKey === selectionCardMessageKey/);
assert.match(messageLoop, /<RectificationChoiceCard/);
assert.match(messageLoop, /choiceAttachment/);
assert.match(messageLoop, /variant="embedded"/);
assert.match(messageLoop, /choiceNonce/);
assert.match(messageLoop, /answered:\$\{settledChoice\.selectedKey\}/);
assert.match(messageLoop, /question\.answer_option/);
assert.doesNotMatch(messageLoop, /rectification-question-slot/);
assert.doesNotMatch(choiceCardComponent, /setSelectedKey\(""\)/);
assert.match(choiceCardComponent, /selectedKey/);
assert.match(choiceCardComponent, /is-answered/);
@@ -635,8 +636,8 @@ test("rectification Agent output stays natural and keeps tool execution silent",
assert.match(strategy, /没有更多事件/);
assert.match(strategy, /(?:无需|不要求)结束、暂停或保存进度/);
assert.match(strategy, /不要一进场就出 A\/B\/C\/D/);
assert.match(agent, /有持久化 current_question 时,题干与选项完全由结构化槽位和 UI 承担/);
assert.match(agent, /正文不提问、复述、改写或拼接题干/);
assert.match(agent, /用 rectification-set-focus 的 spokenPrompt 写出服务端给你的下一问/);
assert.match(agent, /正文只做承接(2-4 句),不提问、复述题干、不预告选项/);
});
test("clear current-turn events go through the batch evidence service", () => {
@@ -664,9 +665,10 @@ test("the Agent prompt cannot offer candidates while asking for more evidence",
assert.doesNotMatch(tools, /offer_selection/);
});
test("choice cards expose the persisted prompt as a visible legend", () => {
test("embedded choice cards hide the prompt legend; standalone cards keep it visible", () => {
assert.match(choiceCardComponent, /<legend>\{props\.card\.prompt\}<\/legend>/);
assert.doesNotMatch(choiceCardComponent, /<legend className="sr-only">\{props\.card\.prompt\}<\/legend>/);
assert.match(choiceCardComponent, /<legend className="sr-only">\{props\.card\.prompt\}<\/legend>/);
assert.match(choiceCardComponent, /variant === "embedded"/);
});
test("choice card answers and stop share one stacked primary list", () => {
@@ -682,7 +684,7 @@ test("adopted time offers a consultation handoff without unique-minute copy", ()
assert.match(board, /换升时刻/);
assert.match(board, /经历与大运对照/);
assert.match(chat, /<RectificationChoiceCard/);
assert.match(chat, /choiceAttachment/);
assert.match(chat, /markQuestionAnswered/);
assert.match(page, /startConsultationAfterRectification/);
assert.match(page, /createSession\(modelCatalog\.defaultModelId\)/);
assert.doesNotMatch(agent, /start_consultation/);