Skip to content

Comments

feat(hooks): add message:received hook for pre-turn automation#7545

Open
wangtian24 wants to merge 2 commits intoopenclaw:mainfrom
wangtian24:feat/message-received-hook
Open

feat(hooks): add message:received hook for pre-turn automation#7545
wangtian24 wants to merge 2 commits intoopenclaw:mainfrom
wangtian24:feat/message-received-hook

Conversation

@wangtian24
Copy link

@wangtian24 wangtian24 commented Feb 2, 2026

Summary

Adds a new hook event type message:received that fires before a message is processed by the agent. This enables:

  • Running custom logic on incoming messages (e.g., memory curation with memfas)
  • Injecting context into the message body via injectedContext
  • Optionally skipping message processing via skipProcessing

Changes

  • src/hooks/internal-hooks.ts: Added MessageReceivedHookContext type and isMessageReceivedEvent() guard
  • src/web/auto-reply/monitor/on-message.ts: Wired up hook for WhatsApp channel
  • src/hooks/internal-hooks.test.ts: Added 10 new tests (28 total, all passing)

Hook Context

type MessageReceivedHookContext = {
  message: string;
  messageId?: string;
  senderId?: string;
  senderName?: string;
  channel: string;
  chatId?: string;
  isGroup: boolean;
  sessionKey: string;
  agentId: string;
  injectedContext?: string;  // mutable - prepended to message
  skipProcessing?: boolean;  // mutable - skip agent turn
  skipReason?: string;       // mutable - logged if skipped
};

Usage

// hooks/my-hook.js
export default async function(event) {
  if (event.type !== 'message' || event.action !== 'received') return;
  
  // Inject context
  event.context.injectedContext = 'Relevant memory: user likes pizza';
  
  // Or skip processing
  if (isSpam(event.context.message)) {
    event.context.skipProcessing = true;
    event.context.skipReason = 'spam detected';
  }
}

Future Work

  • Wire up other channels (Discord, Telegram, Slack, Signal, iMessage)
  • The pattern is identical - just add the hook trigger before each channel's dispatch

Closes #7541

Greptile Overview

Greptile Summary

This PR introduces a new internal hook event message:received (type message, action received) with a dedicated context shape and a runtime type guard (src/hooks/internal-hooks.ts). The WhatsApp web auto-reply handler now triggers this hook before calling processMessage(), allowing hooks to request skipping processing or to inject a context prefix into the inbound message (src/web/auto-reply/monitor/on-message.ts). Tests were extended to cover handler registration/triggering and the new isMessageReceivedEvent guard (src/hooks/internal-hooks.test.ts).

Key items to double-check before merging:

  • The current injection path mutates msg.body but processMessage() still computes and logs combinedBody separately, so the agent input (RawBody) and the logged/contextual Body can diverge.
  • The lockfile now contains two axios snapshot variants; ensure this is intentional and produced by the expected pnpm version/settings.

Confidence Score: 3/5

  • This PR is reasonably safe to merge but has a couple of behavioral/consistency concerns worth addressing.
  • Core hook plumbing and tests look straightforward, but the WhatsApp wiring currently injects context by mutating msg.body in a way that can diverge from how processMessage() constructs/logs combinedBody, and the pnpm-lock change introduces an additional axios resolution variant that should be verified as intentional.
  • src/web/auto-reply/monitor/on-message.ts, pnpm-lock.yaml

(2/5) Greptile learns from your feedback when you react with thumbs up/down!

Adds a new hook event type 'message:received' that fires before a message
is processed by the agent. This enables:

- Running custom logic on incoming messages (e.g., memory curation)
- Injecting context into the message body (injectedContext field)
- Optionally skipping message processing (skipProcessing field)

The hook context includes:
- message, messageId, senderId, senderName
- channel, chatId, isGroup
- sessionKey, agentId
- Mutable injectedContext and skipProcessing fields

Currently wired up for the WhatsApp web channel; other channels can be
added incrementally.

Implements openclaw#7541
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.

2 files reviewed, 3 comments

Edit Code Review Agent Settings | Greptile

