Merge f7a3bb0614de1b1d6b3c0bafdee2ebf4f8766ddb into 598f1826d8b2bc969aace2c6459824737667218c

This commit is contained in:
Julian 2026-03-20 21:26:32 -07:00 committed by GitHub
commit 39e07aad22
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 58 additions and 0 deletions

View File

@ -46,4 +46,54 @@ describe("hook-runner-global", () => {
expect(modC.getGlobalHookRunner()).toBeNull();
expect(modC.getGlobalPluginRegistry()).toBeNull();
});
it("does not replace an existing runner that has typed hooks", async () => {
const mod = await importHookRunnerGlobalModule();
const registryWithHooks = createMockPluginRegistry([
{ hookName: "llm_input", handler: vi.fn() },
]);
const emptyRegistry = createMockPluginRegistry([]);
mod.initializeGlobalHookRunner(registryWithHooks);
const originalRunner = mod.getGlobalHookRunner();
expect(originalRunner?.hasHooks("llm_input")).toBe(true);
// Second call with empty registry should be a no-op
mod.initializeGlobalHookRunner(emptyRegistry);
expect(mod.getGlobalHookRunner()).toBe(originalRunner);
expect(mod.getGlobalPluginRegistry()).toBe(registryWithHooks);
expect(mod.getGlobalHookRunner()?.hasHooks("llm_input")).toBe(true);
});
it("allows replacing a runner that has no typed hooks", async () => {
const mod = await importHookRunnerGlobalModule();
const emptyRegistry = createMockPluginRegistry([]);
const registryWithHooks = createMockPluginRegistry([
{ hookName: "llm_input", handler: vi.fn() },
]);
mod.initializeGlobalHookRunner(emptyRegistry);
expect(mod.getGlobalHookRunner()?.hasHooks("llm_input")).toBe(false);
// Second call with hooks should replace
mod.initializeGlobalHookRunner(registryWithHooks);
expect(mod.getGlobalHookRunner()?.hasHooks("llm_input")).toBe(true);
expect(mod.getGlobalPluginRegistry()).toBe(registryWithHooks);
});
it("allows re-initialization after reset", async () => {
const mod = await importHookRunnerGlobalModule();
const registryA = createMockPluginRegistry([{ hookName: "llm_input", handler: vi.fn() }]);
const registryB = createMockPluginRegistry([{ hookName: "llm_output", handler: vi.fn() }]);
mod.initializeGlobalHookRunner(registryA);
expect(mod.getGlobalHookRunner()?.hasHooks("llm_input")).toBe(true);
mod.resetGlobalHookRunner();
expect(mod.getGlobalHookRunner()).toBeNull();
mod.initializeGlobalHookRunner(registryB);
expect(mod.getGlobalHookRunner()?.hasHooks("llm_output")).toBe(true);
expect(mod.getGlobalHookRunner()?.hasHooks("llm_input")).toBe(false);
});
});

View File

@ -35,6 +35,14 @@ function getHookRunnerGlobalState(): HookRunnerGlobalState {
*/
export function initializeGlobalHookRunner(registry: PluginRegistry): void {
const state = getHookRunnerGlobalState();
// Preserve an existing hook runner that has registered hooks.
// Subsequent ensureRuntimePluginsLoaded calls (e.g. from non-default agent runs)
// may build a fresh registry with fewer/no hooks due to cache key divergence;
// replacing the working runner would silently drop all plugin hooks.
if (state.hookRunner && state.registry && state.registry.typedHooks.length > 0) {
log.debug("hook runner already initialized with hooks; skipping re-initialization");
return;
}
state.registry = registry;
state.hookRunner = createHookRunner(registry, {
logger: {