a03756bf18
Conflict nodes stay server-owned; the Agent writes the question and option copy so users can tap instead of typing through an interrogation. Co-authored-by: Cursor <cursoragent@cursor.com>
158 lines
5.8 KiB
TypeScript
158 lines
5.8 KiB
TypeScript
import assert from "node:assert/strict";
|
||
import test from "node:test";
|
||
|
||
import {
|
||
buildChoiceFrame,
|
||
choiceCardUserMessage,
|
||
HOLDOUT_MESSAGE_PREFIX,
|
||
isHoldoutVerificationQuote,
|
||
lifePeriodLabel,
|
||
mergeChoiceCard,
|
||
parseAgentChoiceCopy,
|
||
parseRectificationChoiceCard,
|
||
} from "../src/lib/rectification-agentic/v9/choice-card.ts";
|
||
import { buildMethodFollowupPlan, projectRectificationChoiceCard } from "../src/lib/rectification-agentic/v9/method-followup.ts";
|
||
|
||
const SAMPLE_COPY = {
|
||
prompt: "2016 年前后,更像哪一件?",
|
||
option_a: "那时开始一段认真交往",
|
||
option_b: "那时工作突然压上来",
|
||
option_c: "那几年感情和工作都没什么明显变化",
|
||
option_d: "不记得那几年发生过什么",
|
||
} as const;
|
||
|
||
test("choice frames reverse-infer competing relationship vs career when D9 and D10 both differ", () => {
|
||
const frame = buildChoiceFrame(
|
||
{
|
||
method_id: "d9_relationship",
|
||
ask_theme: "relationship_style",
|
||
domain: "relationship",
|
||
user_prompt_hint: "unused",
|
||
},
|
||
{
|
||
observations: [
|
||
{ layer: "d9", candidates_differ: true, ask_theme: "relationship_style" },
|
||
{ layer: "d10", candidates_differ: true, ask_theme: "career_style" },
|
||
],
|
||
evidence: [{
|
||
status: "confirmed",
|
||
datePrecision: "year",
|
||
occurredFrom: "2016-01-01",
|
||
occurredTo: null,
|
||
}],
|
||
},
|
||
);
|
||
assert.equal(frame.period, "2016 年前后");
|
||
assert.match(frame.option_a_hint, /认真关系/);
|
||
assert.match(frame.option_b_hint, /职责/);
|
||
assert.equal(/外貌|疤痕|胎记/.test(frame.option_a_hint + frame.option_b_hint), false);
|
||
assert.equal(frame.scoring, true);
|
||
assert.equal(mergeChoiceCard(frame, null), null);
|
||
});
|
||
|
||
test("the visible card uses Agent copy for A/B/C/D, not the server hint", () => {
|
||
const frame = buildChoiceFrame({
|
||
method_id: "d9_relationship",
|
||
ask_theme: "relationship_style",
|
||
domain: "relationship",
|
||
user_prompt_hint: "unused",
|
||
}, {
|
||
evidence: [{
|
||
status: "confirmed",
|
||
datePrecision: "year",
|
||
occurredFrom: "2016-01-01",
|
||
occurredTo: null,
|
||
}],
|
||
});
|
||
const card = mergeChoiceCard(frame, SAMPLE_COPY);
|
||
assert.equal(card?.prompt, SAMPLE_COPY.prompt);
|
||
assert.equal(card?.options[0]?.label, SAMPLE_COPY.option_a);
|
||
assert.equal(card?.options[1]?.label, SAMPLE_COPY.option_b);
|
||
assert.equal(card?.options[2]?.label, SAMPLE_COPY.option_c);
|
||
assert.equal(card?.options[3]?.label, SAMPLE_COPY.option_d);
|
||
assert.equal(card?.options[2]?.label === frame.neither_label, false);
|
||
assert.equal(card?.options[3]?.label === frame.unsure_label, false);
|
||
assert.equal(card?.stop_label, frame.stop_label);
|
||
});
|
||
|
||
test("holdout messages are prefixed and do not count as scoring quotes", () => {
|
||
const frame = buildChoiceFrame(
|
||
{
|
||
method_id: "oos_blind",
|
||
ask_theme: "oos_blind",
|
||
domain: "family",
|
||
user_prompt_hint: "unused",
|
||
},
|
||
{ scoring: false },
|
||
);
|
||
const card = mergeChoiceCard(frame, {
|
||
prompt: "家人这条线还没用过,更像哪一件?",
|
||
option_a: "家里有一件记得大概年份的变化",
|
||
option_b: "有接近的变化,但时间不太准",
|
||
option_c: "家里没什么明显变化",
|
||
option_d: "不记得家里那几年的事",
|
||
});
|
||
assert.ok(card);
|
||
const message = choiceCardUserMessage(card, "A");
|
||
assert.match(message, new RegExp(HOLDOUT_MESSAGE_PREFIX));
|
||
assert.equal(isHoldoutVerificationQuote(message), true);
|
||
assert.equal(isHoldoutVerificationQuote("A. 认真关系进入或结婚"), false);
|
||
assert.equal(card.scoring, false);
|
||
});
|
||
|
||
test("life period collapses a single dated year and skips unknown dates", () => {
|
||
assert.equal(lifePeriodLabel([
|
||
{ status: "confirmed", datePrecision: "unknown", occurredFrom: null, occurredTo: null },
|
||
]), "按还在比的两套盘");
|
||
assert.equal(lifePeriodLabel([
|
||
{ status: "confirmed", datePrecision: "year", occurredFrom: "2018-01-01", occurredTo: null },
|
||
{ status: "confirmed", datePrecision: "year", occurredFrom: "2020-06-01", occurredTo: null },
|
||
]), "2018–2020 年这段里");
|
||
});
|
||
|
||
test("agent choice copy rejects appearance, scars, clock times, and incomplete C/D", () => {
|
||
assert.equal(parseAgentChoiceCopy({
|
||
choice: { prompt: "你长得更像哪一种?", option_a: "外貌偏圆", option_b: "有疤痕", option_c: "都没有", option_d: "不记得" },
|
||
}), null);
|
||
assert.equal(parseAgentChoiceCopy({
|
||
...SAMPLE_COPY,
|
||
prompt: "08:12 换升时发生了什么?",
|
||
}), null);
|
||
assert.equal(parseAgentChoiceCopy({
|
||
prompt: SAMPLE_COPY.prompt,
|
||
option_a: SAMPLE_COPY.option_a,
|
||
option_b: SAMPLE_COPY.option_b,
|
||
}), null);
|
||
assert.equal(parseAgentChoiceCopy({
|
||
...SAMPLE_COPY,
|
||
option_c: SAMPLE_COPY.option_a,
|
||
}), null);
|
||
assert.ok(parseAgentChoiceCopy(SAMPLE_COPY));
|
||
});
|
||
|
||
test("GET card appears only after the Agent writes choice copy onto the focus", () => {
|
||
const withoutCopy = projectRectificationChoiceCard({ evidence: [] });
|
||
assert.equal(withoutCopy, null);
|
||
const withCopy = projectRectificationChoiceCard({
|
||
evidence: [],
|
||
activeFocus: {
|
||
intent: "collect_method_evidence",
|
||
targetDomain: null,
|
||
targetKind: null,
|
||
expectedAnswerSchema: { choice: SAMPLE_COPY },
|
||
},
|
||
});
|
||
assert.equal(withCopy?.prompt, SAMPLE_COPY.prompt);
|
||
assert.equal(withCopy?.options[2]?.label, SAMPLE_COPY.option_c);
|
||
assert.ok(parseRectificationChoiceCard(withCopy));
|
||
});
|
||
|
||
test("opening follow-up ships a discriminator frame, not a canned user card", () => {
|
||
const plan = buildMethodFollowupPlan({ evidence: [] });
|
||
assert.equal(plan.next_followup?.method_id, "dasha_events");
|
||
assert.ok(plan.next_followup?.choice_frame);
|
||
assert.equal(plan.next_followup?.choice_frame.choice_mode, "A/B/C/D");
|
||
assert.doesNotMatch(plan.next_followup?.user_prompt_hint ?? "", /请用 A\/B\/C\/D 回答/);
|
||
assert.match(plan.next_followup?.user_prompt_hint ?? "", /expectedAnswerSchema\.choice/);
|
||
});
|