Skip to content

Comments

feat(hooks): add message:sent and message:received internal hook events#6384

Closed
heybeaux wants to merge 5 commits intoopenclaw:mainfrom
heybeaux:feat/message-lifecycle-hooks
Closed

feat(hooks): add message:sent and message:received internal hook events#6384
heybeaux wants to merge 5 commits intoopenclaw:mainfrom
heybeaux:feat/message-lifecycle-hooks

Conversation

@heybeaux
Copy link

@heybeaux heybeaux commented Feb 1, 2026

Summary

Implements the internal hook events documented as "Future Events" in the hooks documentation. These events enable extensions to observe the complete message lifecycle.

Related Issue

This PR addresses the functionality requested in #5354, which was closed as NOT_PLANNED due to maintainer bandwidth. As suggested, submitting a PR instead.

Changes

New Events

message:received - Triggered when an inbound message is processed

  • Context includes: text, channel, chatType, from, to, senderId, senderName, senderUsername, messageId, threadId, timestamp, accountId, mediaUrls, mediaTypes
  • Triggered in dispatch-from-config.ts after duplicate check, alongside the existing plugin hook

message:sent - Triggered when an outbound message is successfully delivered

  • Context includes: text, channel, chatType, kind (tool/block/final), mediaUrl, mediaUrls, timestamp
  • Triggered in reply-dispatcher.ts after successful delivery

Implementation Details

  1. Added "message" to InternalHookEventType in src/hooks/internal-hooks.ts
  2. Trigger message:received in dispatchReplyFromConfig() for inbound messages
  3. Trigger message:sent in the reply dispatcher after delivery success
  4. Added sessionKey, channel, and chatType options to ReplyDispatcherOptions for hook context
  5. Updated dispatchInboundMessageWithDispatcher and dispatchInboundMessageWithBufferedDispatcher to inject session context into dispatcher options

Use Cases

  • Agent memory systems (e.g., Engram) that need to persist conversations for context retrieval
  • Analytics and logging plugins for conversation insights
  • Message archival and search indexing
  • Conversation export features

Example Usage

import { registerInternalHook } from './hooks/internal-hooks.js';

// Track all received messages
registerInternalHook('message:received', async (event) => {
  await saveToMemory({
    sessionKey: event.sessionKey,
    text: event.context.text,
    from: event.context.from,
    timestamp: event.timestamp,
  });
});

// Track all sent messages
registerInternalHook('message:sent', async (event) => {
  await logOutbound({
    sessionKey: event.sessionKey,
    text: event.context.text,
    kind: event.context.kind,
    timestamp: event.context.timestamp,
  });
});

Testing

  • TypeScript compilation passes ✅
  • Existing internal hook tests pass ✅
  • The hooks are fire-and-forget with error handling to prevent disrupting message flow

Checklist

  • Code compiles without errors
  • Follows existing hook patterns (createInternalHookEvent, triggerInternalHook)
  • Non-blocking (uses void promises with catch)
  • Includes context useful for memory/analytics use cases

Greptile Overview

Greptile Summary

Adds two new internal hook events to the existing internal hook registry: message:received is emitted during inbound message processing (after duplicate-check) with message/metadata context, and message:sent is emitted after successful outbound delivery from the reply dispatcher. To support outbound context, dispatcher options are extended to carry sessionKey, channel, and chatType, and the inbound dispatch entrypoints now inject those values from the finalized inbound context.

Confidence Score: 4/5

  • This PR is generally safe to merge, with a couple of hook-contract consistency issues worth addressing.
  • Changes are localized to hook emission points and dispatcher option plumbing, and the new hooks are fire-and-forget. Main concerns are consistency/contract details (timestamp representation and asymmetric gating on sessionKey) and observability (silent error swallowing) rather than core message delivery correctness.
  • src/auto-reply/reply/reply-dispatcher.ts and src/auto-reply/reply/dispatch-from-config.ts

(3/5) Reply to the agent's comments like "Can you suggest a fix for this @greptileai?" or ask follow-up questions!

Copy link
Contributor

@greptile-apps greptile-apps bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

1 file reviewed, 3 comments

Edit Code Review Agent Settings | Greptile

@heybeaux
Copy link
Author

heybeaux commented Feb 2, 2026

Hi maintainers,

Just following up on this PR. Greptile bot feedback has been addressed:

  • Removed duplicate timestamp (now uses event.timestamp only)
  • Made sessionKey handling consistent (both events fire even with empty sessionKey)
  • Added error logging at verbose level (matching existing patterns)

