feat(plugins): add cancel support to message_received hook#11681
feat(plugins): add cancel support to message_received hook#11681PrimeTenet wants to merge 3 commits intoopenclaw:mainfrom
Conversation
Enables plugins to block inbound messages by returning { cancel: true },
matching existing message_sending cancel behavior.
Changes:
- Add PluginHookMessageReceivedResult type with cancel field
- Update runMessageReceived to collect results from parallel hook execution
- Process cancel in dispatch-from-config.ts to stop message processing
Hook execution: Preserves existing parallel execution (Promise.all).
Cancel merge: If ANY hook returns { cancel: true }, message is cancelled.
Use cases:
- Security plugins blocking malicious content
- Content moderation
- Rate limiting
- Access control
Breaking changes: None (purely additive)
Co-authored-by: Cursor <cursoragent@cursor.com>
| // Run all hooks in parallel (preserves existing behavior) | ||
| const promises = hooks.map(async (hook) => { | ||
| try { | ||
| return await ( |
There was a problem hiding this comment.
message_received no longer fire-and-forget
runMessageReceived now awaits Promise.all(promises) and returns a result, which changes runtime behavior (it blocks the caller until all hooks finish). The docstring still implies the previous behavior via “preserves existing behavior”/parallel wording; if other inbound paths rely on fire-and-forget semantics this change will introduce head-of-line blocking. Either keep message_received fire-and-forget and introduce a separate cancellable variant, or update callers/expectations consistently.
Prompt To Fix With AI
This is a comment left during a code review.
Path: src/plugins/hooks.ts
Line: 255:258
Comment:
**`message_received` no longer fire-and-forget**
`runMessageReceived` now `await`s `Promise.all(promises)` and returns a result, which changes runtime behavior (it blocks the caller until all hooks finish). The docstring still implies the previous behavior via “preserves existing behavior”/parallel wording; if other inbound paths rely on fire-and-forget semantics this change will introduce head-of-line blocking. Either keep `message_received` fire-and-forget and introduce a separate cancellable variant, or update callers/expectations consistently.
How can I resolve this? If you propose a fix, please make it concise.| try { | ||
| const hookResult = await hookRunner.runMessageReceived( | ||
| { | ||
| from: ctx.From ?? "", |
There was a problem hiding this comment.
Cancel path skips diagnostics
When hookResult?.cancel is true, the function returns early (dispatch is dropped) but doesn’t call recordProcessed(...)/markIdle(...). With diagnostics enabled and a session key present, this can leave the session in a “processing” state without a corresponding “idle” transition and no processed outcome recorded.
Prompt To Fix With AI
This is a comment left during a code review.
Path: src/auto-reply/reply/dispatch-from-config.ts
Line: 169:172
Comment:
**Cancel path skips diagnostics**
When `hookResult?.cancel` is true, the function `return`s early (dispatch is dropped) but doesn’t call `recordProcessed(...)`/`markIdle(...)`. With diagnostics enabled and a session key present, this can leave the session in a “processing” state without a corresponding “idle” transition and no processed outcome recorded.
How can I resolve this? If you propose a fix, please make it concise.- Update docstring to clarify message_received now blocks until all hooks complete - Add diagnostics cleanup (recordProcessed/markIdle) when message is cancelled to prevent session tracking from getting stuck in processing state Addresses reviewer feedback on PR openclaw#11681 Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
bfc1ccb to
f92900f
Compare
|
This pull request has been automatically marked as stale due to inactivity. |
|
This pull request has been automatically marked as stale due to inactivity. |
Summary
Enables plugins to block inbound messages by returning
{ cancel: true }from themessage_receivedhook, matching the existingmessage_sendingcancel behavior.Changes
3 files changed:
src/plugins/types.ts- AddPluginHookMessageReceivedResulttype withcancelfieldsrc/plugins/hooks.ts- Collect results from parallel hook execution, return{ cancel: true }if any hook cancelledsrc/auto-reply/reply/dispatch-from-config.ts- Await hook results and stop processing if message marked for cancelUsage
api.on('message_received', async (event, ctx) => {
if (containsThreat(event.content)) {
return { cancel: true }; // Block the message
}
// Allow through
}, { priority: 100 });
Use Cases
Behavior
Promise.allbehavior){ cancel: true }, message is blockedBreaking Changes
Latency impact: Messages now wait for all
message_receivedhooks to complete before processing (was fire-and-forget). Adds ~10ms latency per message if hooks are present.Plugins should be optimized for fast execution (<10ms recommended).
Backward Compatibility
Existing
message_receivedhooks that returnvoidcontinue to work unchanged.Greptile Overview
Greptile Summary
Adds a cancellable return type for the
message_receivedplugin hook ({ cancel?: boolean }), updates the hook runner to execute allmessage_receivedhandlers in parallel and aggregate their results, and changes the config-based dispatcher to awaitmessage_receivedhooks and stop further processing when any hook cancels.This fits into the existing plugin hook system by extending the hook type map in
src/plugins/types.tsand exposing the new result type viasrc/plugins/hooks.ts, then consuming the cancel result in the inbound message dispatch path.Confidence Score: 3/5
(2/5) Greptile learns from your feedback when you react with thumbs up/down!