- BUG-1049 (recurrence of BUG-504): the opening stem carries six examples and an example answer again and is server-owned on the zero-evidence opening; the body is two plain sentences (no 大运/盘面/代表分钟/精确到秒, no year, no question). Stem de-dup compares whole sentences / near-equality instead of a 12-char prefix, which had deleted the body's examples sentence sinceaa7ccb30(BUG-604) +dd8f35f7(BUG-648). - BUG-1050: plain step labels; a finished step label shows once and 「已完成 N 步」counts shown rows; failed rows read 「…未完成」 from the in-progress wording. - Skill 10.0.30 -> 10.0.31 (OpeningPolicy); 10.0.30 kept as deprecated. - VOICE / DESIGN / CHANGELOG / BUG_HISTORY / PROGRESS / real-device checklist and screenshots. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017eEAG8HD3mm8gsKXgk8uU8
114 lines
6.3 KiB
TypeScript
114 lines
6.3 KiB
TypeScript
import assert from "node:assert/strict";
|
||
import { readFileSync } from "node:fs";
|
||
import test from "node:test";
|
||
|
||
import { buildInferenceState } from "../src/lib/rectification-agentic/core/build-state.ts";
|
||
import { unionStillValidRange } from "../src/lib/rectification-agentic/core/credible-range.ts";
|
||
import { RECTIFICATION_USER_COPY } from "../src/lib/rectification-agentic/user-copy.ts";
|
||
import { MACHINE_VOICE_LEXICON } from "../src/lib/rectification-agentic/v9/agent-voice-lexicon.ts";
|
||
import { stripVerbalWindowChange } from "../src/lib/rectification-agentic/v9/collect-prompt.ts";
|
||
import { parseDeclaredBirthWindow } from "../src/lib/rectification-agentic/v9/declared-window-utterance.ts";
|
||
import { RECTIFICATION_SKILL_VERSION } from "../src/lib/rectification-agentic/v9/case-status.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";
|
||
|
||
const SKILL = readFileSync(
|
||
new URL("../../skills/jyotish-birth-time-rectification/SKILL.md", import.meta.url),
|
||
"utf8",
|
||
);
|
||
|
||
test("declared birth-window utterances parse ranges and around-times", () => {
|
||
assert.deepEqual(
|
||
parseDeclaredBirthWindow("我的出生时间是 14 点 45 到 14 点 50"),
|
||
{ kind: "range", start: "14:45", end: "14:50" },
|
||
);
|
||
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.equal(parseDeclaredBirthWindow("没有"), null);
|
||
assert.equal(parseDeclaredBirthWindow("2014年入学,大概秋天"), null);
|
||
assert.equal(parseDeclaredBirthWindow("继续吧"), null);
|
||
assert.equal(parseDeclaredBirthWindow("2024 年 8 月 8 日 20:00 左右分手"), null);
|
||
assert.equal(parseDeclaredBirthWindow("下午 3 点到 5 点被车撞"), null);
|
||
assert.equal(parseDeclaredBirthWindow("2019 年 10 月 23 日 9 点半入职"), null);
|
||
});
|
||
|
||
test("choice-focus declared window is 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, []);
|
||
assert.equal(
|
||
RECTIFICATION_USER_COPY.declaredWindowLockedReply,
|
||
"搜索范围是开始时按你的资料定的,校正过程中不改。想按别的时间段重来,请先到资料里改出生时间,再新建一次校正。",
|
||
);
|
||
});
|
||
|
||
test("engine cluster coverage becomes the credible-range right edge", () => {
|
||
const state = buildInferenceState({
|
||
range_start: "14:00",
|
||
range_end: "15:00",
|
||
candidates: [{
|
||
id: "14:40",
|
||
time: "14:40",
|
||
relative_support: 40,
|
||
cluster_times: ["14:40", "14:41", "14:42", "14:43", "14:44", "14:45"],
|
||
cluster_start: "14:40",
|
||
cluster_end: "14:45",
|
||
}],
|
||
events: [],
|
||
probes: [],
|
||
});
|
||
assert.deepEqual(state.candidates[0]?.cluster_range, ["14:40", "14:45"]);
|
||
assert.deepEqual(unionStillValidRange(state.candidates), ["14:40", "14:45"]);
|
||
});
|
||
|
||
test("agent body cannot verbally accept a spoken birth window", () => {
|
||
assert.ok(MACHINE_VOICE_LEXICON.includes("以你说的为准"));
|
||
assert.equal(
|
||
stripVerbalWindowChange("明白了,出生时间以你说的 14:45–14:50 为准。"),
|
||
"明白了,出生时间。",
|
||
);
|
||
assert.equal(stripVerbalWindowChange("以你说的为准。"), "");
|
||
assert.equal(stripVerbalWindowChange("明白了,以你说的为准。"), "明白了。");
|
||
assert.match(SKILL, /不得回答『以你说的为准』或改写搜索窗口/);
|
||
// 原值: "10.0.25"
|
||
// 原值: "10.0.26"
|
||
// 新值: "10.0.27"
|
||
// 原因: 出卡精度门槛 + 引导式补经历
|
||
// 原因: BUG-668 定向补事第四选项写进 Skill
|
||
// 原值: "10.0.28" / 新值: "10.0.29" / 原因: 年月阶段改口述并禁止追问本人
|
||
// 原值: "10.0.29" / 新值: "10.0.30" / 原因: D2 出卡句与 D4 卡上百分比规则写进 Skill(2026-09-26)
|
||
// 原值: "10.0.30" / 新值: "10.0.31" / 原因: 开场改大白话、题干带例子和示例回答写进 Skill OpeningPolicy(BUG-1049,2026-09-26)
|
||
assert.equal(RECTIFICATION_SKILL_VERSION, "10.0.31");
|
||
});
|