The CI failures appear to be unrelated to this PR — they're in Swift lint and Windows build, while this change only touches TypeScript hook dispatch code. Happy to rebase if there are conflicts, but wanted to check if a maintainer could re-run CI or confirm these failures are pre-existing.

This feature would enable automatic memory capture for AI agents via the hook system. Appreciate any guidance on getting it over the line.

@heybeaux heybeaux force-pushed the feat/message-lifecycle-hooks branch from fdd23a7 to bd107e2 Compare February 2, 2026 14:41
@f2daz
Copy link

f2daz commented Feb 4, 2026

👍 This feature would be very valuable for our Mission Control dashboard integration. We're building a real-time agent activity tracker that needs to sync tool calls and messages to an external dashboard. Looking forward to this landing!

Use case: Automatic status updates in a custom dashboard showing what the agent is currently doing, token usage, and activity history.

@heybeaux
Copy link
Author

heybeaux commented Feb 4, 2026

@f2daz It sounds like a lot of projects, both existing and in development, would benefit from this feature. I'm hopeful that we'll see movement in the next week or so. It's incredible to see such a busy open source project.

@Glucksberg
Copy link
Contributor

📋 Duplicate triage note: PR #6797 appears to implement the same fix. Consider consolidating or closing one in favor of the other.

@heybeaux
Copy link
Author

heybeaux commented Feb 9, 2026

Hey @Glucksberg, thanks for flagging the overlap with #6797.

I've taken a look at both implementations and they're solving the same problem with very similar approaches — dedicated message:received and message:sent hook events using the existing internal hook system.

A few observations:

I'm very open to consolidating — the goal is getting this feature landed, not who gets credit. A few paths forward:

  1. If the maintainers prefer feat(hooks): add message:received and message:sent hook events #6797's module structure, I'm happy to adopt that pattern here and rebase
  2. If feat(hooks): add message:received and message:sent hook events #6797 is closer to what the team wants, I can close this PR — just want to make sure the context fields from both are considered (this PR includes chatType, from, to, mediaUrls which are useful for memory/analytics use cases)
  3. Happy to collaborate directly with @NOVA-Openclaw if that's helpful

Whatever gets the feature across the finish line works for me. 🤝

grunt3714-lgtm added a commit to grunt3714-lgtm/grunt3714-lgtm-openclaw that referenced this pull request Feb 11, 2026
Implements the message lifecycle hooks documented as 'Future Events' in the
hooks documentation. These events enable extensions to observe inbound and
outbound messages for logging, analytics, memory systems, and archival.

New events:
- message:received: triggered when an inbound message is processed
- message:sent: triggered after an outbound message is successfully delivered

Both events include rich context (text, channel, chatType, sender info,
media URLs) and are fired asynchronously so they never block message
processing.

Includes:
- 6 test cases covering event triggering, general/specific handler routing,
  media fields, error isolation, and type validation
- Documentation update moving these from 'Future Events' to documented events

Combines approaches from prior community PRs openclaw#6384, openclaw#6797, and openclaw#9859.

Resolves openclaw#12775, openclaw#8807, openclaw#13004
Related: openclaw#5354, openclaw#12867, openclaw#12914
@matskevich
Copy link

