Refactor SessionEventBase to pass sessionManager and modelRegistry

Breaking changes to hook types:
- SessionEventBase now passes sessionManager and modelRegistry directly
- before_compact: passes preparation, previousCompactions (newest first)
- before_switch: has targetSessionFile; switch: has previousSessionFile
- Removed resolveApiKey (use modelRegistry.getApiKey())
- getSessionFile() returns string | undefined for in-memory sessions

Updated:
- All session event emissions in agent-session.ts
- Hook examples (custom-compaction.ts, auto-commit-on-exit.ts, confirm-destructive.ts)
- Tests (compaction-hooks.test.ts, compaction-hooks-example.test.ts)
- export-html.ts guards for in-memory sessions
This commit is contained in:
Mario Zechner 2025-12-26 22:22:43 +01:00
parent d96375b5e5
commit 9bba388ec5
14 changed files with 145 additions and 177 deletions

View file

@ -20,9 +20,10 @@ export default function (pi: HookAPI) {
}
// Find the last assistant message for commit context
const entries = event.sessionManager.getEntries();
let lastAssistantText = "";
for (let i = event.entries.length - 1; i >= 0; i--) {
const entry = event.entries[i];
for (let i = entries.length - 1; i >= 0; i--) {
const entry = entries[i];
if (entry.type === "message" && entry.message.role === "assistant") {
const content = entry.message.content;
if (Array.isArray(content)) {

View file

@ -5,6 +5,7 @@
* Demonstrates how to cancel session events using the before_* variants.
*/
import type { SessionMessageEntry } from "@mariozechner/pi-coding-agent";
import type { HookAPI } from "@mariozechner/pi-coding-agent/hooks";
export default function (pi: HookAPI) {
@ -28,7 +29,10 @@ export default function (pi: HookAPI) {
if (!ctx.hasUI) return;
// Check if there are unsaved changes (messages since last assistant response)
const hasUnsavedWork = event.entries.some((e) => e.type === "message" && e.message.role === "user");
const entries = event.sessionManager.getEntries();
const hasUnsavedWork = entries.some(
(e): e is SessionMessageEntry => e.type === "message" && e.message.role === "user",
);
if (hasUnsavedWork) {
const confirmed = await ctx.ui.confirm(

View file

@ -23,15 +23,11 @@ export default function (pi: HookAPI) {
ctx.ui.notify("Custom compaction hook triggered", "info");
const {
messagesToSummarize,
messagesToKeep,
previousSummary,
tokensBefore,
resolveApiKey,
entries: _,
signal,
} = event;
const { preparation, previousCompactions, modelRegistry, signal } = event;
const { messagesToSummarize, messagesToKeep, tokensBefore, firstKeptEntryId } = preparation;
// Get previous summary from most recent compaction (if any)
const previousSummary = previousCompactions[0]?.summary;
// Use Gemini Flash for summarization (cheaper/faster than most conversation models)
const model = getModel("google", "gemini-2.5-flash");
@ -41,7 +37,7 @@ export default function (pi: HookAPI) {
}
// Resolve API key for the summarization model
const apiKey = await resolveApiKey(model);
const apiKey = await modelRegistry.getApiKey(model);
if (!apiKey) {
ctx.ui.notify(`No API key for ${model.provider}, using default compaction`, "warning");
return;
@ -102,11 +98,11 @@ Format the summary as structured markdown with clear sections.`,
}
// Return compaction content - SessionManager adds id/parentId
// Use firstKeptEntryId from event to keep recent messages
// Use firstKeptEntryId from preparation to keep recent messages
return {
compaction: {
summary,
firstKeptEntryId: event.firstKeptEntryId,
firstKeptEntryId,
tokensBefore,
},
};