d7afe5b50d
Coverage-complete ties never persisted A/B/C/D because contrast probes were stamped with an answered education quality probe, remaining minutes were asked as window D10 signs, and 「没有了」 missed the stop pattern. Co-authored-by: Cursor <cursoragent@cursor.com>
156 lines
7.3 KiB
TypeScript
156 lines
7.3 KiB
TypeScript
import assert from "node:assert/strict";
|
||
import test from "node:test";
|
||
|
||
import {
|
||
finalizeRectificationSpokenAndThinking,
|
||
isRectificationProcessNarration,
|
||
nextStableChannelDelta,
|
||
settleRectificationSpokenAndThinking,
|
||
splitRectificationSpokenAndThinking,
|
||
} from "../src/lib/rectification-agentic/v9/spoken-answer.ts";
|
||
|
||
test("a plain spoken follow-up stays on the answer channel", () => {
|
||
const spoken = [
|
||
"记下了,大约六岁入学小学。",
|
||
"接下来你大概哪一年上的初中?说个大概年份或范围就行。",
|
||
].join("\n\n");
|
||
|
||
assert.deepEqual(splitRectificationSpokenAndThinking(spoken), {
|
||
thinking: "",
|
||
spoken,
|
||
});
|
||
});
|
||
|
||
test("process self-talk leaves the spoken conclusion on the answer channel", () => {
|
||
const processTalk = [
|
||
"用户提到先给了一个很晚的年份,后又改口说六岁入学。这里有个明显的内部矛盾。",
|
||
"但按 skill 规则,日期精度真实保留,不得猜补。datePrecision 用 year,occurredFrom 只能按用户原话来。",
|
||
"我决定先按六岁入学记下来,再在正文里确认那句晚年份是不是口误。",
|
||
"让我调用 batch 写入 education_start。",
|
||
].join("\n\n");
|
||
const spoken = [
|
||
"入学小学这条先按大约六岁记下。你第一句提到的那个很晚的年份,我理解是口误对吗?",
|
||
"接下来你大概哪一年上的初中?说个大概年份或范围就行。",
|
||
].join("\n\n");
|
||
|
||
assert.deepEqual(splitRectificationSpokenAndThinking(`${processTalk}\n\n${spoken}`), {
|
||
thinking: processTalk,
|
||
spoken,
|
||
});
|
||
});
|
||
|
||
test("English process talk is classified as thinking, not the spoken answer", () => {
|
||
const split = splitRectificationSpokenAndThinking([
|
||
"The proposedKind value was rejected. Retrying with education_start.",
|
||
"记下了,那年九月上大学。",
|
||
].join("\n\n"));
|
||
|
||
assert.match(split.thinking, /proposedKind/);
|
||
assert.equal(split.spoken, "记下了,那年九月上大学。");
|
||
});
|
||
|
||
test("stable channel deltas only emit the newly classified suffix", () => {
|
||
assert.equal(nextStableChannelDelta("", "先核对升学年份。"), "先核对升学年份。");
|
||
assert.equal(
|
||
nextStableChannelDelta("先核对升学年份。", "先核对升学年份。\n\n再问初中。"),
|
||
"\n\n再问初中。",
|
||
);
|
||
assert.equal(nextStableChannelDelta("先核对升学年份。", "另一段"), "");
|
||
});
|
||
|
||
test("a lone process paragraph stays in thinking until a spoken conclusion arrives", () => {
|
||
const processTalk = "用户提到先给了一个很晚的年份。这里有个明显的内部矛盾。";
|
||
assert.deepEqual(splitRectificationSpokenAndThinking(`${processTalk}\n\n`), {
|
||
thinking: processTalk,
|
||
spoken: "",
|
||
});
|
||
});
|
||
|
||
test("truncated third-person batch-tool narration stays in thinking", () => {
|
||
const truncated = "用户在上一轮里提供了两件带日期的经历。我需要用批量工具写入这些证据。用户";
|
||
assert.equal(isRectificationProcessNarration(truncated), true);
|
||
assert.deepEqual(splitRectificationSpokenAndThinking(truncated), {
|
||
thinking: truncated,
|
||
spoken: "",
|
||
});
|
||
assert.deepEqual(finalizeRectificationSpokenAndThinking(truncated), {
|
||
thinking: truncated,
|
||
spoken: "",
|
||
});
|
||
});
|
||
|
||
test("finalizing does not promote process-only self-talk into the spoken answer", () => {
|
||
const first = "用户提到先给了一个很晚的年份。这里有个明显的内部矛盾。";
|
||
const last = "但按 skill 规则,日期精度真实保留,不得猜补。";
|
||
const processTalk = `${first}\n\n${last}`;
|
||
assert.deepEqual(finalizeRectificationSpokenAndThinking(processTalk), {
|
||
thinking: processTalk,
|
||
spoken: "",
|
||
});
|
||
});
|
||
|
||
test("Chinese interview planning after tools stays out of the spoken answer", () => {
|
||
const processTalk = [
|
||
"读取校正记录",
|
||
"整理多条事件证据",
|
||
"比较候选时间",
|
||
"职业类型已经记录,并重新计算了候选。虽然职业领域现在有信息了,但账户里仍然只有 3 件带评分日期的事件,还不足以拉开候选范围区间(当前不可分宽度 8 分钟)。",
|
||
"方法覆盖上,职业这一层已经通过 occupation_note 补齐了(不计分),但还需要更多带日期的经历来区分候选。当前还应继续收集事件。",
|
||
"我用自然语言再问一件带大概年份的经历。根据 method_followup_plan,职业已经覆盖。当前 open_question 为空,next_action 为空。",
|
||
"候选区分尚未充分(not_separated),应继续收集。",
|
||
"让我继续访谈,问一件能帮助区分候选的职业前事。",
|
||
"本轮对照了Gochara、D1 本命盘、D10 事业分盘。",
|
||
].join("\n\n");
|
||
const spoken = "职业类型已经记下。接下来想请你回想一下这份工作的时间段——**你大概是在哪一年入职的?**又是什么时候离开的?只要个大概年份就行。";
|
||
|
||
const split = splitRectificationSpokenAndThinking(`${processTalk}\n\n${spoken}`);
|
||
assert.equal(split.spoken, spoken);
|
||
assert.match(split.thinking, /occupation_note/);
|
||
assert.match(split.thinking, /读取校正记录/);
|
||
assert.match(split.thinking, /本轮对照了/);
|
||
assert.doesNotMatch(split.thinking, /你大概是在哪一年入职/);
|
||
assert.equal(isRectificationProcessNarration("读取校正记录"), true);
|
||
assert.equal(isRectificationProcessNarration(spoken), false);
|
||
});
|
||
|
||
test("planning about candidate_contrast_packet stays out of the spoken answer", () => {
|
||
const processTalk = [
|
||
"这意味着:方法资料已齐,还不能出牌。",
|
||
"服务器给了 candidate_contrast_packet,choice_frame 已写好。",
|
||
"第 7 条边界:id=ask_candidate_discriminator 时不得 offer。",
|
||
].join("\n\n");
|
||
const spoken = "那次高考或重要考试,发挥有没有明显失常、压力很大?说有或没有就行。";
|
||
|
||
const split = splitRectificationSpokenAndThinking(`${processTalk}\n\n${spoken}`);
|
||
assert.equal(split.spoken, spoken);
|
||
assert.match(split.thinking, /这意味着/);
|
||
assert.match(split.thinking, /candidate_contrast_packet/);
|
||
assert.match(split.thinking, /不得 offer/);
|
||
assert.match(split.thinking, /第 7 条边界/);
|
||
assert.doesNotMatch(split.thinking, /发挥有没有明显失常/);
|
||
assert.equal(isRectificationProcessNarration("这意味着:方法资料已齐"), true);
|
||
assert.equal(isRectificationProcessNarration("服务器给了 candidate_contrast_packet"), true);
|
||
assert.equal(isRectificationProcessNarration(spoken), false);
|
||
|
||
const settled = settleRectificationSpokenAndThinking(`${processTalk}\n\n${spoken}`, "");
|
||
assert.equal(settled.spoken, spoken);
|
||
assert.doesNotMatch(settled.spoken, /candidate_contrast_packet/);
|
||
});
|
||
|
||
test("leaked process text on the answer channel is not mixed into native thinking", () => {
|
||
const processTalk = "用户在上一轮里提供了两件带日期的经历。我需要用批量工具写入这些证据。用户";
|
||
const spoken = "记下了升学这两件。接下来有没有一件带大概年份的工作变化?";
|
||
assert.deepEqual(settleRectificationSpokenAndThinking(processTalk, ""), {
|
||
thinking: processTalk,
|
||
spoken: "",
|
||
});
|
||
assert.deepEqual(settleRectificationSpokenAndThinking(spoken, processTalk), {
|
||
thinking: processTalk,
|
||
spoken,
|
||
});
|
||
assert.deepEqual(settleRectificationSpokenAndThinking(processTalk, processTalk), {
|
||
thinking: processTalk,
|
||
spoken: "",
|
||
});
|
||
});
|