Skip to content

feat(plugin-gateway): add RegisterAsDispatchListener - #245

Merged
kodiakhq[bot] merged 1 commit into
mainfrom
feat/plugin-gateway-dispatch-listener
Oct 17, 2026
Merged

kodiakhq[bot] merged 1 commit into
mainfrom
feat/plugin-gateway-dispatch-listener

Conversation

@RedStar071

Copy link
Copy Markdown
Member

🔗 Linked issue

Resolves #244

🧭 Context

With @wolfstar/plugin-broker, the gateway process forwards every dispatch and still emits it to its own listeners, while workers replay the same dispatch on theirs. A bot that loads the same pieces in both processes handles each dispatch twice, and every bot has to work out by itself, usually from environment variables, which listeners to switch off.

📚 Description

Adds RegisterAsDispatchListener to @wolfstar/plugin-gateway. It takes the same arguments as RegisterAsGatewayListener and runs the listener on one process only: the workers while the gateway client forwards its dispatches, the gateway client itself otherwise (so it is harmless on a bot without a broker).

forwardGatewayDispatches marks the client as forwarding through a new optional GatewayDispatchEmitterLike#markDispatchesForwarded, which GatewayClient implements as a ref-counted forwardsDispatches flag undone by the stop function. The decorator reads the flag on every emit rather than at load time: forwarding usually starts after client.load(), and checking per emit also copes with stop(). Its wrapper sits outside the once wrapper, so an idle once listener is not spent.

Departures from the Pullfrog plan:

  • It settled the lazy-enable question without a spike: Listener binds _listener when the piece loads and nothing else gates it, so wrapping _listener is the mechanism (as once already does), not toggling enabled.
  • The event guard is a GatewayOnlyEventName union with a matching runtime set, instead of a test that derives the set from GatewayEventMap. A test checks that the set only holds real events and excludes raw. Whether every other event is reachable from a handler can't be checked mechanically, so that part is left out.
  • EventGatewayListener is not changed to default to this, as the plan recommended; that stays a separate issue.

Known limits, documented in both READMEs: the guildDelete emitted by READY reconciliation is not forwarded (#243), and with events set on forwardGatewayDispatches, a listener of an event built from a dispatch left out runs nowhere. Delivery is at-least-once, so listeners should be idempotent.

Key changes

  • plugin-gateway: RegisterAsDispatchListener, GatewayClient#forwardsDispatches and markDispatchesForwarded(), and the DispatchEventName, GatewayOnlyEventName and gatewayOnlyEvents exports. Gateway-only events (clientReady, shardReady, shardResume, shardClose, shardError, dispatch, cacheError, cacheSweep) are rejected by the types and throw at decoration time.
  • plugin-broker: forwardGatewayDispatches marks the emitter while it forwards and unmarks on stop. Emitters without the method behave as before.
  • Tests: decorator behaviour (standalone, forwarding, forwarding started before load, ref-counting, once, gateway-only events), the broker marking, and an end-to-end gateway/worker test where only the worker's listener runs and the gateway's returns once forwarding stops.
  • READMEs of both packages and two minor changesets.

Type of Change

  • New feature (non-breaking change which adds functionality)
  • Bug fix (non-breaking change which fixes an issue)
  • Breaking change (fix or feature that would cause existing functionality to not work as expected)
  • Documentation change
  • Workflow / CI change

Pre-flight Checklist

  • I have read the Contributing Guidelines (if applicable)
  • My code follows the code style of this project
  • I have added tests that prove my fix is effective or that my feature works
  • New and existing unit tests pass locally with my changes
  • I have updated the documentation accordingly
  • I added a changeset with pnpm changeset (or pnpm changeset add --empty if no release is needed). See .changeset/README.md.

Verification: pnpm lint, pnpm build, pnpm typecheck and pnpm test (121 files, 1708 tests) pass locally.

🤖 AI disclosure: Claude Code was used on this PR. Code written with claude-sonnet-5-5; PR description written with claude-sonnet-5-5. AI policy.

…dispatch listeners

Add a decorator that runs a dispatch listener on the workers while the gateway
client forwards its dispatches, and on the gateway client otherwise.

forwardGatewayDispatches marks the client through the new optional
GatewayDispatchEmitterLike#markDispatchesForwarded (ref-counted, undone by the stop
function). The decorator checks the flag on every emit, so the order forwarding and
the pieces load in does not matter. Gateway-only events are rejected by the types
and throw at decoration time.

