fix(feishu): route thread replies via Lark reply endpoint

Bot responses to messages inside Lark topic threads were dropped
outside the thread because outbound Send always used the new-message
endpoint. This change:

- Adds LarkClient.ReplyMessage() that POSTs to
  /open-apis/im/v1/messages/{id}/reply with reply_in_thread=true
- Parses thread_id from im.message.receive_v1 events (distinct from
  root_id which fires on any quote reply) and stamps
  feishu_reply_target_id into the message metadata
- Propagates the key through cmd/gateway_consumer_normal.go and the
  new package-level routingMetaKeys var in internal/channels/events.go
  so block replies and retry notifications also land in thread
- Routes sendText, sendMarkdownCard, sendImage, sendFile, and
  sendMediaAttachment via a new deliverMessage helper that falls back
  to SendMessage with a warning log on reply endpoint errors (e.g.
  thread root deleted)
- Adds 10 unit tests covering routing, fallback, content
  double-encoding, and the thread_id gate that prevents plain quote
  replies from being silently promoted to threads

Closes #818
This commit is contained in:
viettranx committed 2026-04-11 21:22:23 +07:00
1 parent e8e00d1884
commit bf272d8dbf
13 files changed
+504 -35

No files matched your search

+9
View File
@@ -367,6 +367,15 @@ When enabled, each thread gets an isolated session:
- Different threads within the same group maintain separate conversation histories
- Disabled by default
### Thread Reply Routing
When a message is sent inside a Lark thread (detected via the `thread_id` field in the inbound event), the inbound handler stamps `metadata["feishu_reply_target_id"]` with the triggering message ID. During outbound delivery, the channel routes responses back to the same thread using `LarkClient.ReplyMessage()` which POSTs to `/open-apis/im/v1/messages/{message_id}/reply` with `reply_in_thread: true`.
- **Automatic thread detection**: No configuration needed; replies are routed based on inbound `thread_id`
- **Metadata propagation**: The `feishu_reply_target_id` key is included in the `routingMetaKeys` allowlist so replies, block replies, and placeholder updates all land in the correct thread
- **Graceful fallback**: If the reply endpoint fails (e.g., thread root deleted), the channel falls back to `SendMessage()` for the regular chat
- **Applies to**: Text, card, image, and file messages
---
## 7. Discord
+7
View File
@@ -32,6 +32,13 @@ All notable changes to GoClaw Gateway are documented here. Format follows [Keep
## [Unreleased]
### Fixed
#### Feishu/Lark Thread Reply Routing — Issue #818 (2026-04-11)
- **Thread detection**: Inbound messages with `thread_id` now properly route responses back to the same Feishu thread via `/open-apis/im/v1/messages/{id}/reply` endpoint
- **Metadata propagation**: New `feishu_reply_target_id` metadata key added to `routingMetaKeys` allowlist so all outbound messages (text, cards, files, reactions) land in the correct thread
- **Graceful fallback**: If thread root is deleted, channel falls back to regular `SendMessage()` for robustness
### Testing
#### Test Coverage Improvement — Wave 1-3 (2026-04-11)