import type { HookEvent, HookMatcher } from './types'; /** * Run-scoped storage for hook matchers with an additional layer for * session-scoped matchers that should be cleaned up between sessions. * * Hosts construct one registry per `Run` (mirroring how `HandlerRegistry` is * scoped) and register global matchers + per-session matchers against it. * Registration is strictly additive — nothing in this class mutates a * matcher's callbacks or flags after insertion. * * ## Why `Map` and not `Record` * * LibreChat runs thousands of parallel sessions in one Node process, and * hook registration happens inside hot paths (tool loading, agent spawning). * A `Record` has to be spread on every insertion, which is * O(n) per call and O(n²) total for a batch of parallel registrations. A * Map mutates in place, keeping insertions O(1). This mirrors the reasoning * Claude Code documents at `utils/hooks/sessionHooks.ts:62`. */ export declare class HookRegistry { private readonly global; private readonly sessions; /** * Register a matcher for the lifetime of this registry (= one Run). * Returns an unregister function that removes the matcher by reference. */ register(event: E, matcher: HookMatcher): () => void; /** * Register a matcher for a specific session. Cleared automatically when * `clearSession(sessionId)` is called, or can be removed directly via the * returned unregister function. */ registerSession(sessionId: string, event: E, matcher: HookMatcher): () => void; /** * Returns all matchers registered for `event`, concatenating global first * and then session-specific (when `sessionId` is supplied). The caller * receives a fresh array, so iterating it is safe even if a matcher is * removed mid-iteration (e.g. via `once: true`). */ getMatchers(event: E, sessionId?: string): HookMatcher[]; /** * Removes `matcher` by reference from global storage first, falling back * to the session bucket when `sessionId` is supplied. Used by * `executeHooks` to drop `once: true` matchers after they fire. */ removeMatcher(event: E, matcher: HookMatcher, sessionId?: string): boolean; /** * Drops every session-scoped matcher for `sessionId`. Call this in the * `finally` block around a Run so a `once: true` hook that never fired * cannot leak into the next session on the same registry. */ clearSession(sessionId: string): void; /** True if at least one matcher exists for `event` (global + session). */ hasHookFor(event: HookEvent, sessionId?: string): boolean; private ensureSessionBucket; }