feat(plugins): add before_message_process hook with first-claim-wins semantics#50366
feat(plugins): add before_message_process hook with first-claim-wins semantics#50366xyzphp wants to merge 6 commits intoopenclaw:mainfrom
Conversation
Greptile SummaryThis PR introduces a new Key findings:
Confidence Score: 2/5
|
WingedDragon
left a comment
There was a problem hiding this comment.
🔍 Review: Approve with nits
Scope: Adds before_message_process hook — fires after routing but before AI agent runs. Plugins can return { handled: true } to intercept messages and skip AI processing (first-claim-wins).
Strengths:
- Well-documented: CHANGELOG, agent-loop docs, and inline comments all updated
- Test coverage is thorough:
handled: trueskips AI,undefinedruns AI,handled: falseruns AI - The hook placement (after routing, before agent) is the right interception point
- First-claim-wins semantics prevent multiple plugins from fighting over message ownership
Nit: The before_message_process hook docs in agent-loop.md say "Return { handled: true } to intercept" — consider also documenting what happens to the message when it's intercepted. Is it silently dropped? Can the hook provide a custom response? This would help plugin authors understand the full behavior.
Verdict: Solid feature addition. Ship it.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: baa082068b
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
WingedDragon
left a comment
There was a problem hiding this comment.
✅ Approved
Scope: Adds before_message_process hook that fires after routing but before AI agent runs. Plugins can return { handled: true } to intercept and skip AI processing (first-claim-wins semantics).
Strengths:
- Well-designed hook lifecycle placement: after routing (session key resolved, channel identified) but before AI (token cost avoided)
- First-claim-wins semantics — once one plugin handles it, no further processing
- ACP-dispatched messages correctly excluded (they have their own lifecycle)
- Test coverage is thorough: handled:true skips AI, undefined/false proceed, event fields correctly passed
- CHANGELOG and docs updated consistently
- Type definitions properly extended in
src/plugins/types.ts
No concerns. Clean hook addition with good test coverage and documentation. Ship it.
WingedDragon
left a comment
There was a problem hiding this comment.
🔍 Review: Approve with nits
Scope: New before_message_process hook that fires after routing but before AI processing. Plugins can return { handled: true } to intercept and skip AI entirely (first-claim-wins semantics).
Strengths:
- Well-designed hook lifecycle:
message_received→ routing →before_message_process→ AI agent → outbound hooks - First-claim-wins semantics are correct — prevents multiple plugins from fighting over message ownership
handled: truesilently drops the message with no automatic reply — the plugin is responsible for responding. This is the right design (avoids double replies).- ACP-dispatched messages correctly skip this hook (they have their own session lifecycle)
- 5 thorough tests: handled=true skips AI, undefined passes through, handled:false passes through, event fields verification, no-registered-handlers skips
- CHANGELOG and docs updated
Nits:
-
No test for first-claim-wins behavior — The description mentions "first-claim-wins across registered plugins" but there's no test verifying that when plugin A returns
handled: trueand plugin B would also want to handle it, plugin B is never called. This is the core semantic guarantee. -
No rate limiting or abuse protection — A misconfigured plugin that returns
handled: truefor all messages would silently drop ALL inbound messages. Consider logging a warning when messages are intercepted, or exposing a counter/metric.
Verdict: Good architectural addition. Ship it after addressing the first-claim-wins test gap.
8f9a6fa to
6e5a18d
Compare
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 6e5a18db85
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
94ab012 to
00b6f2b
Compare
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 00b6f2b4e5
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
771c751 to
5db0ac7
Compare
…semantics - Add PluginHookBeforeMessageProcessEvent and PluginHookBeforeMessageProcessResult types - Add runBeforeMessageProcess() to hook runner; first handler returning handled:true short-circuits AI agent processing - Wire hook into dispatch-from-config after ACP dispatch, before AI agent - Add unit tests (wired-hooks-before-message-process.test.ts) covering no-handler, intercept, pass-through, first-claim-wins, and full event field passing - Add dispatch-from-config integration tests for intercept and pass-through paths - Update docs/concepts/agent-loop.md to document the new hook - Add CHANGELOG entry under Unreleased
- Add missing runBeforeMessageProcess mock to hookMocks.runner in dispatch-from-config.test.ts (fixes TypeError in beforeEach) - Fix conversationId assertion to match actual toPluginMessageContext output - Remove unpopulated metadata field from PluginHookBeforeMessageProcessEvent - Document intercepted-message behavior and ACP bypass in agent-loop.md
Move the hook call to fire after fast-abort but before sendPolicy and ACP dispatch. This ensures plugins can intercept or forward inbound messages even when the AI agent is disabled via /send off or config-level send policy deny the main forwarding/filtering use case for this hook. Update docs/concepts/agent-loop.md to reflect the new execution order and remove the incorrect ACP note. Add integration test: hook fires and can intercept when sendPolicy is deny.
5db0ac7 to
531b462
Compare
Summary
The existing
message_receivedhook is observational (fire-and-forget) and cannotstop downstream agent processing.
often need to forward inbound messages to external systems before the AI agent replies.
In addition, some plugins may need to perform lightweight pre-processing such as
authentication checks, request validation, or filtering, and decide whether to skip
AI processing entirely.
before_message_processhook that runs after messagerouting but before the AI agent starts. Plugins can use this hook to intercept
messages for forwarding, filtering, or pre-checks (e.g. authentication). If a plugin
returns
{ handled: true }, the message is intercepted and AI processing is skipped.Multiple handlers run sequentially with first-claim-wins semantics.
full authentication/authorization system and does not modify ACP dispatch, tool
execution, session lifecycle, or message routing semantics.
Relationship with #43422 (before_dispatch)
#43422 proposes a
before_dispatchhook for authentication gate scenarios. This PRtakes a different approach:
before_message_processprovides richer inbound message contextChange Type (select all)
Scope (select all touched areas)
Linked Issue/PR
before_dispatchplugin hook for universal pre-LLM message interception #43418 (Feature Request: before_dispatch hook for pre-LLM interception)before_dispatchhook for pre-LLM message interception #43422 (PR: feat(plugins): add before_dispatch hook)User-visible / Behavior Changes
Plugins can now register
api.on("before_message_process", handler):{ handled: true }intercepts the inbound message and prevents AI agent processing.undefined,void, or{ handled: false }lets the message continue through the normal AI path.handled: truewins and later handlers are not called.Security Impact (required)
Yes/No) YesYes/No) NoYes/No) NoYes/No) NoYes/No) NoYes, explain risk + mitigation:Repro + Verification
Environment
Steps
before_message_processand returns{ handled: true }.Expected
before_message_process.Actual
src/plugins/wired-hooks-before-message-process.test.tscovers no-hook pass-through,handled: trueinterception,handled: falsepass-through,voidpass-through, first-claim-wins behavior, second-handler fallback, and full event field propagation.src/auto-reply/reply/dispatch-from-config.test.tsadds dispatch-path coverage forhandled: trueshort-circuiting,undefinedcontinuing into the AI path, andhandled: falsecontinuing into the AI path.Evidence
Attach at least one:
Relevant coverage:
src/plugins/wired-hooks-before-message-process.test.tssrc/auto-reply/reply/dispatch-from-config.test.tsHuman Verification (required)
What you personally verified (not just CI), and how:
handled: false,undefined, no registered hook, and multiple handlers with first-claim-wins behavior.Review Conversations
If a bot review conversation is addressed by this PR, resolve that conversation yourself. Do not leave bot review conversation cleanup for maintainers.
Compatibility / Migration
Yes/No) YesYes/No) NoYes/No) NoFailure Recovery (if this breaks)
before_message_process, or revert the hook dispatch block indispatch-from-config.ts.src/auto-reply/reply/dispatch-from-config.ts,src/plugins/hooks.ts,src/plugins/types.ts{ handled: true }.Risks and Mitigations
{ handled: true }return value, uses first-claim-wins semantics, and logs an optional reason for debugging.