The hook file discovery works. The gateway loads managed hooks from
~/.openclaw/hooks/ and finds working-memory/ → reads HOOK.md → selects
handler.js as the handler (candidates list: handler.ts, handler.js, index.ts,
index.js — handler.mjs is not on that list, so your manual test is testing a
different file than what the gateway loads).
The HOOK.md metadata parsing probably works via a roundabout path: YAML
parsing fails on the trailing commas inside the flow mapping (YAML doesn't
allow them), so it falls back to the line-based parser which captures the
whole indented block as a raw multi-line string, then JSON5 successfully
parses it (JSON5 does allow trailing commas). End result: events are
extracted.
The message:sent event is conditionally gated. In deliver-DgXmvvc9.js:634-653:
const canEmitInternalHook = Boolean(params.sessionKeyForInternalHooks);
// ...
if (!canEmitInternalHook) return;
fireAndForgetHook(triggerInternalHook(createInternalHookEvent("message",
"sent", ...)))
More importantly: deliverOutboundPayloads is the channel outbound delivery
path — WhatsApp, Telegram, SMS, etc. The webchat/ACP UI is a direct WebSocket
response; it likely never calls this function, so message:sent may simply
never fire for webchat sessions.