fix(feishu): prevent duplicate message after streaming card close (#67791)#68491
Conversation
Greptile SummaryThis PR fixes a duplicate-message bug in the Feishu reply dispatcher where Confidence Score: 5/5Safe to merge — the fix is logically correct, minimal, and consistent with how All findings are P2. The fix correctly captures No files require special attention. Prompt To Fix All With AIThis is a comment left during a code review.
Path: extensions/feishu/src/reply-dispatcher.ts
Line: 317-319
Comment:
**Missing regression test for the fixed race sequence**
The fix correctly adds `streamText` to `deliveredFinalTexts` in `closeStreaming()`, but there is no new test covering the specific sequence: `onPartialReply` → `onIdle` (closes streaming) → `deliver({…}, { kind: "final" })`. The existing tests cover duplicate `final` deliveries in back-to-back calls, but not the `onIdle`-before-`final` timing described in the PR description. Adding a test that mimics this sequence would guard against the regression recurring without the dedup logic breaking in subtler ways.
How can I resolve this? If you propose a fix, please make it concise.Reviews (1): Last reviewed commit: "fix(feishu): prevent duplicate message a..." | Re-trigger Greptile |
016834a to
4c58143
Compare
…enclaw#67791) When onIdle closed the streaming card before the final delivery arrived, the streamed text was not tracked in deliveredFinalTexts. The subsequent final payload bypassed the streaming?.isActive() guard (already closed) and fell through to the non-streaming path, sending the same content as a redundant text/card message. Track raw streamText in deliveredFinalTexts when closeStreaming finalizes the card so the duplicate-final check catches it.
4c58143 to
a20dfdc
Compare
…enclaw#67791) (openclaw#68491) * fix(feishu): prevent duplicate message after streaming card close (openclaw#67791) When onIdle closed the streaming card before the final delivery arrived, the streamed text was not tracked in deliveredFinalTexts. The subsequent final payload bypassed the streaming?.isActive() guard (already closed) and fell through to the non-streaming path, sending the same content as a redundant text/card message. Track raw streamText in deliveredFinalTexts when closeStreaming finalizes the card so the duplicate-final check catches it. * test(feishu): cover idle streaming final dedupe --------- Co-authored-by: Vincent Koc <vincentkoc@ieee.org>
…enclaw#67791) (openclaw#68491) * fix(feishu): prevent duplicate message after streaming card close (openclaw#67791) When onIdle closed the streaming card before the final delivery arrived, the streamed text was not tracked in deliveredFinalTexts. The subsequent final payload bypassed the streaming?.isActive() guard (already closed) and fell through to the non-streaming path, sending the same content as a redundant text/card message. Track raw streamText in deliveredFinalTexts when closeStreaming finalizes the card so the duplicate-final check catches it. * test(feishu): cover idle streaming final dedupe --------- Co-authored-by: Vincent Koc <vincentkoc@ieee.org>
…enclaw#67791) (openclaw#68491) * fix(feishu): prevent duplicate message after streaming card close (openclaw#67791) When onIdle closed the streaming card before the final delivery arrived, the streamed text was not tracked in deliveredFinalTexts. The subsequent final payload bypassed the streaming?.isActive() guard (already closed) and fell through to the non-streaming path, sending the same content as a redundant text/card message. Track raw streamText in deliveredFinalTexts when closeStreaming finalizes the card so the duplicate-final check catches it. * test(feishu): cover idle streaming final dedupe --------- Co-authored-by: Vincent Koc <vincentkoc@ieee.org>
…enclaw#67791) (openclaw#68491) * fix(feishu): prevent duplicate message after streaming card close (openclaw#67791) When onIdle closed the streaming card before the final delivery arrived, the streamed text was not tracked in deliveredFinalTexts. The subsequent final payload bypassed the streaming?.isActive() guard (already closed) and fell through to the non-streaming path, sending the same content as a redundant text/card message. Track raw streamText in deliveredFinalTexts when closeStreaming finalizes the card so the duplicate-final check catches it. * test(feishu): cover idle streaming final dedupe --------- Co-authored-by: Vincent Koc <vincentkoc@ieee.org>
…enclaw#67791) (openclaw#68491) * fix(feishu): prevent duplicate message after streaming card close (openclaw#67791) When onIdle closed the streaming card before the final delivery arrived, the streamed text was not tracked in deliveredFinalTexts. The subsequent final payload bypassed the streaming?.isActive() guard (already closed) and fell through to the non-streaming path, sending the same content as a redundant text/card message. Track raw streamText in deliveredFinalTexts when closeStreaming finalizes the card so the duplicate-final check catches it. * test(feishu): cover idle streaming final dedupe --------- Co-authored-by: Vincent Koc <vincentkoc@ieee.org>
…enclaw#67791) (openclaw#68491) * fix(feishu): prevent duplicate message after streaming card close (openclaw#67791) When onIdle closed the streaming card before the final delivery arrived, the streamed text was not tracked in deliveredFinalTexts. The subsequent final payload bypassed the streaming?.isActive() guard (already closed) and fell through to the non-streaming path, sending the same content as a redundant text/card message. Track raw streamText in deliveredFinalTexts when closeStreaming finalizes the card so the duplicate-final check catches it. * test(feishu): cover idle streaming final dedupe --------- Co-authored-by: Vincent Koc <vincentkoc@ieee.org>
…enclaw#67791) (openclaw#68491) * fix(feishu): prevent duplicate message after streaming card close (openclaw#67791) When onIdle closed the streaming card before the final delivery arrived, the streamed text was not tracked in deliveredFinalTexts. The subsequent final payload bypassed the streaming?.isActive() guard (already closed) and fell through to the non-streaming path, sending the same content as a redundant text/card message. Track raw streamText in deliveredFinalTexts when closeStreaming finalizes the card so the duplicate-final check catches it. * test(feishu): cover idle streaming final dedupe --------- Co-authored-by: Vincent Koc <vincentkoc@ieee.org>
Summary
Feishu sends a duplicate text/card message after streaming completes because the
onIdle-triggeredcloseStreaming()does not record the streamed content indeliveredFinalTexts. The subsequentfinaldelivery bypasses the inactive streaming guard and sends the same content again.Root Cause
When
onIdlefires before thefinaldelivery arrives:closeStreaming()finalizes the streaming card with the accumulated textstreamingis set tonullfinaldelivery arrives, butstreaming?.isActive()returnsfalsedeliveredFinalTextsdoes not contain the streamed text (only added at line 419 inside theisActive()guard), so the duplicate check passesChanges
extensions/feishu/src/reply-dispatcher.ts: AddstreamTexttodeliveredFinalTextsincloseStreaming()before clearing state, so the duplicate-final check indeliver()can skip the redundant text delivery.Test
All 30 reply-dispatcher tests pass (
pnpm test extensions/feishu/src/reply-dispatcher.test.ts). 2 pre-existing failures in other Feishu test files (media upload mock, webhook ECONNRESET) are unrelated.Closes #67791