fix(web): keep consultation thinking above one spoken answer

Per-domain thinking trees were interleaved with sliced analysis, so a finished reply still looked like unfinished checklists. One collapsed thinking panel and one full body restores the reading order.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Jesse_Chen
2026-08-23 01:49:05 +08:00
co-authored by Cursor
parent 0285cad1fd
commit 995b752e70
12 changed files with 274 additions and 95 deletions
@@ -0,0 +1,31 @@
import assert from "node:assert/strict";
import test from "node:test";
import { promoteDefinitionLists } from "../src/lib/chat-definition-lists.ts";
test("consecutive label-colon paragraphs become a markdown list", () => {
const promoted = promoteDefinitionLists([
"行运焦点:",
"",
"月亮行运三分本命太阳(orb 0.29°):情绪与精力契合。",
"",
"天王星行运三分本命月亮(orb 0.75°):突发灵感可能冒出来。",
"",
"海王星行运对本命月亮(orb 0.87°):注意信息模糊。",
].join("\n"));
assert.match(promoted, /^- \*\*月亮行运三分本命太阳(orb 0\.29°)\*\*:情绪与精力契合。$/m);
assert.match(promoted, /^- \*\*天王星行运三分本命月亮(orb 0\.75°)\*\*:突发灵感可能冒出来。$/m);
assert.match(promoted, /行运焦点:/);
assert.equal(promoted.includes("- **行运焦点**"), false);
});
test("code fences and existing lists are left alone", () => {
const source = [
"```",
"标签:不要动",
"```",
"",
"- 已有列表:保持原样",
].join("\n");
assert.equal(promoteDefinitionLists(source), source);
});
+5 -1
View File
@@ -94,7 +94,11 @@ test("shows honest agent activity states before and during streamed text", () =>
assert.match(pageSource, /activeStreamingSections/);
assert.match(messageRowSource, /ConsultationThinkingReport/);
assert.match(reportSource, /caption="思考"/);
assert.match(reportSource, /分析/);
assert.match(reportSource, /aria-label="分析"/);
assert.match(reportSource, /revealAll/);
assert.match(reportSource, /groups=\{progressed\.map/);
assert.doesNotMatch(reportSource, /analysisForSection|splitAnswerByHeadings/);
assert.match(globalStyles, /\.markdown-list/);
assert.match(activitySource, /思考过程/);
assert.match(pageSource, /event\.type === "run\.failed"/);
assert.match(pageSource, /event\.code === "answer_truncated"/);
@@ -104,6 +104,7 @@ test("the folded audit control stays closed and never remounts the internal pane
assert.doesNotMatch(disclosureSource, /EvidenceAuditPanel/);
assert.doesNotMatch(disclosureSource, /techniqueTruth|workflowReceipt|rawReceipt/);
assert.match(contentSource, /splitSpokenAnswerAndTechniqueAudit/);
assert.match(contentSource, /showFoldedAudit = rows\.length > 0 && split\.rows\.length === 0/);
assert.match(contentSource, /TechniqueAuditDisclosure/);
assert.match(toolsSource, /ctx\.state\.techniqueAuditTable = normalizeTechniqueAuditRows/);
assert.match(routeSource, /techniqueAuditTable: state\.techniqueAuditTable/);
@@ -5,6 +5,7 @@ import {
applyThinkingSectionProgress,
consultationContinuePrompt,
consultationReportHeadings,
consultationSpokenHeadingRule,
natalConsultationThinkingPlan,
REPORT_HEADING,
splitAnswerByHeadings,
@@ -78,6 +79,16 @@ test("answer split follows the required heading order", () => {
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");
@@ -91,3 +102,7 @@ test("continue prompt asks to resume after the last complete heading", () => {
assert.match(prompt, /不要重复已写出的段落/);
assert.match(prompt, /不要写思考过程清单/);
});
test("natal spoken answers must use markdown lists for parallel points", () => {
assert.match(consultationSpokenHeadingRule("natal"), /Markdown bullet lists/);
});