import assert from "node:assert/strict"; import { readFileSync } from "node:fs"; import test from "node:test"; import { COLUMN_COMPARE_BUDGET_MS, columnTimesForSlowCompare, } from "../src/lib/rectification-agentic/v9/column-times-for-compare.ts"; import { parseDeclaredBirthWindow } from "../src/lib/rectification-agentic/v9/declared-window-utterance.ts"; import { RECTIFICATION_USER_COPY } from "../src/lib/rectification-agentic/user-copy.ts"; import { replyToDeclaredBirthWindow } from "../src/lib/rectification-agentic/v9/agent-route-typed-message.ts"; import { ndjsonEvents, routeContextFixture, ROUTE_TURN_ID } from "./rectification-agent-route-fixtures.ts"; import { readRectificationAgentRouteFile } from "./rectification-agent-route-surface.ts"; test("dated or event-clock sentences are not treated as a declared birth window", () => { assert.equal(parseDeclaredBirthWindow("2024 年 8 月 8 日 20:00 左右分手"), null); assert.equal(parseDeclaredBirthWindow("下午 3 点到 5 点被车撞"), null); assert.equal(parseDeclaredBirthWindow("2019 年 10 月 23 日 9 点半入职"), null); assert.equal(parseDeclaredBirthWindow("20:00 左右分手"), null); assert.equal(parseDeclaredBirthWindow("2014年入学,大概秋天"), null); }); test("birth-context and bare clock ranges still count as a declared window", () => { assert.deepEqual( parseDeclaredBirthWindow("我的出生时间是 14 点 45 到 14 点 50"), { kind: "range", start: "14:45", end: "14:50" }, ); assert.deepEqual( parseDeclaredBirthWindow("我是 14:47 左右生的"), { kind: "around", time: "14:47" }, ); assert.deepEqual( parseDeclaredBirthWindow("14:45–14:50"), { kind: "range", start: "14:45", end: "14:50" }, ); assert.deepEqual( parseDeclaredBirthWindow("14:47 左右"), { kind: "around", time: "14:47" }, ); assert.deepEqual( parseDeclaredBirthWindow("大概 14:47 左右"), { kind: "around", time: "14:47" }, ); }); test("choice-focus declared window is still answered from the route before the model", async () => { // 原值: route.ts 源码里 `if (action === "message" && parseDeclaredBirthWindow` 早于 // `const selectedModel = isStructuredChoice`(以及早于 `await classifyTurnIntentWithRetry`)。 // 新值: route.ts 先 `await replyToDeclaredBirthWindow(context)` 并在有回复时直接 return,之后才解析模型、才进打字预检 // (分类器只在 handleRectificationTypedMessage 里);再直接调用 replyToDeclaredBirthWindow: // 报时段的消息落一条确定性 turn、流里给锁定回复和 run.completed,别的消息返回 null、不碰持久层。 // 原因: TASK-rectification-code-split-20260926 这一截原样搬进 agent-route-typed-message.ts;调用断言强于源码顺序。 const routeFile = readRectificationAgentRouteFile("../src/app/api/rectification/agent/route.ts"); const intercept = routeFile.indexOf("const declaredWindowReply = await replyToDeclaredBirthWindow(context);"); const selectedModel = routeFile.indexOf("const selectedModel = isStructuredChoice"); const typedPreflight = routeFile.indexOf("return handleRectificationTypedMessage("); assert.ok(intercept > 0); assert.match(routeFile, /if \(declaredWindowReply\) return declaredWindowReply;/); assert.ok(selectedModel > intercept); assert.ok(typedPreflight > selectedModel); const declared = routeContextFixture({ action: "message", message: "大概 14:47 左右" }); const reply = await replyToDeclaredBirthWindow(declared.context); assert.ok(reply); assert.equal(reply.status, 200); assert.deepEqual(await ndjsonEvents(reply), [ { type: "answer.delta", text: RECTIFICATION_USER_COPY.declaredWindowLockedReply }, { type: "run.completed", turnId: ROUTE_TURN_ID }, ]); assert.deepEqual(declared.rpcCalls.map((call) => call.fn), ["append_agentic_rectification_turn"]); assert.equal(declared.rpcCalls[0].args.p_assistant_message, RECTIFICATION_USER_COPY.declaredWindowLockedReply); const ordinary = routeContextFixture({ action: "message", message: "2019 年换了工作" }); assert.equal(await replyToDeclaredBirthWindow(ordinary.context), null); assert.deepEqual(ordinary.rpcCalls, []); }); test("score persist passes active minutes only after a slow by_time compare", () => { const persist = readFileSync( new URL("../src/lib/rectification-agentic/v9/score-persist.ts", import.meta.url), "utf8", ); assert.match(persist, /columnTimesForSlowCompare/); assert.match(persist, /columnTimes,/); }); test("slow by_time compares may pass only active inference minutes", () => { assert.equal( columnTimesForSlowCompare({ previousColumnCompareMs: 1200, activeTimes: ["14:46", "14:47", "14:48"] }), undefined, ); assert.deepEqual( columnTimesForSlowCompare({ previousColumnCompareMs: COLUMN_COMPARE_BUDGET_MS + 1, activeTimes: ["14:46", "14:47", "14:46", "bad"], }), ["14:46", "14:47"], ); assert.equal( columnTimesForSlowCompare({ previousColumnCompareMs: 4000, activeTimes: [] }), undefined, ); });