fix(slack): flush draft stream before final reply to preserve messag ordering#20248
fix(slack): flush draft stream before final reply to preserve messag ordering#20248Utkarshbhimte wants to merge 4 commits intoopenclaw:mainfrom
Conversation
… ordering When the agent generates multiple text segments with tool calls between them, the draft stream (partial reply preview) accumulates throttled updates independently of the reply dispatcher. Without this fix, the final reply can be delivered before pending draft stream text has flushed, causing intermediate/thinking messages to appear AFTER the result in Slack threads. This change flushes and stops the draft stream before delivering final reply payloads, ensuring all preview text is sent before the conclusive response.
|
You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard. |
| // Flush any pending draft stream text before delivering the final reply. | ||
| // Without this, throttled partial-reply text can appear AFTER the final | ||
| // message because the draft stream loop is independent of the dispatcher | ||
| // delivery chain. | ||
| if (kind === "final" && hasStreamedMessage) { | ||
| await draftStream.flush(); | ||
| draftStream.stop(); | ||
| hasStreamedMessage = false; | ||
| } |
There was a problem hiding this comment.
Flush becomes a no-op on preview-edit fallback path
When canFinalizeViaPreviewEdit is true (line 242), draftStream?.stop() is called at line 243 before the chat.update attempt. If that chat.update fails (catch at line 252), execution falls through to this new block. At that point, stopped is already true and loop.pendingText was cleared by loop.stop(), so flush() here becomes a no-op — any throttled partial-reply text that was pending when stop() was called at line 243 is silently lost.
This is a pre-existing issue (the preview-edit path already called stop() without flush()), but this new block won't rescue that edge case as one might expect from reading the code. Consider calling flush() before stop() in the canFinalizeViaPreviewEdit branch as well:
| // Flush any pending draft stream text before delivering the final reply. | |
| // Without this, throttled partial-reply text can appear AFTER the final | |
| // message because the draft stream loop is independent of the dispatcher | |
| // delivery chain. | |
| if (kind === "final" && hasStreamedMessage) { | |
| await draftStream.flush(); | |
| draftStream.stop(); | |
| hasStreamedMessage = false; | |
| } | |
| // Flush any pending draft stream text before delivering the final reply. | |
| // Without this, throttled partial-reply text can appear AFTER the final | |
| // message because the draft stream loop is independent of the dispatcher | |
| // delivery chain. | |
| if (kind === "final" && hasStreamedMessage) { | |
| await draftStream.flush(); | |
| draftStream.stop(); | |
| hasStreamedMessage = false; | |
| } |
Prompt To Fix With AI
This is a comment left during a code review.
Path: src/slack/monitor/message-handler/dispatch.ts
Line: 277:285
Comment:
**Flush becomes a no-op on preview-edit fallback path**
When `canFinalizeViaPreviewEdit` is true (line 242), `draftStream?.stop()` is called at line 243 before the `chat.update` attempt. If that `chat.update` fails (catch at line 252), execution falls through to this new block. At that point, `stopped` is already `true` and `loop.pendingText` was cleared by `loop.stop()`, so `flush()` here becomes a no-op — any throttled partial-reply text that was pending when `stop()` was called at line 243 is silently lost.
This is a pre-existing issue (the preview-edit path already called `stop()` without `flush()`), but this new block won't rescue that edge case as one might expect from reading the code. Consider calling `flush()` before `stop()` in the `canFinalizeViaPreviewEdit` branch as well:
```suggestion
// Flush any pending draft stream text before delivering the final reply.
// Without this, throttled partial-reply text can appear AFTER the final
// message because the draft stream loop is independent of the dispatcher
// delivery chain.
if (kind === "final" && hasStreamedMessage) {
await draftStream.flush();
draftStream.stop();
hasStreamedMessage = false;
}
```
How can I resolve this? If you propose a fix, please make it concise.Address review comment: when canFinalizeViaPreviewEdit is true, the previous code called stop() without flush(), silently dropping any throttled partial-reply text. Now flush() is called before stop() to ensure all pending text is delivered.
|
This pull request has been automatically marked as stale due to inactivity. |
Summary
• Problem: When streaming is enabled, the draft stream loop runs independently of the dispatcher delivery chain. On final reply, throttled partial-reply text can flush after the final message, causing messages to appear out of order.
• Why it matters: Users see the final answer first, then get a dump of intermediate "thinking" text after — confusing and unprofessional.
• What changed: Added a draftStream.flush() + stop() call before delivering the final reply payload, ensuring all pending streamed text is sent before the final message.
• What did NOT change: Non-streaming delivery path unchanged. No changes to block reply behavior or partial reply throttling logic.
Change Type
• [x] Bug fix
Scope
• Slack monitor (dispatch.ts)
Greptile Summary
This PR fixes a message ordering bug in Slack's non-streaming draft stream delivery path. When the final reply is dispatched, throttled partial-reply text from the independent draft stream loop could flush after the final message, causing confusing out-of-order delivery. The fix adds
draftStream.flush()+stop()before delivering the final reply payload whenkind === "final".deliverRepliescall ensures all pending partial text is sent first.{ kind }destructuring with a default value is safe but unnecessary —createReplyDispatcherWithTypingalways passes{ kind }to the deliver callback.canFinalizeViaPreviewEditfallback path wherestop()is called (line 243) beforeflush()would run, making the new flush block a no-op in that scenario. This is pre-existing behavior but worth noting.Confidence Score: 4/5
src/slack/monitor/message-handler/dispatch.ts.Last reviewed commit: eb8cdc9