Skip to content

fix(feishu): message handling improvements for cross-type edit, chunk resilience, and post fallback - #18371

Open
highland0971 wants to merge 4 commits into
NousResearch:mainfrom
highland0971:fix-feishu-message-handling
Open

fix(feishu): message handling improvements for cross-type edit, chunk resilience, and post fallback#18371
highland0971 wants to merge 4 commits into
NousResearch:mainfrom
highland0971:fix-feishu-message-handling

Conversation

@highland0971

Copy link
Copy Markdown

Summary

Fixes three Feishu-specific message handling issues:

Problem 1: Cross-type edit content loss

  • Issue: Feishu API rejects cross-type edits (post→text or text→post), causing edit failures
  • Fix: Track sent message types and preserve original type during edit operations

Problem 2: Fenced code block truncation with chunk indicators

  • Issue: Chunk indicators like (1/3) appended to closing fence lines break fence detection
  • Fix: Strip chunk indicators from fence lines and emit them as standalone prose rows

Problem 3: Chunk send failure losing subsequent chunks

  • Issue: Single chunk failure aborts entire multi-chunk message delivery
  • Fix: Per-chunk error handling with continue - failures logged but remaining chunks still sent

Problem 4: Post payload rejection losing formatting

  • Issue: When post payload rejected, _strip_markdown_to_plain_text removes code block content entirely
  • Fix: New _strip_unsafe_markdown() function preserves code as indented text, keeping readability

Changes

  • gateway/platforms/feishu.py:
    • Added _sent_msg_types dict to track message types for edit preservation
    • Added _strip_unsafe_markdown() for lightweight fallback formatting
    • Added _CHUNK_INDICATOR_RE to handle truncate_message indicators
    • Modified send() with per-chunk continue error handling
    • Modified edit_message() to preserve original msg_type
    • Modified _build_markdown_post_rows() to strip indicators from fence lines

Testing

All fixes verified with E2E tests:

  • Code block protection: multi-block code preserved
  • Fence boundary: text and lists display correctly
  • Long message chunking: 9000+ chars delivered completely
  • Cross-type edit: delete+resend fallback works

root and others added 4 commits April 30, 2026 14:11
Fixes Feishu bot replies creating new topics instead of staying inside
the existing topic thread in topic-mode groups (话题模式群).

Root causes and fixes:

A. Inbound: root_id → thread_id fallback
   - _process_inbound_message now uses root_id as fallback when
     thread_id is absent (common in topic-mode groups)
   - Also adds root_id to reply_to_message_id fallback chain

B. Outbound: create path uses thread_id delivery
   - _send_raw_message now distinguishes om_ (message ID) vs omt_
     (topic ID) prefixes for thread_id
   - om_ IDs: use as reply_to via reply API (most reliable)
   - omt_ IDs: use receive_id_type='thread_id' via create API
   - Falls back to chat_id when no thread_id present

C. Gateway: propagate reply_to to all mid-turn messages
   - GatewayStreamConsumer gains initial_reply_to_id parameter
   - First stream send, fallback final, and commentary all use it
   - run.py passes event_message_id for Feishu topic threads
   - send_progress_messages and status callbacks include reply_to

D. Defense: reply failure fallback stays in thread
   - _feishu_send_with_retry: when reply fails (230011/231003),
     if thread_id is om_ ID, retry reply to it instead of create
   - Prevents spawning new topics when original message is withdrawn

Combines insights from 10 community PRs (NousResearch#9760, NousResearch#9986, NousResearch#10929, NousResearch#16018,
NousResearch#16131, NousResearch#16442, NousResearch#16603, NousResearch#16620, NousResearch#17877, NousResearch#17895) that each addressed
only part of the problem.
Four fixes for Feishu message delivery issues:

1. **Cross-type edit preservation**: Track msg_type per message_id in
   _sent_msg_types cache so edit_message can preserve the original type
   (post→text edits are rejected by Feishu API).

2. **Fence boundary handling**: _build_markdown_post_rows now correctly
   handles chunk indicators like (1/3) appended to closing fence lines
   by truncate_message. Strip indicator from fence line and emit as
   standalone prose row after code block.

3. **Chunk-level resilience**: send() now uses per-chunk try-except so
   a single chunk failure does not discard remaining chunks. Each chunk
   is sent independently; any success returns partial success.

4. **Lightweight fallback formatting**: New _strip_unsafe_markdown()
   removes only Feishu-unsafe patterns while preserving readability
   (lists, indentation) instead of full markdown stripping.

Telegram send() also updated with chunk-level resilience using the
same per-chunk error handling pattern.

Tests: 189 feishu + 309 telegram tests passing.

@teknium1 teknium1 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for identifying several real Feishu delivery edge cases. Current main still has the reported behaviors, but the patch needs focused salvage into the migrated adapter.

Problems

  • gateway/platforms/feishu.py:1804-1805 returns _finalize_send_result(last_response) after any earlier success. If a later chunk fails, this returns failure despite the new any_success branch.
  • gateway/platforms/feishu.py:1851-1854 falls back from a rejected post update to text; that reintroduces the post→text edit that the PR correctly identifies as rejected by Feishu.
  • Current main moved the active adapter to plugins/platforms/feishu/adapter.py in 5600105478ffde29d7566b45421b100eaa29c4ef, so the legacy path in this PR is no longer the live implementation.

Suggested changes

  • Preserve a successful chunk response separately, or return a clear partial-delivery result.
  • Make the edit fallback retain the original message type, or deliberately delete and resend.
  • Port the focused changes to plugins/platforms/feishu/adapter.py and add regression coverage in tests/gateway/test_feishu.py.

Automated hermes-sweeper review.

# should not discard the rest of the message.

if any_success:
return self._finalize_send_result(last_response, "send failed")

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

any_success can be true while last_response is a failed final chunk, so this returns failure after a partial delivery. Preserve a successful response separately or return an explicit partial-delivery result.

logger.warning("[Feishu] Invalid post update payload rejected by API; falling back to lightweight text")
fallback_text = _strip_unsafe_markdown(content)
fallback_body = self._build_update_message_body(
msg_type="text",

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This fallback changes a rejected post update to text, which is the cross-type edit this patch is intended to avoid. Keep the original type here or use a deliberate delete-and-resend fallback.

@teknium1 teknium1 added sweeper:risk-message-delivery Sweeper risk: may drop, duplicate, misroute, or suppress messages sweeper:blast-moderate Sweeper blast radius: moderate — a subsystem or single platform labels Jul 12, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

comp/gateway Gateway runner, session dispatch, delivery P2 Medium — degraded but workaround exists platform/feishu Feishu / Lark adapter sweeper:blast-moderate Sweeper blast radius: moderate — a subsystem or single platform sweeper:risk-message-delivery Sweeper risk: may drop, duplicate, misroute, or suppress messages type/bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants