Split HookContext and HookCommandContext to prevent deadlocks

HookContext (all events):
- isIdle() - read-only state check
- hasQueuedMessages() - read-only state check
- abort() - fire-and-forget, does not wait

HookCommandContext (slash commands only):
- waitForIdle() - waits for agent to finish
- newSession(options?) - create new session
- branch(entryId) - branch from entry
- navigateTree(targetId, options?) - navigate session tree

Session control methods moved from HookAPI (pi.*) to HookCommandContext (ctx.*)
because they can deadlock when called from event handlers that run inside
the agent loop (tool_call, tool_result, context events).
This commit is contained in:
Mario Zechner 2026-01-02 00:24:58 +01:00
parent ccdd7bd283
commit 0d9fddec1e
9 changed files with 170 additions and 203 deletions

View file

@ -80,9 +80,6 @@ describe.skipIf(!API_KEY)("Compaction hooks", () => {
commands: new Map(),
setSendMessageHandler: () => {},
setAppendEntryHandler: () => {},
setNewSessionHandler: () => {},
setBranchHandler: () => {},
setNavigateTreeHandler: () => {},
};
}
@ -269,9 +266,6 @@ describe.skipIf(!API_KEY)("Compaction hooks", () => {
commands: new Map(),
setSendMessageHandler: () => {},
setAppendEntryHandler: () => {},
setNewSessionHandler: () => {},
setBranchHandler: () => {},
setNavigateTreeHandler: () => {},
};
createSession([throwingHook]);
@ -319,9 +313,6 @@ describe.skipIf(!API_KEY)("Compaction hooks", () => {
commands: new Map(),
setSendMessageHandler: () => {},
setAppendEntryHandler: () => {},
setNewSessionHandler: () => {},
setBranchHandler: () => {},
setNavigateTreeHandler: () => {},
};
const hook2: LoadedHook = {
@ -351,9 +342,6 @@ describe.skipIf(!API_KEY)("Compaction hooks", () => {
commands: new Map(),
setSendMessageHandler: () => {},
setAppendEntryHandler: () => {},
setNewSessionHandler: () => {},
setBranchHandler: () => {},
setNavigateTreeHandler: () => {},
};
createSession([hook1, hook2]);