Skip to content

fix(feishu): send blocks as independent messages when blockStreaming is enabled#94250

Merged
vincentkoc merged 5 commits into
openclaw:mainfrom
xialonglee:fix/issue-55027-feishu-block-streaming
Jul 1, 2026
Merged

fix(feishu): send blocks as independent messages when blockStreaming is enabled#94250
vincentkoc merged 5 commits into
openclaw:mainfrom
xialonglee:fix/issue-55027-feishu-block-streaming

Conversation

@xialonglee

@xialonglee xialonglee commented Jun 17, 2026

Copy link
Copy Markdown
Contributor

Root Cause

In extensions/feishu/src/reply-dispatcher.ts, the block delivery path has only two behaviors:

  1. streaming: true (card mode) — blocks are consumed by a streaming card with character-by-character playback animation.
  2. streaming: false — blocks are silently dropped (return without sending).

There is no path for true progressive/block delivery without card-side animation playback. The blockStreaming config option exists but was not wired to send blocks as independent Feishu messages.

Summary

  • Feishu channel silently drops block payloads when the streaming card path is unavailable, preventing true progressive delivery.
  • This PR adds a new path in the reply dispatcher: when blockStreaming: true and the streaming card path is not active, blocks are sent as independent Feishu messages immediately. Mention targets are preserved on the first block so @mentioned users receive a notification. A sentBlockText accumulator tracks what was sent to avoid duplicate final messages.
  • Fixes Feishu channel cannot do true block streaming; block chunks are dropped unless streaming card is enabled #55027

Verification

  • pnpm test extensions/feishu/src/reply-dispatcher.test.ts — 83 passed (78 existing + 5 new block-streaming tests)
  • pnpm build — OK
  • Autoreview: clean
  • CI: pending

Real behavior proof

Behavior addressed: Feishu channel blocks delivered as independent messages when blockStreaming: true and streaming card is inactive (streaming: false).

Real environment tested: Linux 4.19.112, Node 24.13.1, commit 428b4754, branch fix/issue-55027-feishu-block-streaming, real Feishu bot credentials, live gateway with WebSocket connection.

Methodology: Temporary console.error("[PROOF]") probes were added at key decision points in reply-dispatcher.ts (dispatch init, block arrival, block send, sentBlockText accumulation) to surface the internal dispatch routing in gateway logs. These probes are for evidence collection only and are not included in the committed code.

Exact steps or command run after this patch:

# Build and start gateway
pnpm build && pnpm openclaw gateway restart
# Verify gateway running + Feishu WS connected
pnpm openclaw gateway status
# Send messages from real Feishu user to trigger dispatch; monitor with:
tail -f /tmp/openclaw/openclaw-2026-06-30.log | grep PROOF

Evidence after fix:

Test 1 — Card mode (streaming=true): blocks go to streaming card (no regression)

[PROOF] feishu dispatch init: blockStreaming=true streaming=true renderMode=auto
[PROOF] feishu block arrived: kind=block text=收到。你想测试什么问题?描述一下具体场景,我来配合。 useStreamingCard=true coreBlockStreaming=true

Screenshots:
[Card mode — block consumed by streaming card]
image

Test 2 — Independent message mode (streaming=false): blocks sent as independent Feishu messages (new path)

[PROOF] feishu dispatch init: blockStreaming=true streaming=false renderMode=auto
[PROOF] feishu block arrived: kind=block text=好的,我来配合测试。你想测试什么样的阻塞场景?给我具体的指令或场景描述就行。 useStreamingCard=false coreBlockStreaming=true
[PROOF] feishu block-streaming: sending block as independent message (text=好的,我来配合测试。你想测试什么样的阻塞场景?给我具体的指令或场景描述就行。)
[PROOF] feishu block-streaming: sentBlockText accumulated (len=38)

Screenshots:
[Independent message — block sent as standalone Feishu message]
image

Observed result after fix:

  • Card mode: block consumed by streaming card (useStreamingCard=true). No regression — existing card rendering path works as before.
  • Independent message mode: block sent as independent Feishu message via sendMessageFeishu. Message received by the real Feishu user. The sentBlockText accumulator correctly tracks sent content (38 chars). Gateway log dispatch complete (queuedFinal=false, replies=0) confirms the block was delivered without a duplicate final.

