feat(coding-agent): context compaction with /compact, /autocompact, and auto-trigger

- Add /compact command for manual context compaction with optional custom instructions
- Add /autocompact command to toggle automatic compaction
- Auto-trigger compaction when context usage exceeds threshold (contextWindow - reserveTokens)
- Add CompactionComponent for TUI display with collapsed/expanded states
- Add compaction events to HTML export with collapsible summary
- Refactor export-html.ts to eliminate duplication between session and streaming formats
- Use setTimeout to break out of agent event handler for safe async compaction
- Show compaction summary in TUI after compaction completes

fixes #92
This commit is contained in:
Mario Zechner 2025-12-04 02:39:54 +01:00
parent bddb99fa7c
commit c89b1ec3c2
6 changed files with 803 additions and 1473 deletions

View file

@ -72,17 +72,21 @@ export interface LoadedSession {
model: { provider: string; modelId: string } | null;
}
const SUMMARY_PREFIX = `Another language model worked on this task and produced a summary. Use this to continue the work without duplicating effort:
export const SUMMARY_PREFIX = `The conversation history before this point was compacted into the following summary:
<summary>
`;
export const SUMMARY_SUFFIX = `
</summary>`;
/**
* Create a user message containing the summary with the standard prefix.
*/
export function createSummaryMessage(summary: string): AppMessage {
return {
role: "user",
content: SUMMARY_PREFIX + summary,
content: SUMMARY_PREFIX + summary + SUMMARY_SUFFIX,
timestamp: Date.now(),
};
}
@ -115,6 +119,18 @@ export function parseSessionEntries(content: string): SessionEntry[] {
* 2. Keep all entries from firstKeptEntryIndex onwards (extracting messages)
* 3. Prepend summary as user message
*/
/**
* Get the latest compaction entry from session entries, if any.
*/
export function getLatestCompactionEntry(entries: SessionEntry[]): CompactionEntry | null {
for (let i = entries.length - 1; i >= 0; i--) {
if (entries[i].type === "compaction") {
return entries[i] as CompactionEntry;
}
}
return null;
}
export function loadSessionFromEntries(entries: SessionEntry[]): LoadedSession {
// Find model and thinking level (always scan all entries)
let thinkingLevel = "off";