Skip to content

Commit ee2530d

Browse files
SidQin-cybercursoragent
andcommitted
fix(hooks): preserve plugin-registered hooks across gateway startup reload
Plugins register internal hooks during loadGatewayPlugins(), but startGatewaySidecars() later calls clearInternalHooks() to reload file-based hook definitions, which wiped plugin handlers. Hooks registered via api.registerHook() appeared in `openclaw hooks list` but never fired. Introduce registerPluginHook() that tracks handlers separately. clearInternalHooks() now restores plugin handlers after clearing, so they survive the gateway startup reload cycle. Closes #25859 Co-authored-by: Cursor <[email protected]>
1 parent fb76e31 commit ee2530d

File tree

3 files changed

+48
-3
lines changed

3 files changed

+48
-3
lines changed

src/hooks/internal-hooks.test.ts

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import {
88
isMessageReceivedEvent,
99
isMessageSentEvent,
1010
registerInternalHook,
11+
registerPluginHook,
1112
triggerInternalHook,
1213
unregisterInternalHook,
1314
type AgentBootstrapHookContext,
@@ -454,5 +455,28 @@ describe("hooks", () => {
454455
const keys = getRegisteredEventKeys();
455456
expect(keys).toEqual([]);
456457
});
458+
459+
it("should preserve plugin-registered handlers", async () => {
460+
const pluginHandler = vi.fn();
461+
const configHandler = vi.fn();
462+
463+
registerPluginHook("message:received", pluginHandler);
464+
registerInternalHook("message:received", configHandler);
465+
466+
clearInternalHooks();
467+
468+
const keys = getRegisteredEventKeys();
469+
expect(keys).toContain("message:received");
470+
471+
const event = createInternalHookEvent("message", "received", "test-session", {
472+
from: "user1",
473+
channelId: "telegram",
474+
content: "hello",
475+
});
476+
await triggerInternalHook(event);
477+
478+
expect(pluginHandler).toHaveBeenCalledTimes(1);
479+
expect(configHandler).not.toHaveBeenCalled();
480+
});
457481
});
458482
});

src/hooks/internal-hooks.ts

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,8 @@ export type InternalHookHandler = (event: InternalHookEvent) => Promise<void> |
112112

113113
/** Registry of hook handlers by event key */
114114
const handlers = new Map<string, InternalHookHandler[]>();
115+
/** Handlers registered by plugins survive clearInternalHooks() */
116+
const pluginHandlers = new Map<string, InternalHookHandler[]>();
115117
const log = createSubsystemLogger("internal-hooks");
116118

117119
/**
@@ -140,6 +142,18 @@ export function registerInternalHook(eventKey: string, handler: InternalHookHand
140142
handlers.get(eventKey)!.push(handler);
141143
}
142144

145+
/**
146+
* Register a hook handler from a plugin. Plugin-registered handlers are
147+
* preserved when clearInternalHooks() is called during gateway startup.
148+
*/
149+
export function registerPluginHook(eventKey: string, handler: InternalHookHandler): void {
150+
registerInternalHook(eventKey, handler);
151+
if (!pluginHandlers.has(eventKey)) {
152+
pluginHandlers.set(eventKey, []);
153+
}
154+
pluginHandlers.get(eventKey)!.push(handler);
155+
}
156+
143157
/**
144158
* Unregister a specific hook handler
145159
*
@@ -164,10 +178,17 @@ export function unregisterInternalHook(eventKey: string, handler: InternalHookHa
164178
}
165179

166180
/**
167-
* Clear all registered hooks (useful for testing)
181+
* Clear config-loaded hooks while preserving plugin-registered handlers.
182+
* Called during gateway startup to ensure fresh loading from disk-based
183+
* hook definitions without losing hooks registered by plugins.
168184
*/
169185
export function clearInternalHooks(): void {
170186
handlers.clear();
187+
for (const [eventKey, list] of pluginHandlers) {
188+
if (list.length > 0) {
189+
handlers.set(eventKey, [...list]);
190+
}
191+
}
171192
}
172193

173194
/**

src/plugins/registry.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import type {
66
GatewayRequestHandler,
77
GatewayRequestHandlers,
88
} from "../gateway/server-methods/types.js";
9-
import { registerInternalHook } from "../hooks/internal-hooks.js";
9+
import { registerPluginHook } from "../hooks/internal-hooks.js";
1010
import type { HookEntry } from "../hooks/types.js";
1111
import { resolveUserPath } from "../utils.js";
1212
import { registerPluginCommand } from "./commands.js";
@@ -262,7 +262,7 @@ export function createPluginRegistry(registryParams: PluginRegistryParams) {
262262
}
263263

264264
for (const event of normalizedEvents) {
265-
registerInternalHook(event, handler);
265+
registerPluginHook(event, handler);
266266
}
267267
};
268268

0 commit comments

Comments
 (0)