What was not tested:

  • Multi-block scenario with final deduplication (requires an AI response that produces multiple blocks whose concatenation matches the final text — impractical to engineer deterministically from a chat prompt; covered by unit tests).
  • Feishu group chat block delivery (requires a different chat_id; DM path is the same codepath).

Revision 1 (2026-06-30)

Per ClawSweeper review:

  • Route the independent block-send path through sendChunkedTextReply instead of calling sendMessageFeishu directly, so the configured Feishu textChunkLimit is honored for completed blocks (matching the established final/static fallback path).
  • Preserve mention targets only on the first emitted block chunk (not just the first block).
  • Keep sentBlockText based on the original block text for duplicate-final suppression.
  • Add regression test with textChunkLimit=4 verifying a long block is split into chunked messages and mentions only appear on the first chunk.

Updated verification:

  • pnpm test extensions/feishu/src/reply-dispatcher.test.ts — 84 passed (78 existing + 6 new block-streaming tests)
  • pnpm check:test-types — OK
  • pnpm build — OK
  • Commit: a609e187f1

@openclaw-barnacle openclaw-barnacle Bot added channel: feishu Channel integration: feishu size: S proof: supplied External PR includes structured after-fix real behavior proof. labels Jun 17, 2026
@xialonglee

This comment was marked as outdated.

@clawsweeper

clawsweeper Bot commented Jun 17, 2026

Copy link
Copy Markdown
Contributor

Codex review: needs maintainer review before merge. Reviewed June 30, 2026, 12:08 PM ET / 16:08 UTC.

Summary
The PR updates Feishu reply dispatch and tests so enabled blockStreaming sends completed blocks as chunked independent messages when CardKit streaming is inactive, with duplicate-final suppression and first-block mention preservation.

PR surface: Source +37, Tests +273. Total +310 across 2 files.

Reproducibility: yes. Current main and v2026.6.10 are source-reproducible because the Feishu dispatcher returns from the non-card block path without sending, and the PR body supplies live after-fix Feishu logs/screenshots.

Review metrics: none identified.

Root-cause cluster
Relationship: fixed_by_candidate
Canonical: #55027
Summary: This PR is the narrow candidate fix for the canonical Feishu true completed-block delivery issue; the broader segment-streaming PR overlaps but does not supersede this repair.

Members:

Proposal only: this assessment does not dispatch repair, suppress jobs, mutate sibling items, close, or merge anything.

Merge readiness
Overall: 🦞 diamond lobster
Proof: 🦞 diamond lobster
Patch quality: 🦞 diamond lobster
Result: ready for maintainer review.

Overall follows the weaker of proof and patch quality, so missing proof can cap an otherwise strong patch.

Risk before merge

  • [P1] The PR intentionally changes Feishu blockStreaming: true delivery: when CardKit streaming is inactive, completed blocks now become visible standalone messages instead of being suppressed, so maintainers should accept the ordering and duplicate-final behavior before landing.
  • [P1] One latest-head CI/proof check was still in progress during review; landing should wait for required checks to finish green.

Maintainer options:

  1. Land Narrow Repair After Green Checks (recommended)
    If required checks finish green, maintainers can land this focused dispatcher fix while accepting that enabled Feishu blockStreaming now emits visible independent messages when CardKit streaming is inactive.
  2. Ask For Extra Group-Chat Proof
    If maintainers want more transport confidence, ask for one additional Feishu group-chat run because the supplied live proof demonstrates the DM path and the same dispatcher branch is shared.
  3. Defer To Segment-Mode Direction
    Maintainers can pause this PR in favor of the broader segment-streaming proposal, but that leaves the existing blockStreaming drop path unfixed until larger config work lands.

Next step before merge

  • No automated repair job is needed; maintainer review should focus on accepting the intentional Feishu delivery change and waiting for required checks.

Security
Cleared: The diff only changes Feishu TypeScript dispatcher code and tests; it adds no dependency, workflow, secret handling, package metadata, artifact download, or new code-execution surface.

Review details

Best possible solution:

Land this narrow Feishu dispatcher repair after required checks pass, and keep broader segment-streaming product/config work separate unless maintainers choose that direction.

Do we have a high-confidence way to reproduce the issue?

Yes. Current main and v2026.6.10 are source-reproducible because the Feishu dispatcher returns from the non-card block path without sending, and the PR body supplies live after-fix Feishu logs/screenshots.

Is this the best way to solve the issue?

Yes. The dispatcher is the right owner boundary for this existing channel option, and the latest head reuses established chunked text delivery instead of adding a new config mode; the broader segment-mode PR is not the narrowest fix for this bug.

