openclaw - 💡(How to fix) Fix Plugin SDK: expose a supported full-fidelity inbound redispatch capability for before_dispatch claim-then-rerun [1 comments, 2 participants]

Official PRs (…)
ON THIS PAGE

Recommended Tools

×6

Utilities matched from this issue’s tags and category — try them while you read without losing context.

GitHub issue graph ai analysis

Paste a GitHub issue URL. We fetch that issue, discover linked issues from bodies/comments/timeline, collect linked pull requests, and produce a structured English report.

The report is written in English Markdown for sharing and archival.

Helpful · Quick feedback

Loading…
GitHub stats
openclaw/openclaw#77147Fetched 2026-05-05 05:51:39
View on GitHub
Comments
1
Participants
2
Timeline
1
Reactions
2
Author
Timeline (top)
commented ×1

Root Cause

So arbitrary inbound headers are not forwarded into before_dispatch — a plan that depends on event.headers['x-klawbi-redispatch'] surviving into the projected hook surface is unsafe. (My implementation initially assumed this worked, and the integration tests passed only because synthetic events were constructed with headers: {...} directly; in production the field was always undefined and the idempotency guard never fired.)

Fix Action

Fix / Workaround

Build a gateway extension that hooks before_dispatch to detect a specific class of inbound message (direct-addressed bot messages — "Hey Bot, ..." patterns) and, on a hit, claim the event (returning { handled: true }) to take ownership of the substantive-reply guarantee from normal-dispatch's classifier. After acknowledging the user synchronously ("Got it — working on it."), the extension would re-enter the dispatch pipeline programmatically with the original event (full fidelity: body, sessionKey, conversationId, accountId, sender, channel-context, reply-context, quote, media — everything the original inbound carried) so the agent can produce the substantive reply, while a plugin-local idempotency guard prevents the re-entry from looping back through the same hook.

