import assert from "node:assert/strict"; import { readFileSync } from "node:fs"; import test from "node:test"; import { composeCollectSpokenAssistantText, detachCollectSpokenAssistantText } from "../src/lib/rectification-agentic/v9/collect-prompt.ts"; import { attachQuestionsToTurns } from "../src/lib/rectification-agentic/v9/turn-question.ts"; import { GENERIC_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("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, /detachCollectSpokenAssistantText/); assert.match(attach, /if \(!focus\.askedTurnId\) continue/); });