fix(discord): use per-channel message queues to restore parallel agent dispatch#31927
Conversation
The native streaming path (chatStream) and preview final edit path (chat.update) send raw Markdown text without converting to Slack mrkdwn format. This causes **bold** to appear as literal asterisks instead of rendered bold text. Apply markdownToSlackMrkdwn() in streaming.ts (start/append/stop) and in dispatch.ts (preview final edit via chat.update) to match the non-streaming delivery path behavior. Closes openclaw#31892
…t dispatch Replace the single per-account messageQueue Promise chain in DiscordMessageListener with per-channel queues. This restores parallel processing for channel-bound agents that regressed in 2026.3.1. Messages within the same channel remain serialized to preserve ordering, while messages to different channels now proceed independently. Completed queue entries are cleaned up to prevent memory accumulation. Closes openclaw#31530
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 8629b05a5f
ℹ️ About Codex in GitHub
Your team has set up Codex to 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 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| .then(() => { | ||
| if (this.channelQueues.get(channelId) === next) { | ||
| this.channelQueues.delete(channelId); | ||
| } | ||
| }) |
There was a problem hiding this comment.
Clean up channel queue entries on rejection paths
channelQueues entries are deleted only in the success-only then branch, so if next rejects (for example, if logging/slow-log code throws), that channel key is never removed unless another message later arrives for the same channel. In long-lived processes handling many channels, this can accumulate stale queue promises and channel IDs over time; use a finally-style cleanup so the Map entry is cleared for both resolve and reject outcomes when it is still the current promise.
Useful? React with 👍 / 👎.
Greptile SummarySuccessfully fixes the Discord message serialization bottleneck by replacing the single per-account message queue with per-channel queues. Messages within the same channel remain serialized to preserve ordering, while messages to different channels can now process in parallel as intended. The implementation includes:
The cleanup logic correctly handles the case where new messages arrive before previous ones complete - only the final promise in each channel's queue deletes the map entry. Confidence Score: 5/5
Last reviewed commit: 8629b05 |
|
Landed via temp rebase onto main.
Thanks @Sid-Qin! |
Summary
messageQueuePromise chain inDiscordMessageListenerwith per-channel queues (Map<channelId, Promise>). Messages within the same channel remain serialized to preserve ordering, while messages to different channels proceed independently.maxConcurrentbehavior. No Discord API interaction changes.Change Type (select all)
Scope (select all touched areas)
Linked Issue/PR
User-visible / Behavior Changes
Security Impact (required)
NoNoNoNoNoRepro + Verification
Environment
Steps
Expected
Agent B starts processing its message while Agent A is still replying to its own.
Actual (before fix)
Agent B waits for Agent A to fully complete before beginning, even though they are on different channels.
Evidence
Root cause:
DiscordMessageListenerused a singlemessageQueuePromise chain per account. All incoming messages — regardless of channel — were serialized through this single chain. This created head-of-line blocking across unrelated channels.Before (single queue):
After (per-channel queues):
Tests: 4/4 pass — including a new test that verifies different channels run in parallel while same-channel messages remain serialized.
Human Verification (required)
Compatibility / Migration
YesNoNoFailure Recovery (if this breaks)
Risks and Mitigations
maxConcurrentalready gates actual agent execution; the change only affects the preflight/routing phase.