Concretely: when a user directly addresses the bot, my extension should be able to ack within ~1-2s and then, asynchronously and with full inbound fidelity, redispatch the same logical turn through the normal agent pipeline (with reply-gate's per-message silencing logic bypassed for that one turn).

Internal seams that exist:

  • dispatchInboundMessage(...)dist/dispatch-qvEfkoBF.js
  • dispatchInboundDirectDmWithRuntime(...)dist/direct-dm-JAvoJntw.js (lines 6–62)
  • dispatchReplyWithBufferedBlockDispatcher(...) and dispatchReplyWithDispatcher(...)dist/reply-dispatch-runtime-jxTu7uhs.js
  • runtime.channel.reply.finalizeInboundContext() — invoked internally inside direct-dm-JAvoJntw.js:21

Code Example

{
  content, body, channel, sessionKey, senderId, isGroup, timestamp
}

---

{
  channelId, accountId, conversationId, sessionKey, senderId
}

---

api.runtime.channel.turn.redispatch(event, {
  bypass: { replyGate: true },
  marker: 'klawbi-direct-address-contract:abc123', // becomes available to before_dispatch hooks as ctx.redispatchMarker
})Promise<{ replied: boolean; message_id: string | null; latency_ms: number }>
RAW_BUFFERClick to expand / collapse

What I'm trying to do

Build a gateway extension that hooks before_dispatch to detect a specific class of inbound message (direct-addressed bot messages — "Hey Bot, ..." patterns) and, on a hit, claim the event (returning { handled: true }) to take ownership of the substantive-reply guarantee from normal-dispatch's classifier. After acknowledging the user synchronously ("Got it — working on it."), the extension would re-enter the dispatch pipeline programmatically with the original event (full fidelity: body, sessionKey, conversationId, accountId, sender, channel-context, reply-context, quote, media — everything the original inbound carried) so the agent can produce the substantive reply, while a plugin-local idempotency guard prevents the re-entry from looping back through the same hook.

Concretely: when a user directly addresses the bot, my extension should be able to ack within ~1-2s and then, asynchronously and with full inbound fidelity, redispatch the same logical turn through the normal agent pipeline (with reply-gate's per-message silencing logic bypassed for that one turn).

What I found in the bundled runtime

I verified against the OpenClaw 2026.5.2 runtime at /docker/openclaw-hvex/data/.openclaw/plugin-runtime-deps/openclaw-2026.5.2-c27ae31043c7/dist/:

Internal seams that exist:

  • dispatchInboundMessage(...)dist/dispatch-qvEfkoBF.js
  • dispatchInboundDirectDmWithRuntime(...)dist/direct-dm-JAvoJntw.js (lines 6–62)
  • dispatchReplyWithBufferedBlockDispatcher(...) and dispatchReplyWithDispatcher(...)dist/reply-dispatch-runtime-jxTu7uhs.js
  • runtime.channel.reply.finalizeInboundContext() — invoked internally inside direct-dm-JAvoJntw.js:21

These functions DO preserve full inbound fidelity (body, session key, account id, addresses, generic extraContext for quote/media). Internally, OpenClaw can re-enter the dispatch pipeline with full fidelity.

The gap I'm hitting: Per dist/api-builder-DWoxKaSf.js, buildPluginApi() does NOT expose any of the above to plugins via api.runtime.channel.turn/reply/dispatch/.... There is no plugin-SDK surface for "redispatch this event through the full pipeline." The only ownership primitive plugins receive is ctx.claim() (via before_dispatch), which is a drop / silence mechanism — there is no symmetric "re-enter" path.

Header forwarding is also not a solution: The before_dispatch hook receives a projected event (per dist/dispatch-qvEfkoBF.js:615-625):

{
  content, body, channel, sessionKey, senderId, isGroup, timestamp
}

with ctx:

{
  channelId, accountId, conversationId, sessionKey, senderId
}

So arbitrary inbound headers are not forwarded into before_dispatch — a plan that depends on event.headers['x-klawbi-redispatch'] surviving into the projected hook surface is unsafe. (My implementation initially assumed this worked, and the integration tests passed only because synthetic events were constructed with headers: {...} directly; in production the field was always undefined and the idempotency guard never fired.)

Workarounds we considered and rejected:

  1. Calling the internal dispatchInbound... functions via runtime introspection — depends on undocumented internals; brittle to npm bumps; explicitly out of scope per our coding standards.
  2. Modifying lib/reply-gate-rules.js to honor a context flag for forced-reply — couples the reply-gate to the contract layer; cross-extension entanglement.
  3. Re-implementing the dispatch pipeline as a near-copy in our extension — exactly the architectural anti-pattern your reviewer model flagged ("don't invent a second near-copy of the normal inbound path").

What I'd like to request

A supported plugin-callable redispatch capability with these properties:

  1. Full-fidelity inbound preservation — accepts the same context shape that the gateway's own re-entry primitives accept (body / channel / accountId / sessionKey / conversationId / senderId / surface / timestamp / extraContext for reply-context, quote, media).
  2. Loop-safe semantics — invoked re-entry surfaces a marker (header, ctx flag, or capability) that downstream before_dispatch hooks can observe to skip their own logic on the redispatched turn (idempotency).
  3. Reply-gate bypass option — either built-in (the redispatch path skips reply-gate by design) or expressed via a flag honored by the gate, so a plugin that has already decided "this turn merits a reply" doesn't get its turn silenced by the classifier.
  4. Trust gating — gate the capability behind a manifest declaration or origin: bundled|trusted check (analogous to how channel-listener registration is currently gated in dist/channel-plugin-ids-B_qWBF4F.js:96-152). Untrusted workspace plugins should NOT have access.
  5. Synchronous result surface — return { replied: bool, message_id: string | null } so the calling plugin can update an audit row and emit alerts on broken-promise paths. (Async-with-callback also works; sync is simpler.)

A draft surface might look like:

api.runtime.channel.turn.redispatch(event, {
  bypass: { replyGate: true },
  marker: 'klawbi-direct-address-contract:abc123', // becomes available to before_dispatch hooks as ctx.redispatchMarker
})Promise<{ replied: boolean; message_id: string | null; latency_ms: number }>

The capability would be opt-in (manifest flag like "capabilities": ["channel.turn.redispatch"]) and gated to bundled / explicitly-trusted plugins.

Concrete contract canon for our case

Our use case is the "direct-address contract" — a user-facing guarantee that any message addressed directly to the bot receives a substantive reply, not silence. Without this redispatch capability, we can ship the substrate (detector + ack + audit log) but can't actually own the contract; the substantive reply still depends on the normal classifier's silencing logic, which is exactly the seam our contract was designed to take ownership AWAY from.

This isn't unique to our project — any plugin that wants to layer "claim-then-rerun" semantics over the normal inbound path needs this. The closest analog is HTTP middleware that can both claim a request AND request fresh dispatch through the rest of the chain.

Summary statement (for cross-referencing)

"OpenClaw core has full-fidelity inbound re-entry internally, but the plugin SDK does not expose a supported redispatch capability."

If a non-redispatch path is the right design (e.g., a plugin-callable agent-invocation endpoint that doesn't go through the dispatch pipeline at all, with the channel-send happening via existing relay/route-message), I'm happy to redesign around that. But the core constraint is the same: I need a supported way for a plugin to invoke the full reply path with full inbound fidelity, without depending on internal dist functions.

Happy to provide a minimal reproducer plugin if helpful.


Filed by midsneo (lane3 work, 2026-05-04). Linked work-in-progress: mids-neo/klawbi-infra#70 (substrate-only ship; this issue gates the contract-layer follow-on).

extent analysis

TL;DR

The most likely fix is to request a supported plugin-callable redispatch capability in the OpenClaw plugin SDK, allowing plugins to re-enter the dispatch pipeline with full inbound fidelity.

Guidance

  • Review the OpenClaw plugin SDK documentation to confirm the absence of a redispatch capability.
  • Consider filing a feature request for the proposed api.runtime.channel.turn.redispatch method, including the required properties for full-fidelity inbound preservation, loop-safe semantics, reply-gate bypass option, trust gating, and synchronous result surface.
  • Evaluate alternative design approaches, such as a plugin-callable agent-invocation endpoint, that could achieve the desired functionality without relying on internal dist functions.
  • Develop a minimal reproducer plugin to demonstrate the need for the redispatch capability and facilitate testing of potential solutions.

Example

A potential implementation of the redispatch method could look like:

api.runtime.channel.turn.redispatch(event, {
  bypass: { replyGate: true },
  marker: 'klawbi-direct-address-contract:abc123', // becomes available to before_dispatch hooks as ctx.redispatchMarker
})Promise<{ replied: boolean; message_id: string | null; latency_ms: number }>

However, this is speculative and would require confirmation from the OpenClaw development team.

Notes

The proposed solution relies on the OpenClaw development team implementing the requested redispatch capability. In the absence of this capability, alternative design approaches may be necessary.

Recommendation

Apply for the proposed api.runtime.channel.turn.redispatch method to be added to the OpenClaw plugin SDK, as it would provide a supported and efficient way to achieve the desired functionality.

Vote matrix · Quick signals

Works
Did the solution work? Tap to confirm.
Easy Fix
Was it a quick fix?
Time Saver
Did it save you time?
Blocking
Was it severely blocking?
Common Issue
Are others likely hitting this too?
Flaky / Intermittent
Is it intermittent?
Verified / Reproducible
Can you reproduce it reliably?
Loading…

Still need to ship something?

×6

Another batch ranked right after the header list — different links, same matching logic.

Back to top recommendations

TRENDING

openclaw - 💡(How to fix) Fix Plugin SDK: expose a supported full-fidelity inbound redispatch capability for before_dispatch claim-then-rerun [1 comments, 2 participants]