AGENTS.md: found and applied where relevant.

Codex review notes: model internal, reasoning high; reviewed against 6cb82eaab865.

Label changes

Label justifications:

  • P2: This is a bounded Feishu channel message-delivery bug fix with source-level reproduction, a linked canonical issue, user workaround history, and limited plugin-surface blast radius.
  • merge-risk: 🚨 message-delivery: The diff changes when Feishu block and final messages are sent or suppressed for enabled blockStreaming, which can affect visible chat delivery ordering and duplication.
  • rating: 🦞 diamond lobster: Overall readiness is 🦞 diamond lobster; proof is 🦞 diamond lobster and patch quality is 🦞 diamond lobster.
  • status: 👀 ready for maintainer look: ClawSweeper has no concrete contributor-facing blocker left for this PR. Sufficient (logs): The PR body includes live Feishu gateway logs and screenshots from a real bot setup showing CardKit mode still works and streaming: false blockStreaming now delivers an independent Feishu message.
  • proof: sufficient: Contributor real behavior proof is sufficient. The PR body includes live Feishu gateway logs and screenshots from a real bot setup showing CardKit mode still works and streaming: false blockStreaming now delivers an independent Feishu message.
Evidence reviewed

PR surface:

Source +37, Tests +273. Total +310 across 2 files.

View PR surface stats
Area Files Added Removed Net
Source 1 39 2 +37
Tests 1 273 0 +273
Docs 0 0 0 0
Config 0 0 0 0
Generated 0 0 0 0
Other 0 0 0 0
Total 2 312 2 +310

What I checked:

Likely related people:

  • vincentkoc: Recent Feishu history includes native mention delivery on the same sendMessageFeishu and reply-dispatcher path this PR extends. (role: recent Feishu dispatcher/send contributor; confidence: high; commits: e46bcb834faa; files: extensions/feishu/src/reply-dispatcher.ts, extensions/feishu/src/reply-dispatcher.test.ts, extensions/feishu/src/send.ts)
  • steipete: Recent Feishu commits preserved replies before error finals and maintained fallback/thread delivery behavior adjacent to this block/final suppression path. (role: adjacent reply-delivery contributor; confidence: high; commits: 7cca3d4618b9, f9c0dc2d2b9e, 05eda57b3c72; files: extensions/feishu/src/reply-dispatcher.ts, extensions/feishu/src/reply-dispatcher.test.ts)
  • qiangu: Recent streaming-card work made ordinary Feishu replies stream through CardKit in auto mode, which is the sibling behavior this PR intentionally leaves unchanged. (role: Feishu streaming-card contributor; confidence: medium; commits: 3b8ab4e11232; files: extensions/feishu/src/reply-dispatcher.ts, extensions/feishu/src/reply-dispatcher.test.ts, extensions/feishu/src/streaming-card.ts)
  • xinhuagu: The earlier Feishu safety change disabled block streaming to prevent silent reply drops, which is the historical behavior this PR narrows by adding a real non-card block delivery path. (role: prior silent-drop safeguard contributor; confidence: high; commits: 024af2b738d1; files: extensions/feishu/src/reply-dispatcher.ts, extensions/feishu/src/reply-dispatcher.test.ts)
What the crustacean ranks mean
  • 🦀 challenger crab: rare, exceptional readiness with strong proof, clean implementation, and convincing validation.
  • 🦞 diamond lobster: very strong readiness with only minor maintainer review expected.
  • 🐚 platinum hermit: good normal PR, likely mergeable with ordinary maintainer review.
  • 🦐 gold shrimp: useful signal, but proof or patch confidence is still limited.
  • 🦪 silver shellfish: thin signal; proof, validation, or implementation needs work.
  • 🧂 unranked krab: not merge-ready because proof is missing/unusable or there are serious correctness or safety concerns.
  • 🌊 off-meta tidepool: rating does not apply to this item.

Shiny media proof means a screenshot, video, or linked artifact directly shows the changed behavior. Runtime, network, CSP, and security claims still need visible diagnostics.

