refactor(coding-agent): fix compaction for branched sessions, consolidate hook context types

Compaction API:
- prepareCompaction() now takes (pathEntries, settings) only
- CompactionPreparation restructured: removed cutPoint/messagesToKeep/boundaryStart, added turnPrefixMessages/isSplitTurn/previousSummary/fileOps/settings
- compact() now takes (preparation, model, apiKey, customInstructions?, signal?)
- Fixed token overflow by using getPath() instead of getEntries()

Hook types:
- HookEventContext renamed to HookContext
- HookCommandContext removed, RegisteredCommand.handler takes (args, ctx)
- HookContext now includes model field
- SessionBeforeCompactEvent: removed previousCompactions/model, added branchEntries
- SessionBeforeTreeEvent: removed model (use ctx.model)
- HookRunner.initialize() added for modes to set up callbacks
This commit is contained in:
Mario Zechner 2025-12-31 02:24:24 +01:00
parent b4ce93c577
commit ddda8b124c
12 changed files with 177 additions and 201 deletions

View file

@ -3,7 +3,7 @@
*
* Replaces the default compaction behavior with a full summary of the entire context.
* Instead of keeping the last 20k tokens of conversation turns, this hook:
* 1. Summarizes ALL messages (both messagesToSummarize and messagesToKeep and previousSummary)
* 1. Summarizes ALL messages (messagesToSummarize + turnPrefixMessages)
* 2. Discards all old turns completely, keeping only the summary
*
* This example also demonstrates using a different model (Gemini Flash) for summarization,
@ -14,6 +14,7 @@
*/
import { complete, getModel } from "@mariozechner/pi-ai";
import type { CompactionEntry } from "@mariozechner/pi-coding-agent";
import { convertToLlm } from "@mariozechner/pi-coding-agent";
import type { HookAPI } from "@mariozechner/pi-coding-agent/hooks";
@ -21,11 +22,8 @@ export default function (pi: HookAPI) {
pi.on("session_before_compact", async (event, ctx) => {
ctx.ui.notify("Custom compaction hook triggered", "info");
const { preparation, previousCompactions, signal } = event;
const { messagesToSummarize, messagesToKeep, tokensBefore, firstKeptEntryId } = preparation;
// Get previous summary from most recent compaction (if any)
const previousSummary = previousCompactions[0]?.summary;
const { preparation, branchEntries, signal } = event;
const { messagesToSummarize, turnPrefixMessages, tokensBefore, firstKeptEntryId, previousSummary } = preparation;
// Use Gemini Flash for summarization (cheaper/faster than most conversation models)
const model = getModel("google", "gemini-2.5-flash");
@ -42,7 +40,7 @@ export default function (pi: HookAPI) {
}
// Combine all messages for full summary
const allMessages = [...messagesToSummarize, ...messagesToKeep];
const allMessages = [...messagesToSummarize, ...turnPrefixMessages];
ctx.ui.notify(
`Custom compaction: summarizing ${allMessages.length} messages (${tokensBefore.toLocaleString()} tokens) with ${model.id}...`,

View file

@ -5,13 +5,9 @@
* Useful to ensure work is committed before switching context.
*/
import type { HookAPI, HookEventContext } from "@mariozechner/pi-coding-agent/hooks";
import type { HookAPI, HookContext } from "@mariozechner/pi-coding-agent/hooks";
async function checkDirtyRepo(
pi: HookAPI,
ctx: HookEventContext,
action: string,
): Promise<{ cancel: boolean } | undefined> {
async function checkDirtyRepo(pi: HookAPI, ctx: HookContext, action: string): Promise<{ cancel: boolean } | undefined> {
// Check for uncommitted changes
const { stdout, code } = await pi.exec("git", ["status", "--porcelain"]);

View file

@ -310,7 +310,7 @@ export default function (pi: HookAPI) {
pi.registerCommand("snake", {
description: "Play Snake!",
handler: async (ctx) => {
handler: async (_args, ctx) => {
if (!ctx.hasUI) {
ctx.ui.notify("Snake requires interactive mode", "error");
return;