import assert from "node:assert/strict"; import test from "node:test"; import { applyLiveCandidateOffer, attachOfferResultToTurns, persistedOfferFromTurn, type TurnQuestion, } from "../src/lib/rectification-agentic/v9/turn-question.ts"; function collect(status: "active" | "resolved"): TurnQuestion { return { focus_id: "focus-career", question_id: "collect:career:collect_method_evidence", kind: "collect_spoken", prompt: "工作上呢,还记得哪年入职、换工作,或职责一下子变重吗?", options: null, status, answer_option: null, probe_id: null, }; } test("adopt offer skips an unanswered collect question and lands on the later narration", () => { const attached = attachOfferResultToTurns([ { id: "collect-turn", role: "assistant", question: collect("active") }, { id: "user-turn", role: "user", question: null }, { id: "adopt-turn", role: "assistant", question: null }, ], { resultId: "result-1", canAdopt: true, acceptedTime: null, }); assert.equal(attached[0]?.offer_result_id, null); assert.equal(attached[2]?.offer_result_id, "result-1"); }); test("adopt offer is not pinned onto a live collect question even if it is the only assistant turn", () => { const attached = attachOfferResultToTurns([ { id: "collect-turn", role: "assistant", question: collect("active") }, ], { resultId: "result-1", canAdopt: true, acceptedTime: null, }); assert.equal(attached[0]?.offer_result_id, null); }); test("live offer moves off a collect message once the adopt narration exists", () => { const next = applyLiveCandidateOffer([ { renderKey: "collect", role: "assistant", state: "settled", text: "工作上呢,还记得哪年入职、换工作,或职责一下子变重吗?", question: collect("resolved"), candidateOffer: { resultId: "result-1" }, }, { renderKey: "user", role: "user", state: "settled", text: "2020 年 4 月实习然后 10 月离职", }, { renderKey: "adopt", role: "assistant", state: "settled", text: "再问下去也分不出更准的时间了。眼下更站得住的是 05:15。这只是代表性候选,不是已确认的唯一出生分钟。我按你说的经历认真分析过了,下面是这次的结果。", }, ], { resultId: "result-1", canOffer: true, adopted: false }); assert.equal(next[0]?.candidateOffer, undefined); assert.deepEqual(next[2]?.candidateOffer, { resultId: "result-1" }); }); test("live offer stays hidden while an unanswered collect question is still on screen", () => { const next = applyLiveCandidateOffer([ { renderKey: "collect", role: "assistant", state: "settled", text: "工作上呢,还记得哪年入职、换工作,或职责一下子变重吗?", question: collect("active"), candidateOffer: { resultId: "result-1" }, }, ], { resultId: "result-1", canOffer: true, adopted: false }); assert.equal(next[0]?.candidateOffer, undefined); }); test("after adopt the offer stays on the original message", () => { const next = applyLiveCandidateOffer([ { renderKey: "offer", role: "assistant", state: "settled", text: "我按你说的经历认真分析过了,下面是这次的结果。", candidateOffer: { resultId: "result-1" }, }, { renderKey: "verify", role: "assistant", state: "settled", text: "2021 年前后,有没有家人结婚、添丁或住院?", question: { focus_id: "verify", question_id: "reverse_verify:family:score", kind: "reverse_verify", prompt: "2021 年前后,有没有家人结婚、添丁或住院?", options: null, status: "active", answer_option: null, probe_id: null, }, }, ], { resultId: "result-2", canOffer: true, adopted: true }); assert.deepEqual(next[0]?.candidateOffer, { resultId: "result-2" }); assert.equal(next[1]?.candidateOffer, undefined); }); test("hydrated turns drop a stale client offer when GET says this turn is not the owner", () => { assert.equal( persistedOfferFromTurn(null, { resultId: "result-1" }, true), undefined, ); assert.deepEqual( persistedOfferFromTurn("result-2", { resultId: "result-1" }, true), { resultId: "result-2" }, ); assert.deepEqual( persistedOfferFromTurn(null, { resultId: "result-1" }, false), { resultId: "result-1" }, ); });