fix(feishu): prevent streaming card duplication on multi-final replies#42940
fix(feishu): prevent streaming card duplication on multi-final replies#42940NeooChen wants to merge 1 commit intoopenclaw:mainfrom
Conversation
fix(feishu): prevent streaming card duplication on multi-final replies
Greptile SummaryThis PR fixes a streaming card duplication bug in the Feishu integration where N tool calls in one agent turn previously produced N separate streaming cards. The root cause was that each Key points:
Confidence Score: 4/5
Last reviewed commit: 714248b |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 714248b48d
ℹ️ 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".
| // onIdle will close the card when the entire turn is done. | ||
| // Use "snapshot" mode so mergeStreamingText deduplicates against | ||
| // content already delivered via onPartialReply. | ||
| queueStreamingUpdate(text, { mode: "snapshot" }); |
There was a problem hiding this comment.
Keep streaming close in the awaited delivery path
Replacing await closeStreaming() with queueStreamingUpdate(..., { mode: "snapshot" }) moves card finalization from deliver() to onIdle, but the dispatcher infrastructure does not await onIdle callbacks before considering replies idle (withReplyDispatcher only waits dispatcher.waitForIdle(), and createReplyDispatcher invokes onIdle fire-and-forget). In flows that trigger restart/reload right after dispatch settles, the process can proceed while the CardKit close call is still in flight, which can leave Feishu cards stuck in streaming mode ([Generating...]) if shutdown interrupts that request.
Useful? React with 👍 / 👎.
Summary
renderMode: "card"andstreaming: true, eachfinalreply in a single agent turn creates a new streaming card via CardKit API. Turns with N tool calls produce N duplicate cards.deliver(final)no longer callscloseStreaming(). Instead it usesqueueStreamingUpdate(text, { mode: "snapshot" })to merge text into the existing card.onIdlehandles card finalization.Change Type (select all)
Scope (select all touched areas)
Linked Issue/PR
None
User-visible / Behavior Changes
Multi-final turns now produce a single streaming card instead of N duplicate cards. No config changes required.
Security Impact (required)
NoNoNo(fewer calls — eliminates redundant CardKit create calls)NoNoRepro + Verification
Environment
renderMode: "card",streaming: true,cardkit:card:writepermission grantedSteps
renderMode: "card"andstreaming: trueExpected
Actual
Started streaming→Closed streamingrepeated N times per turn.Evidence
Gateway log before fix:
Gateway log after fix:
Human Verification (required)
mode: "delta"which caused content duplication inside the card; fixed by switching tomode: "snapshot"for proper dedup viamergeStreamingText()Review Conversations
Compatibility / Migration
YesNoNoFailure Recovery (if this breaks)
renderMode: "auto"to avoid card streaming path entirely.extensions/feishu/src/reply-dispatcher.tsonIdlenot firing.Risks and Mitigations
onIdlefails to fire, the streaming card stays open indefinitely with "Generating..." state.onErroralso callscloseStreaming()as fallback. This is the same pattern used by block streaming.