fix(coding-agent): preserve kept messages across repeated compaction closes #2608
This commit is contained in:
@@ -9,6 +9,7 @@ import {
|
||||
calculateContextTokens,
|
||||
compact,
|
||||
DEFAULT_COMPACTION_SETTINGS,
|
||||
estimateContextTokens,
|
||||
findCutPoint,
|
||||
getLastAssistantUsage,
|
||||
prepareCompaction,
|
||||
@@ -133,6 +134,42 @@ function createThinkingLevelEntry(thinkingLevel: string): ThinkingLevelChangeEnt
|
||||
return entry;
|
||||
}
|
||||
|
||||
function extractText(messages: AgentMessage[]): string {
|
||||
return messages
|
||||
.map((message) => {
|
||||
switch (message.role) {
|
||||
case "user":
|
||||
return typeof message.content === "string"
|
||||
? message.content
|
||||
: message.content
|
||||
.filter((block): block is { type: "text"; text: string } => block.type === "text")
|
||||
.map((block) => block.text)
|
||||
.join(" ");
|
||||
case "assistant":
|
||||
return message.content
|
||||
.filter((block): block is { type: "text"; text: string } => block.type === "text")
|
||||
.map((block) => block.text)
|
||||
.join(" ");
|
||||
case "branchSummary":
|
||||
case "compactionSummary":
|
||||
return message.summary;
|
||||
case "custom":
|
||||
case "toolResult":
|
||||
return typeof message.content === "string"
|
||||
? message.content
|
||||
: message.content
|
||||
.filter((block): block is { type: "text"; text: string } => block.type === "text")
|
||||
.map((block) => block.text)
|
||||
.join(" ");
|
||||
case "bashExecution":
|
||||
return `${message.command}\n${message.output}`;
|
||||
default:
|
||||
return "";
|
||||
}
|
||||
})
|
||||
.join("\n");
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Unit tests
|
||||
// ============================================================================
|
||||
@@ -358,6 +395,70 @@ describe("buildSessionContext", () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe("prepareCompaction with previous compaction", () => {
|
||||
it("should preserve kept messages across repeated compactions when they still fit", () => {
|
||||
const u1 = createMessageEntry(createUserMessage("user msg 1 (summarized by compaction1)"));
|
||||
const a1 = createMessageEntry(createAssistantMessage("assistant msg 1"));
|
||||
const u2 = createMessageEntry(createUserMessage("user msg 2 - kept by compaction1"));
|
||||
const a2 = createMessageEntry(createAssistantMessage("assistant msg 2"));
|
||||
const u3 = createMessageEntry(createUserMessage("user msg 3 - kept by compaction1"));
|
||||
const a3 = createMessageEntry(createAssistantMessage("assistant msg 3", createMockUsage(5000, 1000)));
|
||||
const compaction1 = createCompactionEntry("First summary", u2.id);
|
||||
const u4 = createMessageEntry(createUserMessage("user msg 4 (new after compaction1)"));
|
||||
const a4 = createMessageEntry(createAssistantMessage("assistant msg 4", createMockUsage(8000, 2000)));
|
||||
|
||||
const pathEntries = [u1, a1, u2, a2, u3, a3, compaction1, u4, a4];
|
||||
const contextBefore = buildSessionContext(pathEntries);
|
||||
const preparation = prepareCompaction(pathEntries, DEFAULT_COMPACTION_SETTINGS);
|
||||
|
||||
expect(preparation).toBeDefined();
|
||||
expect(preparation!.firstKeptEntryId).toBe(u2.id);
|
||||
expect(preparation!.previousSummary).toBe("First summary");
|
||||
expect(extractText(preparation!.messagesToSummarize)).not.toContain("First summary");
|
||||
expect(preparation!.tokensBefore).toBe(estimateContextTokens(contextBefore.messages).tokens);
|
||||
|
||||
const compaction2: CompactionEntry = {
|
||||
type: "compaction",
|
||||
id: "compaction2-id",
|
||||
parentId: a4.id,
|
||||
timestamp: new Date().toISOString(),
|
||||
summary: "Second summary",
|
||||
firstKeptEntryId: preparation!.firstKeptEntryId,
|
||||
tokensBefore: preparation!.tokensBefore,
|
||||
};
|
||||
const contextAfter = buildSessionContext([...pathEntries, compaction2]);
|
||||
const contextAfterText = extractText(contextAfter.messages);
|
||||
|
||||
expect(contextAfterText).toContain("user msg 2 - kept by compaction1");
|
||||
expect(contextAfterText).toContain("user msg 3 - kept by compaction1");
|
||||
});
|
||||
|
||||
it("should re-summarize previously kept messages when the recent window moves past them", () => {
|
||||
const u1 = createMessageEntry(createUserMessage("user msg 1 (summarized by compaction1)".repeat(4)));
|
||||
const a1 = createMessageEntry(createAssistantMessage("assistant msg 1".repeat(4)));
|
||||
const u2 = createMessageEntry(createUserMessage("user msg 2 - kept by compaction1 ".repeat(12)));
|
||||
const a2 = createMessageEntry(createAssistantMessage("assistant msg 2 ".repeat(12)));
|
||||
const u3 = createMessageEntry(createUserMessage("user msg 3 - kept by compaction1 ".repeat(12)));
|
||||
const a3 = createMessageEntry(createAssistantMessage("assistant msg 3 ".repeat(12), createMockUsage(5000, 1000)));
|
||||
const compaction1 = createCompactionEntry("First summary", u2.id);
|
||||
const u4 = createMessageEntry(createUserMessage("user msg 4 (new after compaction1) ".repeat(12)));
|
||||
const a4 = createMessageEntry(createAssistantMessage("assistant msg 4 ".repeat(12), createMockUsage(8000, 2000)));
|
||||
|
||||
const settings: CompactionSettings = {
|
||||
...DEFAULT_COMPACTION_SETTINGS,
|
||||
keepRecentTokens: 100,
|
||||
};
|
||||
const preparation = prepareCompaction([u1, a1, u2, a2, u3, a3, compaction1, u4, a4], settings);
|
||||
|
||||
expect(preparation).toBeDefined();
|
||||
const summarizedText = extractText(preparation!.messagesToSummarize);
|
||||
expect(summarizedText).toContain("user msg 2 - kept by compaction1");
|
||||
expect(summarizedText).toContain("user msg 3 - kept by compaction1");
|
||||
expect(summarizedText).not.toContain("First summary");
|
||||
expect(preparation!.previousSummary).toBe("First summary");
|
||||
});
|
||||
});
|
||||
|
||||
// ============================================================================
|
||||
// Integration tests with real session data
|
||||
// ============================================================================
|
||||
|
||||
Reference in New Issue
Block a user