fix(rectification): hydrate persisted case turns
This commit is contained in:
@@ -2372,6 +2372,7 @@ export default function Home() {
|
||||
status: String((turn.receipt as { status?: unknown }).status ?? ""),
|
||||
phases: Array.isArray((turn.receipt as { phases?: unknown }).phases) ? (turn.receipt as { phases: unknown[] }).phases.map(String) : [],
|
||||
tools: Array.isArray((turn.receipt as { tools?: unknown }).tools) ? (turn.receipt as { tools: unknown[] }).tools.map(String) : [],
|
||||
methods: Array.isArray((turn.receipt as { methods?: unknown }).methods) ? (turn.receipt as { methods: unknown[] }).methods.map(String) : [],
|
||||
skill_name: typeof (turn.receipt as { skill_name?: unknown }).skill_name === "string" ? (turn.receipt as { skill_name: string }).skill_name : undefined,
|
||||
skill_version: typeof (turn.receipt as { skill_version?: unknown }).skill_version === "string" ? (turn.receipt as { skill_version: string }).skill_version : undefined,
|
||||
} : null,
|
||||
@@ -3480,7 +3481,7 @@ export default function Home() {
|
||||
|
||||
{rectificationSurfaceOpen && rectificationCaseId && (
|
||||
<ConversationalBirthTimeRectification
|
||||
key={`${rectificationSessionId}-${rectificationCaseId}`}
|
||||
key={`${rectificationSessionId}-${rectificationCaseId}-${rectificationTurns.at(-1)?.id ?? "loading"}`}
|
||||
caseId={rectificationCaseId}
|
||||
sessionId={rectificationSessionId ?? ""}
|
||||
readonly={rectificationReadonly}
|
||||
|
||||
@@ -13,6 +13,7 @@ export type PersistedRectificationTurn = Readonly<{
|
||||
status: string;
|
||||
phases: readonly string[];
|
||||
tools: readonly string[];
|
||||
methods?: readonly string[];
|
||||
skill_name?: string;
|
||||
skill_version?: string;
|
||||
}> | null;
|
||||
|
||||
@@ -112,6 +112,28 @@ function activityFromReceipt(receipt: PersistedTurn["receipt"]): ReceiptActivity
|
||||
};
|
||||
}
|
||||
|
||||
function messagesFromTurns(initialTurns: readonly PersistedTurn[]): RenderMessage[] {
|
||||
return initialTurns.flatMap((turn, index): RenderMessage[] => {
|
||||
const key = `persisted-${turn.id}-${index}`;
|
||||
if (turn.role === "assistant") {
|
||||
return [{
|
||||
role: "assistant",
|
||||
text: turn.text ?? "",
|
||||
renderKey: key,
|
||||
state: turn.status === "completed" ? "settled" : "thinking",
|
||||
receiptActivity: activityFromReceipt(turn.receipt),
|
||||
receiptStatus: turn.receipt?.status,
|
||||
}];
|
||||
}
|
||||
return [{
|
||||
role: "user",
|
||||
text: turn.text ?? "",
|
||||
renderKey: key,
|
||||
state: "settled",
|
||||
}];
|
||||
});
|
||||
}
|
||||
|
||||
export function RectificationAgenticChat(props: RectificationAgenticChatProps) {
|
||||
const {
|
||||
caseId,
|
||||
@@ -131,27 +153,7 @@ export function RectificationAgenticChat(props: RectificationAgenticChatProps) {
|
||||
onRestart,
|
||||
} = props;
|
||||
|
||||
const [messages, setMessages] = useState<RenderMessage[]>(() =>
|
||||
initialTurns.flatMap((turn, index): RenderMessage[] => {
|
||||
const key = `persisted-${turn.id}-${index}`;
|
||||
if (turn.role === "assistant") {
|
||||
return [{
|
||||
role: "assistant",
|
||||
text: turn.text ?? "",
|
||||
renderKey: key,
|
||||
state: turn.status === "completed" ? "settled" : "thinking",
|
||||
receiptActivity: activityFromReceipt(turn.receipt),
|
||||
receiptStatus: turn.receipt?.status,
|
||||
}];
|
||||
}
|
||||
return [{
|
||||
role: "user",
|
||||
text: turn.text ?? "",
|
||||
renderKey: key,
|
||||
state: "settled",
|
||||
}];
|
||||
}),
|
||||
);
|
||||
const [messages, setMessages] = useState<RenderMessage[]>(() => messagesFromTurns(initialTurns));
|
||||
const [draft, setDraft] = useState("");
|
||||
const [busy, setBusy] = useState(false);
|
||||
const [error, setError] = useState("");
|
||||
|
||||
@@ -29,6 +29,14 @@ test("birth-time rectification entry mounts the V9 case-ref chat", () => {
|
||||
assert.match(component, /initialTurns:/);
|
||||
});
|
||||
|
||||
test("persisted rectification turns hydrate after the async Case refresh", () => {
|
||||
assert.match(chat, /function messagesFromTurns\(initialTurns:/);
|
||||
assert.match(chat, /useState<RenderMessage\[\]>\(\(\) => messagesFromTurns\(initialTurns\)\)/);
|
||||
assert.match(page, /key=\{`\$\{rectificationSessionId\}-\$\{rectificationCaseId\}-\$\{rectificationTurns\.at\(-1\)\?\.id \?\? "loading"\}`\}/);
|
||||
assert.match(page, /methods: Array\.isArray\(\(turn\.receipt as \{ methods\?: unknown \}\)\.methods\)/);
|
||||
assert.match(component, /methods\?: readonly string\[\]/);
|
||||
});
|
||||
|
||||
test("opening is server-owned: shouldStartOpening drives the first turn, never client history", () => {
|
||||
assert.doesNotMatch(chat, /initialMessages\.length > 0 \|\| openingStarted/);
|
||||
assert.doesNotMatch(chat, /agenticOpeningInstruction|用户刚进入生时校正会话/);
|
||||
@@ -100,7 +108,7 @@ test("agent tool calls never end silently; the runner owns completion and failur
|
||||
test("persisted turns survive remounts; duplicate openings are suppressed by the server", () => {
|
||||
assert.match(chat, /initialTurns/);
|
||||
assert.match(chat, /const openingStarted = useRef\(false\)/);
|
||||
assert.match(page, /key=\{`\$\{rectificationSessionId\}-\$\{rectificationCaseId\}`\}/);
|
||||
assert.match(page, /key=\{`\$\{rectificationSessionId\}-\$\{rectificationCaseId\}-\$\{rectificationTurns\.at\(-1\)\?\.id \?\? "loading"\}`\}/);
|
||||
assert.match(page, /initialTurns=\{rectificationTurns\}/);
|
||||
assert.match(page, /onMessagesChange=\{handleRectificationMessagesChange\}/);
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user