fix(agents): preserve compactionSummary in limitHistoryTurns (fixes #97590) (AI-assisted)

This commit is contained in:
liuhao1024
2026-06-29 09:51:19 +08:00
committed by GitHub
parent 4a4657a182
commit b1ef12055e
2 changed files with 69 additions and 5 deletions

View File

@@ -118,6 +118,50 @@ describe("limitHistoryTurns", () => {
expect(limited[1].role).toBe("assistant");
});
it("preserves leading compactionSummary when limiting", () => {
const compactionSummary: AgentMessage = {
role: "compactionSummary",
summary: "Previous conversation about topic X",
tokensBefore: 5000,
tokensAfter: 2000,
timestamp: Date.now(),
} as AgentMessage;
const messages = [
compactionSummary,
...makeMessages(["user", "assistant", "user", "assistant"]),
];
const limited = limitHistoryTurns(messages, 1);
// compactionSummary is preserved, last 1 user turn + assistant kept
expect(limited.length).toBe(3);
expect(limited[0].role).toBe("compactionSummary");
expect(firstText(limited[1])).toBe("message 2");
});
it("preserves leading branchSummary when limiting", () => {
const branchSummary: AgentMessage = {
role: "branchSummary",
summary: "Branch context",
fromId: "abc",
timestamp: Date.now(),
} as AgentMessage;
const messages = [branchSummary, ...makeMessages(["user", "assistant", "user", "assistant"])];
const limited = limitHistoryTurns(messages, 1);
expect(limited.length).toBe(3);
expect(limited[0].role).toBe("branchSummary");
});
it("returns all when only non-conversation messages exist", () => {
const compactionSummary: AgentMessage = {
role: "compactionSummary",
summary: "Summary only",
tokensBefore: 1000,
timestamp: Date.now(),
} as AgentMessage;
const limited = limitHistoryTurns([compactionSummary], 2);
expect(limited).toHaveLength(1);
expect(limited[0].role).toBe("compactionSummary");
});
it("preserves message content integrity", () => {
// Limiting should slice whole turns, not mutate tool calls or message bodies.
const messages: AgentMessage[] = [

View File

@@ -16,6 +16,10 @@ function stripThreadSuffix(value: string): string {
/**
* Limits conversation history to the last N user turns (and their associated
* assistant responses). This reduces token usage for long-running DM sessions.
*
* Leading non-conversation messages (e.g. compactionSummary, branchSummary)
* placed at index 0 by buildSessionContext are always preserved, since they
* carry summarized pre-compaction context that history limiting must not drop.
*/
export function limitHistoryTurns(
messages: AgentMessage[],
@@ -25,14 +29,30 @@ export function limitHistoryTurns(
return messages;
}
let userCount = 0;
let lastUserIndex = messages.length;
// Preserve leading non-conversation messages (compactionSummary, branchSummary, etc.)
// that buildSessionContext places at index 0 to carry pre-compaction context.
let conversationStart = 0;
while (conversationStart < messages.length) {
const role = messages[conversationStart].role;
if (role === "user" || role === "assistant") {
break;
}
conversationStart++;
}
for (let i = messages.length - 1; i >= 0; i--) {
if (messages[i].role === "user") {
const tail = messages.slice(conversationStart);
if (tail.length === 0) {
return messages;
}
let userCount = 0;
let lastUserIndex = tail.length;
for (let i = tail.length - 1; i >= 0; i--) {
if (tail[i].role === "user") {
userCount++;
if (userCount > limit) {
return messages.slice(lastUserIndex);
return [...messages.slice(0, conversationStart), ...tail.slice(lastUserIndex)];
}
lastUserIndex = i;
}