





















@@ -50,9 +50,9 @@ describe("buildChatItems", () => {
5050it("collapses consecutive duplicate text messages into one rendered item with a count", () => {
5151const groups = messageGroups({
5252messages: [
53-{ role: "assistant", content: [{ type: "text", text: "HEARTBEAT_OK" }], timestamp: 1 },
54-{ role: "assistant", content: [{ type: "text", text: "HEARTBEAT_OK" }], timestamp: 2 },
55-{ role: "assistant", content: [{ type: "text", text: "HEARTBEAT_OK" }], timestamp: 3 },
53+{ role: "assistant", content: [{ type: "text", text: "Same update" }], timestamp: 1 },
54+{ role: "assistant", content: [{ type: "text", text: "Same update" }], timestamp: 2 },
55+{ role: "assistant", content: [{ type: "text", text: "Same update" }], timestamp: 3 },
5656],
5757});
5858@@ -61,6 +61,96 @@ describe("buildChatItems", () => {
6161expect(groups[0].messages[0]).toMatchObject({ duplicateCount: 3 });
6262});
636364+it("suppresses assistant HEARTBEAT_OK acknowledgements before rendering history", () => {
65+const groups = messageGroups({
66+messages: [
67+{ role: "assistant", content: [{ type: "text", text: "HEARTBEAT_OK" }], timestamp: 1 },
68+{ role: "assistant", content: "HEARTBEAT_OK", timestamp: 2 },
69+{ role: "user", content: [{ type: "text", text: "HEARTBEAT_OK" }], timestamp: 3 },
70+{ role: "assistant", content: [{ type: "text", text: "Visible reply" }], timestamp: 4 },
71+],
72+});
73+74+expect(groups).toHaveLength(2);
75+expect(groups[0].role).toBe("user");
76+expect(groups[1].role).toBe("assistant");
77+expect(groups[1].messages[0].message).toMatchObject({
78+content: [{ type: "text", text: "Visible reply" }],
79+});
80+});
81+82+it("suppresses assistant HEARTBEAT_OK acknowledgements that carry hidden thinking blocks", () => {
83+const groups = messageGroups({
84+messages: [
85+{
86+role: "assistant",
87+content: [
88+{ type: "thinking", thinking: "Checking scheduled work." },
89+{
90+type: "text",
91+text: "HEARTBEAT_OK",
92+textSignature: JSON.stringify({ v: 1, phase: "final_answer" }),
93+},
94+],
95+timestamp: 1,
96+},
97+{
98+role: "assistant",
99+content: [
100+{ id: "rs_1", type: "reasoning" },
101+{ type: "text", text: "HEARTBEAT_OK" },
102+],
103+timestamp: 2,
104+},
105+{
106+role: "assistant",
107+content: [
108+{ type: "thinking", thinking: "Useful hidden reasoning." },
109+{ type: "text", text: "Visible reply" },
110+],
111+timestamp: 3,
112+},
113+],
114+});
115+116+expect(groups).toHaveLength(1);
117+expect(groups[0].messages).toHaveLength(1);
118+expect(groups[0].messages[0].message).toMatchObject({
119+content: [
120+{ type: "thinking", thinking: "Useful hidden reasoning." },
121+{ type: "text", text: "Visible reply" },
122+],
123+});
124+});
125+126+it("keeps HEARTBEAT_OK turns that carry visible non-text content", () => {
127+const canvasBlock = createAssistantCanvasBlock({ suffix: "heartbeat_visible_content" });
128+const groups = messageGroups({
129+messages: [
130+{
131+role: "assistant",
132+content: [{ type: "text", text: "HEARTBEAT_OK" }, canvasBlock],
133+timestamp: 1,
134+},
135+],
136+});
137+138+expect(groups).toHaveLength(1);
139+expect(groups[0].messages).toHaveLength(1);
140+expect(firstMessageContent(groups[0]).some((block) => isCanvasBlock(block))).toBe(true);
141+});
142+143+it("suppresses active HEARTBEAT_OK streams before rendering", () => {
144+const items = buildChatItems(
145+createProps({
146+stream: "HEARTBEAT_OK",
147+streamStartedAt: 1,
148+}),
149+);
150+151+expect(items).toEqual([]);
152+});
153+64154it("does not collapse duplicate text messages separated by another message", () => {
65155const groups = messageGroups({
66156messages: [
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。