Refs #244
@RedStar071 RedStar071 added packages:plugin-gateway Changes in plugin-gateway packages:plugin-broker Changes in plugin-broker labels Oct 17, 2026
@coldtea-pr-lens

coldtea-pr-lens Bot commented Oct 17, 2026 •

Copy link
Copy Markdown

Architecture

Architecture diagram for wolfstar-project/plugins at eadc650

Play the walkthrough


Data flow

Data flow diagram for wolfstar-project/plugins at eadc650

Follow each request


Review

Nothing flagged · reviewed eadc650


Tip

Set github.comment.collapsed: true in .github/pr-lens.yml to fold the comment behind one View architecture and data flow row. Drawing still runs as before

🪧 More tips
  • Run npx skills add coldteadotai/pr-lens, then tell your coding agent: "Diagram the change you just made with PR Lens and attach it to the pull request."
  • Run npx @coldtea/pr-lens-cli analyze --base origin/main on a branch, then npx @coldtea/pr-lens-cli render .pr-lens/graph.json. Same lenses, your own model key, before the pull request exists
  • Click the link under each diagram to open it on a canvas you can zoom, pan and step through
  • The diagrams are links. Click one to open it on the canvas, then press W or click play to walk through the change
  • Open a diagram on the canvas, then press W or click play to walk through the change one step at a time
  • The CLI's render reads .github/pr-lens.yml and applies your renames, exclusions and lane pins at draw time
  • Set github.draw: on-demand in .github/pr-lens.yml and PR Lens stops drawing pull requests on its own. Comment @pr-lens draw on a pull request when you want that one drawn
  • Add .github/workflows/pr-lens.yml with coldteadotai/pr-lens/packages/action@v0 and your model provider's key as its api-key to run PR Lens from your own CI. Any /chat/completions endpoint works
  • After a push, tick Redraw to draw the latest commit
  • Switch GitHub to dark mode and the diagrams follow. The moving dots are this pull request's data in motion

Thanks for using PR Lens! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

@pullfrog pullfrog Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

✅ No new issues found.

Reviewed changes

Reviewed the full diff of the single commit, plus the surrounding RegisterAsGatewayListener, EventGatewayListener (once wrapper) and the GatewayClient emit sites.

  • RegisterAsDispatchListener — wraps _listener outside the once wrapper and reads emitter.forwardsDispatches on every emit, so load order doesn't matter and an idle once listener isn't spent.
  • GatewayClient#forwardsDispatches / markDispatchesForwarded() — ref-counted flag with an idempotent undo function.
  • forwardGatewayDispatches — marks the emitter through the optional markDispatchesForwarded and unmarks in the stop function; emitters without it behave as before.
  • GatewayOnlyEventName / DispatchEventName / gatewayOnlyEvents — the gateway-only set matches the non-dispatch emit sites in GatewayClient (shardResume, shardClose, shardError, cacheError, clientReady, dispatch, plus shardReady/cacheSweep), and raw is correctly left out since replayDispatch emits it.
  • Tests, READMEs, changesets — decorator behaviour, broker marking, and an end-to-end gateway/worker test; limits (#243, events filtering, at-least-once) are documented.

Pullfrog  | View workflow run | Using claude-sonnet-5-5 | 𝕏

@kodiakhq
kodiakhq Bot merged commit 7257223 into main Oct 17, 2026
19 checks passed
@kodiakhq
kodiakhq Bot deleted the feat/plugin-gateway-dispatch-listener branch October 17, 2026 09:41
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

packages:plugin-broker Changes in plugin-broker packages:plugin-gateway Changes in plugin-gateway

Projects

None yet

Development

Successfully merging this pull request may close these issues.

request: a decorator that runs a dispatch listener on one process only when the broker forwards

1 participant