test(session-file-repair): add regression for trailing stop-assistant after tool-result

Adds a test covering the exact bug scenario described in the fix document:
user → assistant(toolCall) → toolResult → assistant(text, stopReason=stop).

The repair pass must leave this sequence untouched; previously, overly broad
trimming logic could drop the final text response, breaking multi-turn context
for the next user message. The test verifies repaired=false and byte-identity
of the session file.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Vinh Nguyen
2026-05-03 14:36:24 +07:00
parent 39bc94e4dd
commit ee3aa0e036

View File

@@ -465,6 +465,60 @@ describe("repairSessionFileIfNeeded", () => {
expect(after).toBe(original);
});
it("preserves final text assistant turn that follows a tool-call/tool-result pair", async () => {
// Regression: a trailing assistant message with stopReason "stop" that follows a
// tool-call turn and its matching tool-result must never be trimmed by the repair
// pass. This is the exact sequence produced by any agent run that calls at least
// one tool before returning a final text response, and it must survive intact so
// subsequent user messages are parented to the correct leaf node.
const { file } = await createTempSessionPath();
const { header, message } = buildSessionHeaderAndMessage();
const toolCallAssistant = {
type: "message",
id: "msg-asst-tc",
parentId: "msg-1",
timestamp: new Date().toISOString(),
message: {
role: "assistant",
content: [{ type: "toolCall", id: "call_1", name: "get_tasks", input: {} }],
stopReason: "toolUse",
},
};
const toolResult = {
type: "message",
id: "msg-tool-result",
parentId: "msg-asst-tc",
timestamp: new Date().toISOString(),
message: {
role: "toolResult",
toolCallId: "call_1",
toolName: "get_tasks",
content: [{ type: "text", text: "Task A, Task B" }],
isError: false,
},
};
const finalAssistant = {
type: "message",
id: "msg-asst-final",
parentId: "msg-tool-result",
timestamp: new Date().toISOString(),
message: {
role: "assistant",
content: [{ type: "text", text: "Here are your tasks: Task A, Task B." }],
stopReason: "stop",
},
};
const original = `${JSON.stringify(header)}\n${JSON.stringify(message)}\n${JSON.stringify(toolCallAssistant)}\n${JSON.stringify(toolResult)}\n${JSON.stringify(finalAssistant)}\n`;
await fs.writeFile(file, original, "utf-8");
const result = await repairSessionFileIfNeeded({ sessionFile: file });
expect(result.repaired).toBe(false);
const after = await fs.readFile(file, "utf-8");
expect(after).toBe(original);
});
it("preserves assistant-only session history after the header", async () => {
const { file } = await createTempSessionPath();
const { header } = buildSessionHeaderAndMessage();