6356d7b549
The rectification surface had its own activity pipeline: a trace panel with a 20px canvas orb clipped inside a 14px marker, a list that never collapsed, a second receipt disclosure under every reply, an inline failure banner and a staged label while regenerating. Its trace and receipt are now projected onto ConsultationTimelineRow so both surfaces render one ConsultationRunTimeline with one live marker and one settled summary; receipt methods become source chips on the last completed row. The unreachable sections-report/step-tree path, the trace panel, the receipt component and the thinking-orbs dependency are removed. The server-side drop of rectification thinking deltas is untouched. BUG-476 Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JUei7K13cYxLHE3Axe4A45
137 lines
5.4 KiB
TypeScript
137 lines
5.4 KiB
TypeScript
import assert from "node:assert/strict";
|
|
import { readFileSync } from "node:fs";
|
|
import test from "node:test";
|
|
import {
|
|
createRectificationActivityReceiptState,
|
|
reduceRectificationActivityReceipt,
|
|
receiptFromRectificationActivityState,
|
|
} from "../src/lib/rectification-activity-receipt";
|
|
|
|
test("a successful retry clears the same tool's earlier failure", () => {
|
|
let state = createRectificationActivityReceiptState();
|
|
for (const event of [
|
|
{ tool: "rectification-read-case", status: "completed" },
|
|
{ tool: "rectification-propose-evidence", status: "failed" },
|
|
{ tool: "rectification-propose-evidence", status: "started" },
|
|
{ tool: "rectification-propose-evidence", status: "completed" },
|
|
{ tool: "rectification-confirm-evidence", status: "completed" },
|
|
] as const) {
|
|
state = reduceRectificationActivityReceipt(state, event);
|
|
}
|
|
|
|
assert.deepEqual(receiptFromRectificationActivityState(state), {
|
|
steps: [
|
|
"rectification-read-case",
|
|
"rectification-propose-evidence",
|
|
"rectification-confirm-evidence",
|
|
],
|
|
methods: [],
|
|
});
|
|
});
|
|
|
|
test("an unrecovered latest tool failure remains visible", () => {
|
|
let state = createRectificationActivityReceiptState();
|
|
state = reduceRectificationActivityReceipt(state, {
|
|
tool: "rectification-read-case",
|
|
status: "completed",
|
|
});
|
|
state = reduceRectificationActivityReceipt(state, {
|
|
tool: "rectification-propose-evidence",
|
|
status: "failed",
|
|
});
|
|
|
|
assert.deepEqual(receiptFromRectificationActivityState(state), {
|
|
steps: ["rectification-read-case"],
|
|
methods: [],
|
|
failedTool: "rectification-propose-evidence",
|
|
});
|
|
});
|
|
|
|
test("a later failure overrides an earlier completion for the same tool", () => {
|
|
let state = createRectificationActivityReceiptState();
|
|
state = reduceRectificationActivityReceipt(state, {
|
|
tool: "rectification-read-case",
|
|
status: "completed",
|
|
});
|
|
state = reduceRectificationActivityReceipt(state, {
|
|
tool: "rectification-read-case",
|
|
status: "failed",
|
|
});
|
|
|
|
assert.deepEqual(receiptFromRectificationActivityState(state), {
|
|
steps: [],
|
|
methods: [],
|
|
failedTool: "rectification-read-case",
|
|
});
|
|
});
|
|
|
|
test("a later failure removes methods contributed by the failed tool", () => {
|
|
let state = createRectificationActivityReceiptState();
|
|
state = reduceRectificationActivityReceipt(state, {
|
|
tool: "rectification-compare-candidates",
|
|
status: "completed",
|
|
methods: ["d1-rashi", "vimshottari-dasha"],
|
|
});
|
|
state = reduceRectificationActivityReceipt(state, {
|
|
tool: "rectification-read-diagnostics",
|
|
status: "completed",
|
|
methods: ["d1-rashi", "shadbala"],
|
|
});
|
|
state = reduceRectificationActivityReceipt(state, {
|
|
tool: "rectification-compare-candidates",
|
|
status: "failed",
|
|
});
|
|
|
|
assert.deepEqual(receiptFromRectificationActivityState(state), {
|
|
steps: ["rectification-read-diagnostics"],
|
|
methods: ["d1-rashi", "shadbala"],
|
|
failedTool: "rectification-compare-candidates",
|
|
});
|
|
});
|
|
|
|
const chatSource = readFileSync(
|
|
new URL("../src/components/rectification-agentic-chat.tsx", import.meta.url),
|
|
"utf8",
|
|
);
|
|
|
|
test("answer deltas preserve a still-running server activity", () => {
|
|
const deltaBranch = chatSource.slice(
|
|
chatSource.indexOf('event.type === "answer.delta"'),
|
|
chatSource.indexOf('event.type === "run.failed"'),
|
|
);
|
|
// Former locks: `state: raw.trim() ? "streaming" : "thinking"` and `text: raw,` inside the
|
|
// delta branch. Those pinned one setMessages per network chunk (BUG-473); the same state
|
|
// derivation now lives in the frame buffer's flush, which the delta branch feeds.
|
|
const frameFlush = chatSource.slice(
|
|
chatSource.indexOf("const frames = createStreamFrameBuffer"),
|
|
chatSource.indexOf("const abortController = new AbortController()"),
|
|
);
|
|
assert.match(frameFlush, /state: text\.trim\(\) \? "streaming" : "thinking"/);
|
|
assert.match(frameFlush, /^\s*text,$/m);
|
|
assert.match(deltaBranch, /frames\.setAnswer\(raw\)/);
|
|
assert.match(deltaBranch, /event\.replace === true \? event\.text : raw \+ event\.text/);
|
|
assert.doesNotMatch(deltaBranch, /settled\.spoken/);
|
|
assert.match(deltaBranch, /正在组织回答/);
|
|
assert.doesNotMatch(deltaBranch, /activeActivity:\s*undefined/);
|
|
});
|
|
|
|
test("failed server turns retain their receipt state without parsing activity from answer text", () => {
|
|
assert.match(chatSource, /failed:\s*true/);
|
|
assert.match(chatSource, /completedReceipt/);
|
|
// Former lock: `message.state === "settled" && message.failed`, the inline red banner above a
|
|
// failed reply. That banner was the second, rectification-only failure surface (BUG-476);
|
|
// failure now reaches the shared error notice while the receipt state still gates actions.
|
|
assert.match(chatSource, /&& !message\.failed/);
|
|
assert.doesNotMatch(chatSource, /rectification-activity-failure/);
|
|
assert.doesNotMatch(chatSource, /parsed\.(?:activity|receipt|tools|methods)/);
|
|
});
|
|
|
|
test("candidate acceptance copy never presents adoption as confirmation", () => {
|
|
assert.match(chatSource, /当前可能的出生时间/);
|
|
assert.match(chatSource, /采用不等于确认出生时间/);
|
|
assert.match(chatSource, /rectification-candidate-badge">已采用/);
|
|
assert.match(chatSource, /已确认校正时间/);
|
|
assert.doesNotMatch(chatSource, /本轮校正已收口|当前排盘时间(代表性时间|本会话以代表性时间收口/);
|
|
assert.doesNotMatch(chatSource, /candidateResult\.confirmationAllowed \? "确认校正时间"/);
|
|
});
|