fix(rectification): hold time cards until an offer turn
Independent Staging Quality Gate / validate (push) Successful in 11m37s
Independent Staging Quality Gate / publish (push) Failing after 9m4s

Keep collecting while a method follow-up can still distinguish candidates, and show adoption cards only after offer-candidates. Align the natal house snapshot with the agent text column.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Jesse_Chen
2026-08-20 00:50:40 +08:00
parent 4d872c68b2
commit a732ff4b58
19 changed files with 167 additions and 51 deletions
@@ -320,8 +320,11 @@ test("candidate state renders from the snapshot API and never from sentinels", (
assert.match(styles, /\.rectification-house-table \{[^}]*border:\s*1px solid var\(--color-border\)/);
assert.match(styles, /\.rectification-house-table \{[^}]*background:\s*var\(--color-canvas-soft\)/);
assert.match(styles, /\.message-list \{[\s\S]*--assistant-content-inset: calc\(32px \+ var\(--space-3\)\)/);
assert.match(styles, /\.rectification-message-wrap \.rectification-candidates \{[\s\S]*width: calc\(100% - var\(--assistant-content-inset\)\)/);
assert.match(styles, /\.rectification-message-wrap \.rectification-candidates \{[\s\S]*margin-inline-start: var\(--assistant-content-inset\)/);
assert.doesNotMatch(styles, /\.rectification-snapshot \{[^}]*--assistant-content-inset/);
assert.match(styles, /\.rectification-snapshot \{[\s\S]*width: calc\(100% - var\(--assistant-content-inset\)\)/);
assert.match(styles, /\.rectification-snapshot \{[\s\S]*margin-inline-start: var\(--assistant-content-inset\)/);
assert.doesNotMatch(styles, /\.rectification-house-table \{[^}]*padding-inline-start:\s*var\(--assistant-content-inset\)/);
assert.doesNotMatch(styles, /\.rectification-house-table \{[^}]*--assistant-content-inset/);
});
@@ -353,7 +356,7 @@ test("the natal house table is a live snapshot card outside the agent bubble", (
assert.doesNotMatch(houseTable, /<ChatMessageRow/);
});
test("time-selection cards appear under the latest settled agent bubble once selection is allowed", () => {
test("time-selection cards appear under the latest settled agent bubble only after offer-candidates", () => {
const messageLoop = chat.slice(
chat.indexOf("{messages.map((message) => {"),
chat.indexOf("{candidateResult?.houseTable &&"),
@@ -362,9 +365,10 @@ test("time-selection cards appear under the latest settled agent bubble once sel
const cardsIndex = messageLoop.indexOf("<RectificationCandidateCards");
assert.ok(actionsIndex >= 0 && cardsIndex > actionsIndex);
assert.match(chat, /candidateResult\?\.selectionAllowed/);
assert.match(chat, /latestOfferMessageKey/);
assert.match(chat, /showSelectionCards = Boolean\(\s*candidateResult\?\.selectionAllowed[\s\S]*!busy[\s\S]*regeneratingMessageKey === null/);
assert.match(messageLoop, /showSelectionCards && message\.renderKey === latestOfferMessageKey/);
assert.match(chat, /turnOfferedSelection/);
assert.match(chat, /rectification-offer-candidates/);
assert.match(chat, /showSelectionCards = Boolean\(\s*candidateResult\?\.selectionAllowed[\s\S]*turnOfferedSelection\(latestOfferMessageKey\)[\s\S]*!busy/);
assert.match(messageLoop, /showSelectionCards && message\.renderKey === selectionCardMessageKey/);
assert.doesNotMatch(messageLoop, /className="rectification-snapshot"/);
});
@@ -417,6 +421,8 @@ test("the Agent prompt cannot offer candidates while asking for more evidence",
// The hard boundary lives in the prompt; no tool input carries an
// offer_selection boolean anymore.
assert.match(agent, /不得在同一回复中一边要求继续补证据,一边提供候选采用/);
assert.match(agent, /id 不是 adopt_representative 时不得调用 rectification-offer-candidates/);
assert.match(agent, /selection_allowed 只表示可以采用代表性时间/);
const tools = readFileSync(
new URL("../src/mastra/rectification-v9-tools.ts", import.meta.url),
"utf8",