9d1c08cab1
Set-focus must not append a second narration, and a server-owned stem must not also appear as a question in the assistant body. Co-authored-by: Cursor <cursoragent@cursor.com>
124 lines
5.8 KiB
TypeScript
124 lines
5.8 KiB
TypeScript
import assert from "node:assert/strict";
|
||
import { readFileSync } from "node:fs";
|
||
import test from "node:test";
|
||
|
||
import { composeCollectSpokenAssistantText, detachCollectSpokenAssistantText, stripQuestionSentences } from "../src/lib/rectification-agentic/v9/collect-prompt.ts";
|
||
import { attachQuestionsToTurns } from "../src/lib/rectification-agentic/v9/turn-question.ts";
|
||
import { GENERIC_COLLECT_QUESTION, RECTIFICATION_USER_COPY, USER_COLLECT_QUESTION } from "../src/lib/rectification-agentic/user-copy.ts";
|
||
import { CASE_ID, FOCUS_ID, TURN_ID } from "./rectification-v9-test-support.ts";
|
||
|
||
test("composeCollectSpokenAssistantText joins by exact prompt identity", () => {
|
||
const greeting = "你好,我是生时校正助手。";
|
||
const combined = `${greeting}\n\n${GENERIC_COLLECT_QUESTION}`;
|
||
assert.equal(composeCollectSpokenAssistantText(greeting, GENERIC_COLLECT_QUESTION), combined);
|
||
assert.equal(composeCollectSpokenAssistantText(combined, GENERIC_COLLECT_QUESTION), combined);
|
||
assert.equal(composeCollectSpokenAssistantText("", GENERIC_COLLECT_QUESTION), GENERIC_COLLECT_QUESTION);
|
||
assert.equal(composeCollectSpokenAssistantText(GENERIC_COLLECT_QUESTION, GENERIC_COLLECT_QUESTION), GENERIC_COLLECT_QUESTION);
|
||
assert.equal(composeCollectSpokenAssistantText(greeting, " "), greeting);
|
||
assert.equal(
|
||
composeCollectSpokenAssistantText(` ${greeting} `, ` ${GENERIC_COLLECT_QUESTION} `),
|
||
combined,
|
||
);
|
||
});
|
||
|
||
test("detachCollectSpokenAssistantText removes only an exact trailing stem", () => {
|
||
const greeting = "你好,我是生时校正助手。";
|
||
const combined = `${greeting}\n\n${GENERIC_COLLECT_QUESTION}`;
|
||
assert.equal(detachCollectSpokenAssistantText(combined, GENERIC_COLLECT_QUESTION), greeting);
|
||
assert.equal(detachCollectSpokenAssistantText(greeting, GENERIC_COLLECT_QUESTION), greeting);
|
||
assert.equal(detachCollectSpokenAssistantText(GENERIC_COLLECT_QUESTION, GENERIC_COLLECT_QUESTION), GENERIC_COLLECT_QUESTION);
|
||
assert.equal(detachCollectSpokenAssistantText("", GENERIC_COLLECT_QUESTION), "");
|
||
});
|
||
|
||
test("GET rebuild detaches a legacy composed suffix only when asked_turn_id matches", () => {
|
||
const greeting = "你好,我是生时校正助手。";
|
||
const combined = composeCollectSpokenAssistantText(greeting, GENERIC_COLLECT_QUESTION);
|
||
const turns = [{
|
||
id: TURN_ID,
|
||
role: "assistant" as const,
|
||
text: combined,
|
||
status: "completed",
|
||
}];
|
||
const linked = attachQuestionsToTurns(turns, [{
|
||
id: FOCUS_ID,
|
||
caseId: CASE_ID,
|
||
questionId: "collect:unknown:collect_method_evidence",
|
||
intent: "collect_method_evidence",
|
||
targetEvidenceId: null,
|
||
targetDomain: null,
|
||
targetKind: null,
|
||
expectedAnswerSchema: { prompt: GENERIC_COLLECT_QUESTION, collect: true },
|
||
status: "active",
|
||
askedAt: "2026-09-02T00:00:00.000Z",
|
||
resolvedAt: null,
|
||
askedTurnId: TURN_ID,
|
||
}]);
|
||
assert.equal(linked[0]?.text, greeting);
|
||
assert.equal(linked[0]?.question?.prompt, GENERIC_COLLECT_QUESTION);
|
||
|
||
const unlinked = attachQuestionsToTurns(turns, [{
|
||
id: FOCUS_ID,
|
||
caseId: CASE_ID,
|
||
questionId: "collect:unknown:collect_method_evidence",
|
||
intent: "collect_method_evidence",
|
||
targetEvidenceId: null,
|
||
targetDomain: null,
|
||
targetKind: null,
|
||
expectedAnswerSchema: { prompt: GENERIC_COLLECT_QUESTION, collect: true },
|
||
status: "active",
|
||
askedAt: "2026-09-02T00:00:00.000Z",
|
||
resolvedAt: null,
|
||
askedTurnId: null,
|
||
}]);
|
||
assert.equal(unlinked[0]?.text, combined);
|
||
assert.equal(unlinked[0]?.question, null);
|
||
});
|
||
|
||
test("composeCollectSpokenAssistantText drops a near-duplicate restatement of the stem", () => {
|
||
const stem = USER_COLLECT_QUESTION.education;
|
||
const restated = `${stem.slice(0, 12)}还记得大概哪一年吗?`;
|
||
const body = `这条记下了。${restated}`;
|
||
const composed = composeCollectSpokenAssistantText(body, stem);
|
||
assert.equal(composed.includes(restated), false);
|
||
assert.equal(composed.split(stem).length - 1, 1);
|
||
assert.ok(composed.endsWith(stem));
|
||
});
|
||
|
||
test("runtime no longer composes the stem into assistant_message", () => {
|
||
const agentRun = readFileSync(new URL("../src/lib/rectification-agentic/v9/agent-run.ts", import.meta.url), "utf8");
|
||
const attach = readFileSync(new URL("../src/lib/rectification-agentic/v9/turn-question.ts", import.meta.url), "utf8");
|
||
assert.doesNotMatch(agentRun, /composeCollectSpokenAssistantText/);
|
||
assert.match(attach, /stripQuestionSentences/);
|
||
assert.match(attach, /if \(!focus\.askedTurnId\) continue/);
|
||
});
|
||
|
||
test("stripQuestionSentences drops a rewritten trailing question and its tag", () => {
|
||
const body = "范围已经收到,收在 05:00–05:10。你大概哪一年搬过家?有年份就行。";
|
||
const stem = "你大概是哪一年搬的家?";
|
||
assert.equal(stripQuestionSentences(body, stem), "范围已经收到,收在 05:00–05:10。");
|
||
});
|
||
|
||
test("stripQuestionSentences drops a mid-body stem and keeps the surrounding sentences", () => {
|
||
const stem = "你大概是哪一年搬的家?";
|
||
const body = "记下了。你大概是哪一年搬的家?范围还在 05:00–05:10。";
|
||
assert.equal(stripQuestionSentences(body, stem), "记下了。范围还在 05:00–05:10。");
|
||
});
|
||
|
||
test("stripQuestionSentences returns empty when the body is only a question", () => {
|
||
const stem = "你大概是哪一年搬的家?";
|
||
assert.equal(stripQuestionSentences("你大概哪一年搬过家?", stem), "");
|
||
assert.equal(RECTIFICATION_USER_COPY.collectHandoff.includes("请回答下面的问题"), false);
|
||
});
|
||
|
||
test("attachQuestionsToTurns leaves body unchanged when the turn has no focus", () => {
|
||
const body = "范围收到 05:00–05:10。你大概哪一年搬过家?";
|
||
const turns = attachQuestionsToTurns([{
|
||
id: TURN_ID,
|
||
role: "assistant" as const,
|
||
text: body,
|
||
}], []);
|
||
assert.equal(turns[0]?.text, body);
|
||
assert.equal(turns[0]?.question, null);
|
||
});
|
||
|