Files
Jyotisha/frontend/tests/consultation-run-timeline.test.ts
T
Jesse_Chen 6b225a288c fix(web): keep later thinking below rectification tool steps
BUG-360: stream thinking and tool activity as an ordered trace so later CoT opens under 正在整理 instead of filling the first 思考 block.
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-23 17:02:06 +08:00

108 lines
4.7 KiB
TypeScript

import assert from "node:assert/strict";
import test from "node:test";
import { chartCalculationProgressLabel } from "../src/lib/consultation-activity-labels.ts";
import { generalConsultationThinkingPlan, natalConsultationThinkingPlan } from "../src/lib/consultation-thinking-plan.ts";
import {
consultationTimelineFromSettled,
emptyConsultationTimeline,
reduceConsultationTimelineEvents,
} from "../src/lib/consultation-run-timeline.ts";
test("career and wealth events append method, calculate 1/2, think, then the first write", () => {
const [foundation] = natalConsultationThinkingPlan({ domains: ["career", "wealth"] });
assert.ok(foundation);
const afterProgress = reduceConsultationTimelineEvents([
{ type: "skill.started", name: "jyotish-vedic-astrology" },
{ type: "skill.completed", name: "jyotish-vedic-astrology" },
{
type: "tool.started",
callId: "tool-1",
tool: "run-jyotish-consultation",
label: "正在计算本命盘…",
},
{
type: "activity",
phase: "chart-calculation",
label: chartCalculationProgressLabel(1, 2),
},
], emptyConsultationTimeline());
assert.deepEqual(afterProgress.rows.map((row) => row.kind), ["method", "calculate"]);
assert.equal(afterProgress.rows[1]?.status, "live");
assert.equal(afterProgress.rows[1]?.label, "正在计算本命盘(第 1/2 项)…");
const state = reduceConsultationTimelineEvents([
{ type: "thinking.section", ...foundation },
{ type: "answer.delta", text: "## 统一参数与原始结构\n岁差 Lahiri。\n" },
], afterProgress);
assert.deepEqual(state.rows.map((row) => row.kind), ["method", "calculate", "think", "write"]);
assert.equal(state.rows[1]?.status, "done");
assert.equal(state.rows[2]?.label, "先整理本盘的统一参数");
assert.equal(state.rows[2]?.status, "done");
assert.equal(state.rows[3]?.id, "write-统一参数与原始结构");
assert.equal(state.rows[3]?.status, "live");
assert.match(state.rows[3]?.label ?? "", /正在写统一参数与原始结构/);
});
test("thinking after a tool starts a new think row instead of reopening the first", () => {
const state = reduceConsultationTimelineEvents([
{ type: "thinking.delta", text: "先确认用户说的是入职当天。" },
{
type: "tool.started",
callId: "tool-1",
tool: "run-jyotish-consultation",
label: "正在计算本命盘…",
},
{ type: "thinking.delta", text: "再按宫位写。" },
]);
assert.deepEqual(state.rows.map((row) => row.kind), ["think", "calculate", "think"]);
assert.equal(state.rows[0]?.status, "done");
assert.equal(state.rows[0]?.thinkingText, "先确认用户说的是入职当天。");
assert.equal(state.rows[2]?.status, "live");
assert.equal(state.rows[2]?.thinkingText, "再按宫位写。");
assert.notEqual(state.rows[0]?.id, state.rows[2]?.id);
});
test("idle chat without a natal calculation has no calculate row", () => {
const [section] = generalConsultationThinkingPlan();
assert.ok(section);
const state = reduceConsultationTimelineEvents([
{ type: "skill.started", name: "jyotish-vedic-astrology" },
{ type: "skill.completed", name: "jyotish-vedic-astrology" },
{ type: "thinking.section", ...section },
{ type: "thinking.delta", text: "先按问题组织回答。" },
{ type: "answer.delta", text: "今日宜稳,不宜强推。" },
{
type: "run.completed",
receipt: {
runId: "settled",
runtime: "mastra-agentic",
skill: { name: "jyotish-vedic-astrology", loaded: true, referenceReads: 0, methodologySections: 0 },
steps: [],
workflow: { route: "general-no-birth-time", status: "ready", preciseTiming: "blocked", missingLayers: ["birth-minute"] },
},
},
]);
assert.equal(state.rows.some((row) => row.kind === "calculate"), false);
assert.deepEqual(state.rows.map((row) => row.kind), ["method", "think", "write"]);
assert.equal(state.rows[1]?.thinkingText, "先按问题组织回答。");
});
test("settled natal messages hydrate a calculate row from domain sections", () => {
const sections = natalConsultationThinkingPlan({ domains: ["career", "wealth"] });
const rows = consultationTimelineFromSettled({
text: "## 统一参数与原始结构\n岁差。\n## 事业\n方向稳定。\n",
thinkingSections: sections,
thinkingText: "先对照 D10。",
});
assert.ok(rows.some((row) => row.kind === "calculate"));
assert.ok(rows.some((row) => row.kind === "think" && row.label.includes("事业")));
const calculate = rows.find((row) => row.kind === "calculate");
assert.ok(calculate?.queries?.includes("事业"));
assert.ok(calculate?.queries?.includes("D10"));
assert.ok(calculate?.queries?.includes("财富"));
});