How this review workflow works
  • ClawSweeper keeps one durable marker-backed review comment per issue or PR.
  • Re-runs edit this comment so the latest verdict, findings, and automation markers stay together instead of adding duplicate bot comments.
  • A fresh review can be triggered by eligible @clawsweeper re-review comments, exact-item GitHub events, scheduled/background review runs, or manual workflow dispatch.
  • PR/issue authors and users with repository write access can comment @clawsweeper re-review or @clawsweeper re-run on an open PR or issue to request a fresh review only.
  • Maintainers can also comment @clawsweeper review to request a fresh review only.
  • Fresh-review commands do not start repair, autofix, rebase, CI repair, or automerge.
  • Maintainer-only repair and merge flows require explicit commands such as @clawsweeper autofix, @clawsweeper automerge, @clawsweeper fix ci, or @clawsweeper address review.
  • Maintainers can comment @clawsweeper explain to ask for more context, or @clawsweeper stop to stop active automation.

@clawsweeper clawsweeper Bot added rating: 🧂 unranked krab Not merge-ready due to missing proof or serious correctness/safety concerns. status: 📣 needs proof The PR needs real behavior proof before ClawSweeper can clear the contributor ask. P2 Normal backlog priority with limited blast radius. merge-risk: 🚨 message-delivery 🚨 May drop, duplicate, misroute, suppress, or wrongly target messages. labels Jun 18, 2026
@bxy3045134656

Copy link
Copy Markdown

I have a real Feishu tenant setup and would be happy to help test this PR. I'm running OpenClaw 2026.6.8 with feishu plugin 2026.6.8, and the character-by-character streaming issue (#55027) affects my daily usage. If someone can point me to how to apply this patch on my instance, I can provide redacted screenshots of the block streaming behavior. Happy to help unblock this.

@xialonglee

Copy link
Copy Markdown
Contributor Author

Hi @bxy3045134656, thanks for offering to help test! Really appreciate it.

The PR contains a single commit (6412454) modifying only 2 files in extensions/feishu/src/. Here are a few ways to apply it:

Option 1: gh pr checkout (easiest, if you have gh + source clone)

gh pr checkout 94250
pnpm install && pnpm build

Option 2: Fetch from fork

git remote add xialonglee https://github.com/xialonglee/openclaw.git
git fetch xialonglee fix/issue-55027-feishu-block-streaming
git checkout -b test-feishu-fix xialonglee/fix/issue-55027-feishu-block-streaming
pnpm install && pnpm build

Option 3: Download patch file

curl -L https://github.com/openclaw/openclaw/pull/94250.patch | git am
pnpm install && pnpm build

Option 4: Claude Code (AI-assisted)

If you have Claude Code, you can point it at the PR URL and ask it to analyze and apply the changes. It understands the diff semantics and can be more resilient to version differences:

claude -p "Apply the changes from PR https://github.com/openclaw/openclaw/pull/94250 to fix Feishu block streaming"

Important notes

  • Conflict risk is very low — the PR only touches extensions/feishu/src/reply-dispatcher.ts (+23/-2 lines source, +104 lines tests), which is isolated from core runtime.
  • You need to build from source — Feishu is a bundled plugin, so the fix must be compiled. After building, you can test with your Feishu tenant and this config:
    {
      "channels": {
        "feishu": {
          "streaming": false,
          "blockStreaming": true
        }
      }
    }
  • The key behavior to verify: completed blocks appear as separate messages (no character-by-character animation), and the final reply is not duplicated.

Redacted screenshots of your Feishu chat showing independent block messages would be exactly the real behavior proof needed to unblock this PR. Thanks again!

@clawsweeper clawsweeper Bot added rating: 🦪 silver shellfish Thin PR readiness signal; proof, validation, or implementation needs work. proof: sufficient ClawSweeper judged the real behavior proof convincing. rating: 🦐 gold shrimp Decent PR readiness signal, but merge confidence is limited. status: ⏳ waiting on author ClawSweeper has contributor-facing work open and is waiting for author action. and removed rating: 🧂 unranked krab Not merge-ready due to missing proof or serious correctness/safety concerns. rating: 🦪 silver shellfish Thin PR readiness signal; proof, validation, or implementation needs work. status: 📣 needs proof The PR needs real behavior proof before ClawSweeper can clear the contributor ask. labels Jun 26, 2026
Route the independent block-send path through sendChunkedTextReply
instead of calling sendMessageFeishu directly, so the configured
Feishu textChunkLimit is honored for completed blocks. Preserve
mention targets only on the first emitted block chunk and keep
sentBlockText based on the original block text for duplicate-final
suppression.