Comment on lines +168 to +171
// Trigger message:received hook before agent processing
const hookEvent = createInternalHookEvent("message", "received", route.sessionKey, {
message: msg.body,
messageId: msg.id,
Copy link
Contributor

Choose a reason for hiding this comment

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

[P1] Hook context injection mutates msg.body but not msg.rawBody/combinedBody

In this handler you prepend injectedContext directly into msg.body (on-message.ts:193-198), but processMessage() builds its combinedBody from buildInboundLine() and separately passes RawBody: params.msg.body / Body: combinedBody (process-message.ts:143-149, 272-276). This means the injected context will affect command detection / agent input (RawBody) but not the logged/assembled combinedBody (Body), which can cause confusing behavior (agent sees extra prefix while logs/history/context show the original content). Consider injecting at the point where combinedBody is constructed, or ensure both the inbound Body and RawBody are updated consistently when injection is enabled.

Prompt To Fix With AI
This is a comment left during a code review.
Path: src/web/auto-reply/monitor/on-message.ts
Line: 168:171

Comment:
[P1] Hook context injection mutates `msg.body` but not `msg.rawBody`/`combinedBody`

In this handler you prepend `injectedContext` directly into `msg.body` (`on-message.ts:193-198`), but `processMessage()` builds its `combinedBody` from `buildInboundLine()` and separately passes `RawBody: params.msg.body` / `Body: combinedBody` (`process-message.ts:143-149`, `272-276`). This means the injected context will affect command detection / agent input (`RawBody`) but *not* the logged/assembled `combinedBody` (`Body`), which can cause confusing behavior (agent sees extra prefix while logs/history/context show the original content). Consider injecting at the point where `combinedBody` is constructed, or ensure both the inbound `Body` and `RawBody` are updated consistently when injection is enabled.

How can I resolve this? If you propose a fix, please make it concise.

Comment on lines +168 to +172
// Trigger message:received hook before agent processing
const hookEvent = createInternalHookEvent("message", "received", route.sessionKey, {
message: msg.body,
messageId: msg.id,
senderId: msg.senderJid?.trim() || msg.senderE164,
Copy link
Contributor

Choose a reason for hiding this comment

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

[P2] MessageReceivedHookContext duplication of sessionKey/agentId is easy to drift

createInternalHookEvent() already sets event.sessionKey, but MessageReceivedHookContext also requires sessionKey and agentId, and this handler populates both (on-message.ts:168-182). Since hook handlers will read from both event.sessionKey and event.context.sessionKey, it’s easy for a future channel to accidentally mismatch them. Consider removing sessionKey from the context (or deriving it from event.sessionKey in helpers) to keep a single source of truth.

Prompt To Fix With AI
This is a comment left during a code review.
Path: src/web/auto-reply/monitor/on-message.ts
Line: 168:172

Comment:
[P2] `MessageReceivedHookContext` duplication of `sessionKey`/`agentId` is easy to drift

`createInternalHookEvent()` already sets `event.sessionKey`, but `MessageReceivedHookContext` also requires `sessionKey` and `agentId`, and this handler populates both (`on-message.ts:168-182`). Since hook handlers will read from both `event.sessionKey` and `event.context.sessionKey`, it’s easy for a future channel to accidentally mismatch them. Consider removing `sessionKey` from the context (or deriving it from `event.sessionKey` in helpers) to keep a single source of truth.

How can I resolve this? If you propose a fix, please make it concise.

@greptile-apps
Copy link
Contributor

greptile-apps bot commented Feb 2, 2026

Additional Comments (1)

pnpm-lock.yaml
[P1] pnpm-lock changes introduce a second [email protected] snapshot variant

This lockfile change switches several snapshots from axios: 1.13.4([email protected]) to plain axios: 1.13.4 and adds a new [email protected] snapshot alongside the existing [email protected]([email protected]) (pnpm-lock.yaml:6323, 8239+). If this wasn’t intentional, it can bloat the lockfile and lead to subtle differences in the resolved follow-redirects variant (with/without debug). It may be worth re-generating the lockfile in a consistent environment or confirming this is expected for the repo’s pnpm version/settings.

Prompt To Fix With AI
This is a comment left during a code review.
Path: pnpm-lock.yaml
Line: 6320:6327

Comment:
[P1] pnpm-lock changes introduce a second `[email protected]` snapshot variant

This lockfile change switches several snapshots from `axios: 1.13.4([email protected])` to plain `axios: 1.13.4` and adds a new `[email protected]` snapshot alongside the existing `[email protected]([email protected])` (`pnpm-lock.yaml:6323`, `8239+`). If this wasn’t intentional, it can bloat the lockfile and lead to subtle differences in the resolved `follow-redirects` variant (with/without `debug`). It may be worth re-generating the lockfile in a consistent environment or confirming this is expected for the repo’s pnpm version/settings.

How can I resolve this? If you propose a fix, please make it concise.

@openclaw-barnacle openclaw-barnacle bot added docs Improvements or additions to documentation channel: whatsapp-web Channel integration: whatsapp-web labels Feb 2, 2026
@Zjianru
Copy link

Zjianru commented Feb 7, 2026

Support: This is a real need / 支持:这是真实需求

Thanks for being the first to tackle this. I want to add a data point: I independently built an external events-framework (1500 lines Python, event inbox + router + rate-limiting + dedup + quiet hours + Telegram) as a workaround for the lack of native message hooks.

感谢你第一个尝试解决这个问题。我想补充一个数据点:我独立构建了一个外部 events-framework(1500 行 Python,事件收件箱 + 路由器 + 限频 + 去重 + 静默时段 + Telegram)来绕过缺少原生消息钩子的问题。

The fact that multiple people (#7545, #9387, #9859) have independently tried to add message:received hooks shows this is a genuine community need.

多人(#7545#9387#9859)独立尝试添加 message:received 钩子,说明这是真实的社区需求。

I'd love to see maintainers weigh in on the preferred approach so we can consolidate effort.

希望维护者能对首选方案表态,让我们集中力量。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

channel: whatsapp-web Channel integration: whatsapp-web docs Improvements or additions to documentation

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat: Add message:received hook for pre-turn automations

2 participants