- Evidence turns whose batch accepted items: the body is the server recap built from those items (two or fewer listed inline, more as a count); the model no longer restates them (both prompt sets updated). - The case snapshot carries each assistant turn's recorded evidence from the ledger (rejected/superseded excluded); the message shows it in a collapsed <details> list when more than two. - Chinese date labels read straight into the event phrase (2016年入学); ISO labels keep their space. - Assertions updated with original/new/reason notes. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017eEAG8HD3mm8gsKXgk8uU8
137 lines
6.0 KiB
TypeScript
137 lines
6.0 KiB
TypeScript
/**
|
|
* TASK-rectification-message-cleanup-20261001.
|
|
* BUG-1137 (D3): a settled rectification reply carries no 「已完成 N 步」
|
|
* receipt; while it is written the steps are its waiting state. Consultation
|
|
* replies keep their settled receipt.
|
|
*/
|
|
import assert from "node:assert/strict";
|
|
import test from "node:test";
|
|
import { createElement, type MutableRefObject } from "react";
|
|
import { renderToString } from "react-dom/server";
|
|
|
|
import { ChatMessageRow } from "../src/components/chat-message-row.tsx";
|
|
import {
|
|
RectificationMessageEntry,
|
|
type RectificationMessageActions,
|
|
type RenderMessage,
|
|
} from "../src/components/rectification-message-entry.tsx";
|
|
|
|
const actionsRef = { current: {
|
|
submitChoice() {}, submitStop() {}, copyMessage() {}, onFeedback() {},
|
|
} } as MutableRefObject<RectificationMessageActions>;
|
|
|
|
const TRACE = [
|
|
{ id: "s1", kind: "tool" as const, tool: "rectification-read-case", status: "done" as const, label: "看了你的资料" },
|
|
{ id: "s2", kind: "tool" as const, tool: "rectification-record-evidence-batch", status: "done" as const, label: "记录你说的经历" },
|
|
{ id: "s3", kind: "tool" as const, tool: "rectification-set-focus", status: "done" as const, label: "准备好下一个问题" },
|
|
];
|
|
|
|
function entry(state: RenderMessage["state"]): string {
|
|
const message = {
|
|
role: "assistant",
|
|
text: state === "settled" ? "记下了 3 件事。" : "",
|
|
renderKey: "m1",
|
|
state,
|
|
activityTrace: TRACE,
|
|
} as unknown as RenderMessage;
|
|
return renderToString(createElement(RectificationMessageEntry, {
|
|
message,
|
|
busy: state !== "settled",
|
|
readonly: false,
|
|
currentQuestionFocusId: null,
|
|
interactive: state === "settled",
|
|
liveChoiceCard: null,
|
|
choiceNonce: 0,
|
|
savedTime: null,
|
|
copied: false,
|
|
feedback: undefined,
|
|
actionsRef,
|
|
}));
|
|
}
|
|
|
|
test("a settled rectification reply renders no step receipt (BUG-1137)", () => {
|
|
const html = entry("settled");
|
|
assert.match(html, /记下了 3 件事/);
|
|
assert.doesNotMatch(html, /已完成 \d+ 步/);
|
|
assert.doesNotMatch(html, /consultation-run-timeline/);
|
|
for (const step of ["看了你的资料", "记录你说的经历", "准备好下一个问题"]) assert.doesNotMatch(html, new RegExp(step));
|
|
});
|
|
|
|
test("while a rectification reply is written its steps stay the waiting state (BUG-1137)", () => {
|
|
const html = entry("streaming");
|
|
assert.match(html, /consultation-run-timeline/);
|
|
assert.match(html, /正在分析/);
|
|
assert.match(html, /准备好下一个问题/);
|
|
});
|
|
|
|
test("a settled consultation reply keeps its 「已完成 N 步」 receipt (BUG-1137 scope lock)", () => {
|
|
const html = renderToString(createElement(ChatMessageRow, {
|
|
message: {
|
|
role: "assistant",
|
|
content: "回答正文。",
|
|
text: "回答正文。",
|
|
renderKey: "c1",
|
|
state: "settled",
|
|
timeline: [{ id: "method", kind: "method", status: "done", label: "已加载方法" }],
|
|
} as never,
|
|
}));
|
|
assert.match(html, /已完成 1 步/);
|
|
});
|
|
|
|
import { evidenceTurnRecap } from "../src/lib/rectification-agentic/user-copy.ts";
|
|
import { recordedEvidenceByTurn } from "../src/lib/rectification-agentic/v9/turn-question.ts";
|
|
import { messagesFromTurns } from "../src/lib/rectification-chat-messages.ts";
|
|
|
|
test("the server recap lists two or fewer items and counts more (BUG-1136)", () => {
|
|
assert.equal(evidenceTurnRecap([]), null);
|
|
assert.equal(evidenceTurnRecap(["2016年 入学"]), "记下了:2016年 入学。");
|
|
assert.equal(evidenceTurnRecap(["2016年 入学", "2020年 毕业"]), "记下了:2016年 入学、2020年 毕业。");
|
|
assert.equal(evidenceTurnRecap(Array.from({ length: 12 }, (_, i) => `${2000 + i}年 事${i}`)), "记下了 12 件事。");
|
|
});
|
|
|
|
test("recorded items come from the ledger by turn, without rejected or superseded rows (BUG-1136)", () => {
|
|
const row = (sourceTurnId: string, summary: string, status = "confirmed", datePrecision = "year", occurredFrom = "2016-01-01") => ({
|
|
sourceTurnId, status, datePrecision, occurredFrom, occurredTo: null, summary,
|
|
});
|
|
const byTurn = recordedEvidenceByTurn([
|
|
row("t2", "上大学"),
|
|
row("t2", "毕业", "pending_confirmation", "month", "2020-06-01"),
|
|
row("t2", "被拒的", "rejected"),
|
|
row("t2", "被替换的", "superseded"),
|
|
row("t4", "换工作", "confirmed", "day", "2021-03-05"),
|
|
]);
|
|
assert.deepEqual(byTurn.get("t2"), ["2016年上大学", "2020-06 毕业"]);
|
|
assert.deepEqual(byTurn.get("t4"), ["2021-03-05 换工作"]);
|
|
});
|
|
|
|
function settledEntry(message: RenderMessage): string {
|
|
return renderToString(createElement(RectificationMessageEntry, {
|
|
message, busy: false, readonly: false, currentQuestionFocusId: null, interactive: true,
|
|
liveChoiceCard: null, choiceNonce: 0, savedTime: null, copied: false, feedback: undefined, actionsRef,
|
|
}));
|
|
}
|
|
|
|
test("twelve recorded items: one body line and the twelve items collapsed under it (BUG-1136)", () => {
|
|
const items = Array.from({ length: 12 }, (_, i) => `${2000 + i}年 虚构事件${i + 1}`);
|
|
const [message] = messagesFromTurns([
|
|
{ id: "t2", role: "assistant", text: "记下了 12 件事。", status: "completed", recorded_evidence: items },
|
|
]);
|
|
assert.deepEqual(message!.recordedEvidence, items);
|
|
const html = settledEntry(message!);
|
|
assert.match(html, /记下了 12 件事。/);
|
|
assert.match(html, /<details class="rectification-recorded-evidence">/);
|
|
assert.match(html, /看记下的 12 件/);
|
|
assert.equal(html.split("<li").length - 1, 12);
|
|
for (const item of items) assert.ok(html.includes(item), item);
|
|
assert.doesNotMatch(html, /<details[^>]* open/);
|
|
});
|
|
|
|
test("two recorded items stay inline in the body with no list (BUG-1136)", () => {
|
|
const [message] = messagesFromTurns([
|
|
{ id: "t2", role: "assistant", text: "记下了:2016年 入学、2020年 毕业。", status: "completed", recorded_evidence: ["2016年 入学", "2020年 毕业"] },
|
|
]);
|
|
const html = settledEntry(message!);
|
|
assert.match(html, /记下了:2016年 入学、2020年 毕业。/);
|
|
assert.doesNotMatch(html, /rectification-recorded-evidence/);
|
|
});
|