T2 of TASK-rectification-grounding-20260927 (product decision P1). The rectification 「重试(重新生成)」 was a blind agent.generate rewrite that persisted unchecked text (invented range / fit rate reproduced). Removed: the regenerate route, regenerate-turn.ts, the regeneration agent and its read-only tool set, the client regenerate action/state and the regenerating/canRegenerate props. ChatMessageActions renders the regenerate button only when onRegenerate is passed; ordinary consultation is unchanged. The DB function regenerate_agentic_rectification_turn is kept (AGENTS §7.6, retire in a later round). DESIGN.md / VOICE.md updated; source-contract tests follow with 原值/新值/原因 notes. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017eEAG8HD3mm8gsKXgk8uU8
88 lines
3.8 KiB
TypeScript
88 lines
3.8 KiB
TypeScript
/**
|
|
* BUG-1056 (TASK-rectification-grounding-20260927 T2): the rectification
|
|
* 「重试(重新生成)」 is gone. The rewrite was a blind `agent.generate` with no
|
|
* question stripping, range sentence, number or word checks, persisted as is.
|
|
* Ordinary consultation keeps its regenerate button. The database function
|
|
* `regenerate_agentic_rectification_turn` is deliberately left in place
|
|
* (AGENTS §7.6: code first, retire the function in a later round).
|
|
*/
|
|
import assert from "node:assert/strict";
|
|
import { existsSync, readFileSync, readdirSync, statSync } from "node:fs";
|
|
import { join } from "node:path";
|
|
import { fileURLToPath } from "node:url";
|
|
import { createElement, createRef } from "react";
|
|
import { renderToString } from "react-dom/server";
|
|
import test from "node:test";
|
|
|
|
import { ChatMessageActions } from "../src/components/chat-message-actions.tsx";
|
|
import {
|
|
RectificationMessageEntry,
|
|
type RectificationMessageActions,
|
|
type RectificationMessageEntryProps,
|
|
} from "../src/components/rectification-message-entry.tsx";
|
|
|
|
const frontendRoot = fileURLToPath(new URL("../", import.meta.url));
|
|
|
|
function sourceFiles(directory: string): string[] {
|
|
const files: string[] = [];
|
|
for (const name of readdirSync(directory)) {
|
|
const path = join(directory, name);
|
|
if (statSync(path).isDirectory()) files.push(...sourceFiles(path));
|
|
else if (/\.(ts|tsx)$/.test(path)) files.push(path);
|
|
}
|
|
return files;
|
|
}
|
|
|
|
function actionsRef(): RectificationMessageEntryProps["actionsRef"] {
|
|
const ref = createRef<RectificationMessageActions>() as RectificationMessageEntryProps["actionsRef"];
|
|
ref.current = { submitChoice() {}, submitStop() {}, copyMessage() {}, onFeedback() {} };
|
|
return ref;
|
|
}
|
|
|
|
test("a settled rectification reply shows 赞 / 踩 / 复制 and no 重新生成", () => {
|
|
const html = renderToString(createElement(RectificationMessageEntry, {
|
|
message: { role: "assistant", text: "记下了:2016 年 9 月入学。", renderKey: "a1", state: "settled", turnId: "t1" },
|
|
busy: false,
|
|
readonly: false,
|
|
currentQuestionFocusId: null,
|
|
interactive: true,
|
|
liveChoiceCard: null,
|
|
choiceNonce: 0,
|
|
savedTime: null,
|
|
copied: false,
|
|
feedback: undefined,
|
|
actionsRef: actionsRef(),
|
|
}));
|
|
assert.match(html, /aria-label="赞"/);
|
|
assert.match(html, /aria-label="复制回答"/);
|
|
assert.doesNotMatch(html, /重新生成/);
|
|
});
|
|
|
|
test("the ordinary consultation action bar still offers 重新生成回答", () => {
|
|
const html = renderToString(createElement(ChatMessageActions, {
|
|
canRegenerate: true,
|
|
onFeedback() {},
|
|
onCopy() {},
|
|
onRegenerate() {},
|
|
}));
|
|
assert.match(html, /aria-label="重新生成回答"/);
|
|
const transcript = readFileSync(new URL("../src/components/chat-transcript.tsx", import.meta.url), "utf8");
|
|
assert.match(transcript, /onRegenerate=\{\(\) => actionsRef\.current\.onRegenerate\(message\.renderKey\)\}/);
|
|
});
|
|
|
|
test("the rectification regenerate endpoint and its server code are removed; the DB function is kept", () => {
|
|
assert.equal(existsSync(join(frontendRoot, "src/app/api/rectification/cases/[caseId]/turns")), false);
|
|
assert.equal(existsSync(join(frontendRoot, "src/lib/rectification-agentic/v9/regenerate-turn.ts")), false);
|
|
for (const file of sourceFiles(join(frontendRoot, "src"))) {
|
|
const text = readFileSync(file, "utf8");
|
|
assert.doesNotMatch(text, /regenerate_agentic_rectification_turn|get_agentic_rectification_turn_regeneration/, file);
|
|
assert.doesNotMatch(text, /\/api\/rectification\/cases\/[^"`']*\/regenerate/, file);
|
|
assert.doesNotMatch(text, /getRectificationV9RegenerationAgent|regenerateV9AssistantTurn/, file);
|
|
}
|
|
const migration = readFileSync(
|
|
join(frontendRoot, "supabase/migrations/20260813060000_rectification_turn_regeneration.sql"),
|
|
"utf8",
|
|
);
|
|
assert.match(migration, /function public\.regenerate_agentic_rectification_turn/i);
|
|
});
|