hey @heybeaux — running message hooks in production (via backport of #6797, same concept as yours) for a week now. 550+ events, 3 hooks built on top (memory, DLP, multi-bot sync).

found a real gap and opened a discussion with a proposal to extend the lifecycle: #14539

short version: message:received fires before media understanding, so voice messages arrive as <media:audio> with no transcript. proposing message:preprocessed as a third event after applyMediaUnderstanding().

both PRs solve the same core problem. would be great to coordinate — happy to contribute groupId fix and preprocessed to whichever PR the maintainers prefer.

@beaux-riel
Copy link

Hey @matskevich — love that you've been battle-testing this in production. 550+ events with zero crashes is exactly the validation this feature needs.

The message:preprocessed gap is real and I've hit it too — building a memory system (Engram) on top of OpenClaw, and voice messages arriving as <media:audio> tags are useless for semantic search. Having the transcript available at hook time is critical.

I'm happy to add message:preprocessed to this PR. The three-event lifecycle makes sense:

received → media/link understanding → preprocessed → agent → sent

Your proposed MessagePreprocessedContext type looks clean. I'd suggest we also carry forward the context fields from this PR (chatType, from, to, mediaUrls) across all three events for consistency.

Plan:

  1. I'll rebase this PR and add the message:preprocessed event following the pattern in your discussion
  2. Include groupId fix
  3. Keep it minimal — ~40 lines, no breaking changes, same hook system

If you want to co-author or review, happy to coordinate. Let's get this landed. 🤝

@openclaw-barnacle openclaw-barnacle bot added docs Improvements or additions to documentation channel: bluebubbles Channel integration: bluebubbles channel: discord Channel integration: discord channel: googlechat Channel integration: googlechat channel: imessage Channel integration: imessage channel: line Channel integration: line channel: matrix Channel integration: matrix channel: mattermost Channel integration: mattermost channel: msteams Channel integration: msteams channel: nextcloud-talk Channel integration: nextcloud-talk channel: nostr Channel integration: nostr channel: signal Channel integration: signal channel: slack Channel integration: slack channel: telegram Channel integration: telegram channel: tlon Channel integration: tlon channel: voice-call Channel integration: voice-call channel: whatsapp-web Channel integration: whatsapp-web labels Feb 12, 2026
@openclaw-barnacle openclaw-barnacle bot added size: M and removed extensions: lobster Extension: lobster extensions: memory-core Extension: memory-core extensions: memory-lancedb Extension: memory-lancedb extensions: open-prose Extension: open-prose cli CLI command changes security Security documentation scripts Repository scripts commands Command implementations docker Docker and sandbox tooling agents Agent runtime and tooling channel: feishu Channel integration: feishu channel: twitch Channel integration: twitch extensions: minimax-portal-auth size: XL labels Feb 12, 2026
@heybeaux heybeaux force-pushed the feat/message-lifecycle-hooks branch from b516878 to 6b6762d Compare February 12, 2026 20:43
Clawdbot added 3 commits February 12, 2026 12:44
Implements the internal hook events documented as 'Future Events' in the hooks
documentation. These events enable extensions to observe message lifecycle:

- message:received: Triggered when an inbound message is processed
  - Includes: text, channel, chatType, from/to, senderId, senderName,
    messageId, threadId, timestamp, mediaUrls, mediaTypes
  - Triggered in dispatch-from-config.ts after duplicate check

- message:sent: Triggered when an outbound message is successfully delivered
  - Includes: text, channel, chatType, kind (tool/block/final),
    mediaUrl, mediaUrls, timestamp
  - Triggered in reply-dispatcher.ts after successful delivery

Use cases:
- Agent memory systems (e.g., Engram) that need to persist conversations
- Analytics and logging plugins
- Message archival and search indexing
- Conversation export features

API example:
  registerInternalHook('message:received', async (event) => {
    await saveToMemory(event.context.text, event.sessionKey);
  });

  registerInternalHook('message:sent', async (event) => {
    await logOutbound(event.context.text, event.context.kind);
  });

Closes openclaw#5354 (NOT_PLANNED - now implemented via PR)
- Remove duplicate timestamp from hook context (use event.timestamp)
- Make sessionKey handling consistent (fire even with empty key, fall back to "")
- Add debug logging for hook errors (match dispatch-from-config pattern)
@heybeaux heybeaux force-pushed the feat/message-lifecycle-hooks branch from 6b6762d to a15c032 Compare February 12, 2026 20:46
…ding

Fires after applyMediaUnderstanding() and applyLinkUnderstanding(), before
agent processing. Voice messages now have transcripts, images have descriptions,
and links have summaries when this hook fires.

Also adds groupId to message:received internal hook context.

Addresses discussion openclaw#14539 (three-event lifecycle proposal).
@heybeaux heybeaux force-pushed the feat/message-lifecycle-hooks branch from a15c032 to 16c9d44 Compare February 12, 2026 20:49
@beaux-riel
Copy link

Updated — PR now implements the full three-event lifecycle from discussion #14539:

message:received → applyMediaUnderstanding + applyLinkUnderstanding → message:preprocessed → agent → message:sent

What's new in the latest push:

  • message:preprocessed hook — fires after media/link understanding, before agent processing. Voice messages arrive with transcripts, images with descriptions, links with summaries. Both plugin hook and internal hook variants.
  • groupId added to message:received internal hook context
  • All CI checks passing ✅

This addresses @matskevich's production finding that message:received fires too early for hooks that need enriched content (memory, search, moderation). The preprocessed event fills that gap with ~40 lines of new code, no breaking changes.

4 commits, 7 files, +172 lines. Ready for review.

@sebslight
Copy link
Member

Closing as duplicate of #14196. If this is incorrect, comment and we can reopen.

@sebslight sebslight closed this Feb 13, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants