fix(hooks): use globalThis singleton for handler registry to survive bundle splitting

Without this fix, the bundler can emit multiple copies of internal-hooks
into separate chunks. registerInternalHook writes to one Map instance
while triggerInternalHook reads from another — resulting in hooks that
silently fire with zero handlers regardless of how many were registered.

Reproduce: load a hook via hooks.external.entries (loader reads one chunk),
then send a message:transcribed event (get-reply imports a different chunk).
The handler list is empty; the hook never runs.

Fix: use globalThis.__openclaw_internal_hook_handlers__ as a shared
singleton. All module copies check for and reuse the same Map, ensuring
registrations are always visible to triggers.
This commit is contained in:
Eric Lytle
2026-03-02 23:25:31 +00:00
committed by Peter Steinberger
parent 1e8afa16f0
commit 0d8beeb4e5

View File

@@ -200,8 +200,23 @@ export interface InternalHookEvent {
export type InternalHookHandler = (event: InternalHookEvent) => Promise<void> | void;
/** Registry of hook handlers by event key */
const handlers = new Map<string, InternalHookHandler[]>();
/**
* Registry of hook handlers by event key.
*
* Uses a globalThis singleton so that registerInternalHook and
* triggerInternalHook always share the same Map even when the bundler
* emits multiple copies of this module into separate chunks (bundle
* splitting). Without the singleton, handlers registered in one chunk
* are invisible to triggerInternalHook in another chunk, causing hooks
* to silently fire with zero handlers.
*/
const _g = globalThis as typeof globalThis & {
__openclaw_internal_hook_handlers__?: Map<string, InternalHookHandler[]>;
};
if (!_g.__openclaw_internal_hook_handlers__) {
_g.__openclaw_internal_hook_handlers__ = new Map<string, InternalHookHandler[]>();
}
const handlers = _g.__openclaw_internal_hook_handlers__;
const log = createSubsystemLogger("internal-hooks");
/**