Add regression test with a small textChunkLimit that verifies a
long block is split into chunked messages and mentions only appear
on the first chunk.
@clawsweeper clawsweeper Bot added rating: 🐚 platinum hermit Good normal PR readiness with ordinary maintainer review expected. status: 👀 ready for maintainer look ClawSweeper has no concrete contributor-facing blocker left for this PR. and removed rating: 🦐 gold shrimp Decent PR readiness signal, but merge confidence is limited. status: ⏳ waiting on author ClawSweeper has contributor-facing work open and is waiting for author action. labels Jun 30, 2026
@clawsweeper clawsweeper Bot added rating: 🦞 diamond lobster Very strong PR readiness with only minor maintainer review expected. and removed rating: 🐚 platinum hermit Good normal PR readiness with ordinary maintainer review expected. labels Jun 30, 2026
@vincentkoc vincentkoc self-assigned this Jul 1, 2026
@vincentkoc
vincentkoc merged commit 9fabdcf into openclaw:main Jul 1, 2026
97 of 99 checks passed
@vincentkoc

Copy link
Copy Markdown
Member

Merged via squash.

@xialonglee
xialonglee deleted the fix/issue-55027-feishu-block-streaming branch July 1, 2026 16:25
github-actions Bot pushed a commit to Desicool/openclaw that referenced this pull request Jul 2, 2026
…is enabled (openclaw#94250)

* fix(feishu): send blocks as independent messages when blockStreaming is enabled

* fix(feishu): preserve mention targets on first independently sent block

* fix(feishu): route independent block sends through chunked text delivery

Route the independent block-send path through sendChunkedTextReply
instead of calling sendMessageFeishu directly, so the configured
Feishu textChunkLimit is honored for completed blocks. Preserve
mention targets only on the first emitted block chunk and keep
sentBlockText based on the original block text for duplicate-final
suppression.

Add regression test with a small textChunkLimit that verifies a
long block is split into chunked messages and mentions only appear
on the first chunk.

* fix(feishu): preserve block delivery target

---------

Co-authored-by: Vincent Koc <vincentkoc@ieee.org>
chenyangjun-xy pushed a commit to chenyangjun-xy/openclaw that referenced this pull request Jul 3, 2026
…is enabled (openclaw#94250)

* fix(feishu): send blocks as independent messages when blockStreaming is enabled

* fix(feishu): preserve mention targets on first independently sent block

* fix(feishu): route independent block sends through chunked text delivery

Route the independent block-send path through sendChunkedTextReply
instead of calling sendMessageFeishu directly, so the configured
Feishu textChunkLimit is honored for completed blocks. Preserve
mention targets only on the first emitted block chunk and keep
sentBlockText based on the original block text for duplicate-final
suppression.

Add regression test with a small textChunkLimit that verifies a
long block is split into chunked messages and mentions only appear
on the first chunk.

* fix(feishu): preserve block delivery target

---------

Co-authored-by: Vincent Koc <vincentkoc@ieee.org>
sheyanmin pushed a commit to sheyanmin/openclaw that referenced this pull request Jul 8, 2026
…is enabled (openclaw#94250)

* fix(feishu): send blocks as independent messages when blockStreaming is enabled

* fix(feishu): preserve mention targets on first independently sent block

* fix(feishu): route independent block sends through chunked text delivery

Route the independent block-send path through sendChunkedTextReply
instead of calling sendMessageFeishu directly, so the configured
Feishu textChunkLimit is honored for completed blocks. Preserve
mention targets only on the first emitted block chunk and keep
sentBlockText based on the original block text for duplicate-final
suppression.

Add regression test with a small textChunkLimit that verifies a
long block is split into chunked messages and mentions only appear
on the first chunk.

* fix(feishu): preserve block delivery target

---------

Co-authored-by: Vincent Koc <vincentkoc@ieee.org>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

channel: feishu Channel integration: feishu merge-risk: 🚨 message-delivery 🚨 May drop, duplicate, misroute, suppress, or wrongly target messages. P2 Normal backlog priority with limited blast radius. proof: sufficient ClawSweeper judged the real behavior proof convincing. proof: supplied External PR includes structured after-fix real behavior proof. rating: 🦞 diamond lobster Very strong PR readiness with only minor maintainer review expected. size: S status: 👀 ready for maintainer look ClawSweeper has no concrete contributor-facing blocker left for this PR.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Feishu channel cannot do true block streaming; block chunks are dropped unless streaming card is enabled

3 participants