dispatch: broadcast inbound_claim to global plugin listeners#58862
dispatch: broadcast inbound_claim to global plugin listeners#58862flowolforg wants to merge 1 commit intoopenclaw:mainfrom
Conversation
Greptile SummaryThis PR fixes a gap in plugin hook dispatch: The implementation is correct and the logic is safe:
Confidence Score: 5/5Safe to merge — the change is a correct, targeted call-site addition with no risk of double-invocation or regression on existing plugin-bound flows. All remaining findings are P2 (a minor style inconsistency around the missing No files require special attention. Prompt To Fix All With AIThis is a comment left during a code review.
Path: src/auto-reply/reply/dispatch-from-config.ts
Line: 421-431
Comment:
**Missing `hasHooks` guard — minor inconsistency with adjacent patterns**
Every other claiming/observing hook call in this function guards on `hasHooks(...)` before calling the runner method (see `before_dispatch` at line 565 and `message_received` at line 434). The new broadcast call skips that guard.
`runClaimingHook` does return `undefined` immediately when no hooks are registered, so there is no functional difference. But the unconditional call still pays for an async dispatch and a registry lookup on every inbound message, even when no plugin has ever registered an `inbound_claim` handler (the common case for most deployments).
```suggestion
if (hookRunner?.hasHooks("inbound_claim")) {
const broadcastResult = await hookRunner.runInboundClaim(
inboundClaimEvent,
inboundClaimContext,
);
if (broadcastResult?.handled) {
markIdle("plugin_broadcast_claim");
recordProcessed("completed", { reason: "plugin-broadcast-handled" });
return { queuedFinal: false, counts: dispatcher.getQueuedCounts() };
}
}
```
How can I resolve this? If you propose a fix, please make it concise.Reviews (1): Last reviewed commit: "dispatch: broadcast inbound_claim to glo..." | Re-trigger Greptile |
| if (hookRunner) { | ||
| const broadcastResult = await hookRunner.runInboundClaim( | ||
| inboundClaimEvent, | ||
| inboundClaimContext, | ||
| ); | ||
| if (broadcastResult?.handled) { | ||
| markIdle("plugin_broadcast_claim"); | ||
| recordProcessed("completed", { reason: "plugin-broadcast-handled" }); | ||
| return { queuedFinal: false, counts: dispatcher.getQueuedCounts() }; | ||
| } | ||
| } |
There was a problem hiding this comment.
Missing
hasHooks guard — minor inconsistency with adjacent patterns
Every other claiming/observing hook call in this function guards on hasHooks(...) before calling the runner method (see before_dispatch at line 565 and message_received at line 434). The new broadcast call skips that guard.
runClaimingHook does return undefined immediately when no hooks are registered, so there is no functional difference. But the unconditional call still pays for an async dispatch and a registry lookup on every inbound message, even when no plugin has ever registered an inbound_claim handler (the common case for most deployments).
| if (hookRunner) { | |
| const broadcastResult = await hookRunner.runInboundClaim( | |
| inboundClaimEvent, | |
| inboundClaimContext, | |
| ); | |
| if (broadcastResult?.handled) { | |
| markIdle("plugin_broadcast_claim"); | |
| recordProcessed("completed", { reason: "plugin-broadcast-handled" }); | |
| return { queuedFinal: false, counts: dispatcher.getQueuedCounts() }; | |
| } | |
| } | |
| if (hookRunner?.hasHooks("inbound_claim")) { | |
| const broadcastResult = await hookRunner.runInboundClaim( | |
| inboundClaimEvent, | |
| inboundClaimContext, | |
| ); | |
| if (broadcastResult?.handled) { | |
| markIdle("plugin_broadcast_claim"); | |
| recordProcessed("completed", { reason: "plugin-broadcast-handled" }); | |
| return { queuedFinal: false, counts: dispatcher.getQueuedCounts() }; | |
| } | |
| } |
Prompt To Fix With AI
This is a comment left during a code review.
Path: src/auto-reply/reply/dispatch-from-config.ts
Line: 421-431
Comment:
**Missing `hasHooks` guard — minor inconsistency with adjacent patterns**
Every other claiming/observing hook call in this function guards on `hasHooks(...)` before calling the runner method (see `before_dispatch` at line 565 and `message_received` at line 434). The new broadcast call skips that guard.
`runClaimingHook` does return `undefined` immediately when no hooks are registered, so there is no functional difference. But the unconditional call still pays for an async dispatch and a registry lookup on every inbound message, even when no plugin has ever registered an `inbound_claim` handler (the common case for most deployments).
```suggestion
if (hookRunner?.hasHooks("inbound_claim")) {
const broadcastResult = await hookRunner.runInboundClaim(
inboundClaimEvent,
inboundClaimContext,
);
if (broadcastResult?.handled) {
markIdle("plugin_broadcast_claim");
recordProcessed("completed", { reason: "plugin-broadcast-handled" });
return { queuedFinal: false, counts: dispatcher.getQueuedCounts() };
}
}
```
How can I resolve this? If you propose a fix, please make it concise.
This comment was marked as spam.
This comment was marked as spam.
Add a broadcast call to hookRunner.runInboundClaim() after the plugin-bound block in dispatch-from-config.ts. This lets global plugin handlers (spam filters, rate limiters, group-chat observers like listen-only) receive all unbound inbound events. - Uses existing infrastructure from PR openclaw#45318 - hasHooks("inbound_claim") guard matches adjacent hook patterns - Early-return when any listener returns { handled: true } Closes openclaw#48434
Problem
inbound_claimcurrently only fires for plugin-bound conversations (viarunInboundClaimForPluginOutcome). Plugins that register global handlers — e.g. listen-only observers, spam filters, rate limiters — are never invoked for unbound inbound events.Reported in #48434.
Change
After the
if (pluginOwnedBinding)block indispatch-from-config.ts, add a single broadcast call tohookRunner.runInboundClaim(). This iterates all registered global listeners regardless of conversation binding. If any listener returns{ handled: true }, dispatch stops early (same early-return pattern used for plugin-bound handling).Why this is safe
runInboundClaim, types, runners, event mappers, tests) already exists from PR Plugins: broaden plugin surface for Codex App Server #45318 — this is purely a missing call-site.Testing
Existing tests in
dispatch-from-config.test.tsandwired-hooks-inbound-claim.test.tscover the runner. I've validated the broadcast path locally against theopenclaw-listen-onlyplugin (https://github.com/flowolforg/openclaw-listen-only) on a Telegram group chat.Closes #48434