From 2ea95822c58a5409d50ba8ed1d9ad4a01e132e42 Mon Sep 17 00:00:00 2001 From: _Kerman Date: Tue, 2 Jun 2026 15:43:59 +0800 Subject: [PATCH] feat: append TODO list as markdown to compaction summary --- .../append-todo-to-compaction-summary.md | 6 ++++ .../agent-core/src/agent/compaction/full.ts | 13 ++++++++ .../src/tools/builtin/state/todo-list.ts | 4 +-- .../test/agent/compaction/full.test.ts | 33 +++++++++++++++++++ 4 files changed, 54 insertions(+), 2 deletions(-) create mode 100644 .changeset/append-todo-to-compaction-summary.md diff --git a/.changeset/append-todo-to-compaction-summary.md b/.changeset/append-todo-to-compaction-summary.md new file mode 100644 index 0000000000..750c753777 --- /dev/null +++ b/.changeset/append-todo-to-compaction-summary.md @@ -0,0 +1,6 @@ +--- +"@moonshot-ai/agent-core": minor +"@moonshot-ai/kimi-code": minor +--- + +Append the current todo list as markdown to compaction summaries before writing them to history. diff --git a/packages/agent-core/src/agent/compaction/full.ts b/packages/agent-core/src/agent/compaction/full.ts index eac1b65b06..75cd1c3cb6 100644 --- a/packages/agent-core/src/agent/compaction/full.ts +++ b/packages/agent-core/src/agent/compaction/full.ts @@ -31,6 +31,7 @@ import { } from '../../utils/completion-budget'; import compactionInstructionTemplate from './compaction-instruction.md'; import { renderMessagesToText } from './render-messages'; +import { renderTodoList, type TodoItem } from '../../tools/builtin/state/todo-list'; import type { CompactionBeginData, CompactionResult } from './types'; import { DEFAULT_COMPACTION_CONFIG, @@ -309,6 +310,8 @@ export class FullCompaction { } } + summary = this.postProcessSummary(summary); + const recent = originalHistory.slice(compactedCount); const tokensAfter = estimateTokens(summary) + estimateTokensForMessages(recent); @@ -396,6 +399,16 @@ export class FullCompaction { }, }); } + + private postProcessSummary(summary: string): string { + const storeData = this.agent.tools.storeData(); + const todos = (storeData['todo'] as readonly TodoItem[] | undefined) ?? []; + if (todos.length === 0) { + return summary; + } + const todoMarkdown = renderTodoList(todos, '## TODO List'); + return `${summary.trim()}\n\n${todoMarkdown}`; + } } function extractCompactionSummary(response: GenerateResult): string { diff --git a/packages/agent-core/src/tools/builtin/state/todo-list.ts b/packages/agent-core/src/tools/builtin/state/todo-list.ts index 8787d96b9b..94b8692f7e 100644 --- a/packages/agent-core/src/tools/builtin/state/todo-list.ts +++ b/packages/agent-core/src/tools/builtin/state/todo-list.ts @@ -60,7 +60,7 @@ const TODO_STORE_KEY = 'todo'; // ── Implementation ─────────────────────────────────────────────────── -function renderTodoList(todos: readonly TodoItem[]): string { +export function renderTodoList(todos: readonly TodoItem[], title = 'Current todo list:'): string { if (todos.length === 0) { return 'Todo list is empty.'; } @@ -68,7 +68,7 @@ function renderTodoList(todos: readonly TodoItem[]): string { const marker = statusMarker(t.status); return ` ${marker} ${t.title}`; }); - return ['Current todo list:', ...lines].join('\n'); + return [title, ...lines].join('\n'); } function statusMarker(status: TodoStatus): string { diff --git a/packages/agent-core/test/agent/compaction/full.test.ts b/packages/agent-core/test/agent/compaction/full.test.ts index 703413cf72..60944ce041 100644 --- a/packages/agent-core/test/agent/compaction/full.test.ts +++ b/packages/agent-core/test/agent/compaction/full.test.ts @@ -1668,6 +1668,39 @@ describe('FullCompaction', () => { `); await ctx.expectResumeMatches(); }); + + it('appends the todo list to the compaction summary', async () => { + const ctx = testAgent(); + ctx.configure({ + provider: CATALOGUED_PROVIDER, + modelCapabilities: CATALOGUED_MODEL_CAPABILITIES, + }); + ctx.appendExchange(1, 'old user one', 'old assistant one', 20); + ctx.appendExchange(2, 'recent user two', 'recent assistant two', 80); + + ctx.agent.tools.updateStore('todo', [ + { title: 'Fix the auth bug', status: 'in_progress' }, + { title: 'Add tests', status: 'pending' }, + ]); + + const compacted = new Promise((resolve) => { + ctx.emitter.once('context.apply_compaction', () => { + resolve(); + }); + }); + + ctx.mockNextResponse({ type: 'text', text: 'Compacted summary.' }); + await ctx.rpc.beginCompaction({}); + await compacted; + + const history = ctx.compactHistory(); + expect(history).toHaveLength(1); + expect(history[0]).toMatchObject({ + role: 'assistant', + text: 'Compacted summary.\n\n## TODO List\n [in_progress] Fix the auth bug\n [pending] Add tests', + }); + await ctx.expectResumeMatches(); + }); }); afterEach(() => {