04463e9af3
Keep provider thinking on a separate channel so process talk is not billed as the spoken reply (BUG-359). Co-authored-by: Cursor <cursoragent@cursor.com>
130 lines
4.8 KiB
TypeScript
130 lines
4.8 KiB
TypeScript
import assert from "node:assert/strict";
|
|
import test from "node:test";
|
|
|
|
import {
|
|
applyThinkingSectionProgress,
|
|
consultationComposeHeadingGroups,
|
|
consultationContinuePrompt,
|
|
consultationReportHeadings,
|
|
consultationSectionPrompt,
|
|
consultationSpokenHeadingRule,
|
|
natalConsultationThinkingPlan,
|
|
REPORT_HEADING,
|
|
splitAnswerByHeadings,
|
|
visibleThinkingSteps,
|
|
windowConsultationThinkingPlan,
|
|
} from "../src/lib/consultation-thinking-plan.ts";
|
|
|
|
test("multi-domain thinking plan uses Chinese product labels and hides tool ids", () => {
|
|
const sections = natalConsultationThinkingPlan({
|
|
domains: ["career", "wealth"],
|
|
requiredBlocks: [
|
|
"raw_structure",
|
|
"raman_six_step",
|
|
"yoga_table",
|
|
"timing",
|
|
"synthesis",
|
|
"technique_audit_table",
|
|
"modern_wrap",
|
|
],
|
|
mustUseLayers: ["D1", "run-jyotish-consultation", "D10", "skill_read"],
|
|
});
|
|
const encoded = JSON.stringify(sections);
|
|
assert.deepEqual(sections.map((section) => section.heading), [
|
|
REPORT_HEADING.foundation,
|
|
"事业",
|
|
"财富",
|
|
REPORT_HEADING.audit,
|
|
]);
|
|
assert.ok(sections.some((section) => section.title === "接下来分析你的事业"));
|
|
assert.ok(sections.some((section) => section.title === "接下来分析你的财富"));
|
|
assert.equal(encoded.includes("run-jyotish"), false);
|
|
assert.equal(encoded.includes("skill_read"), false);
|
|
assert.ok(sections[0]?.steps.some((step) => step.label.includes("D1")));
|
|
});
|
|
|
|
test("window thinking plan stays on stable-layer copy", () => {
|
|
const [section] = windowConsultationThinkingPlan();
|
|
assert.equal(section?.heading, REPORT_HEADING.foundation);
|
|
assert.equal(JSON.stringify(section).includes("run-jyotish"), false);
|
|
});
|
|
|
|
test("visible steps collapse extras into a remaining count", () => {
|
|
const steps = Array.from({ length: 6 }, (_, index) => ({
|
|
id: `s${index}`,
|
|
label: `步骤 ${index + 1}`,
|
|
status: "pending" as const,
|
|
}));
|
|
const visible = visibleThinkingSteps(steps);
|
|
assert.equal(visible.visible.length, 4);
|
|
assert.equal(visible.hiddenCount, 2);
|
|
});
|
|
|
|
test("answer split follows the required heading order", () => {
|
|
const headings = consultationReportHeadings(["career", "wealth"]);
|
|
const text = [
|
|
"## 统一参数与原始结构",
|
|
"岁差 Lahiri。",
|
|
"## 事业",
|
|
"事业方向稳定。",
|
|
"## 财富",
|
|
"财运看 2/11 宫。",
|
|
"## 技法审计表",
|
|
"| 技法 | 状态 |",
|
|
"## 现代生活",
|
|
"先把合同条款写清楚。",
|
|
].join("\n");
|
|
const { slices } = splitAnswerByHeadings(text, headings);
|
|
assert.match(slices["事业"] ?? "", /事业方向稳定/);
|
|
assert.match(slices["财富"] ?? "", /财运看/);
|
|
assert.match(slices[REPORT_HEADING.audit] ?? "", /技法/);
|
|
assert.match(slices[REPORT_HEADING.wrap] ?? "", /合同条款/);
|
|
});
|
|
|
|
test("settled answers mark leftover thinking sections done", () => {
|
|
const sections = natalConsultationThinkingPlan({ domains: ["career"] });
|
|
const progressed = applyThinkingSectionProgress(
|
|
sections,
|
|
"今日行运已经写完,但没有领域二级标题。",
|
|
{ settled: true },
|
|
);
|
|
assert.equal(progressed.every((section) => section.steps.every((step) => step.status === "done")), true);
|
|
});
|
|
|
|
test("progress marks the current heading active and completed ones done", () => {
|
|
const sections = natalConsultationThinkingPlan({ domains: ["career"] });
|
|
const progressed = applyThinkingSectionProgress(sections, "## 统一参数与原始结构\n岁差。\n");
|
|
assert.equal(progressed[0]?.steps[0]?.status, "done");
|
|
assert.equal(progressed[1]?.steps[0]?.status, "active");
|
|
});
|
|
|
|
test("continue prompt asks to resume after the last complete heading", () => {
|
|
const prompt = consultationContinuePrompt("## 事业\n方向稳定。\n");
|
|
assert.match(prompt, /最后一个完整标题是:## 事业/);
|
|
assert.match(prompt, /不要重复已写出的段落/);
|
|
assert.match(prompt, /不要写思考过程清单/);
|
|
});
|
|
|
|
test("natal spoken answers must use markdown lists for parallel points", () => {
|
|
assert.match(consultationSpokenHeadingRule("natal"), /Markdown bullet lists/);
|
|
});
|
|
|
|
test("close heading group splits audit and modern-life into two compose slices", () => {
|
|
const groups = consultationComposeHeadingGroups(natalConsultationThinkingPlan({
|
|
domains: ["career", "wealth"],
|
|
}));
|
|
assert.deepEqual(groups.map((group) => [...group.headings]), [
|
|
[REPORT_HEADING.foundation],
|
|
["事业"],
|
|
["财富"],
|
|
[REPORT_HEADING.audit, REPORT_HEADING.wrap],
|
|
]);
|
|
});
|
|
|
|
test("section prompt asks for one heading and forbids another calculation", () => {
|
|
const prompt = consultationSectionPrompt("事业", "## 统一参数与原始结构\n岁差。\n");
|
|
assert.match(prompt, /只写这一个二级标题及其正文:## 事业/);
|
|
assert.match(prompt, /不要再调用排盘工具/);
|
|
assert.match(prompt, /统一参数与原始结构/);
|
|
});
|