From b474e7b0bf0862acb35925a72a8909f6923e6883 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9D=A8=E6=88=90=E4=BC=9F?= Date: Wed, 15 Jul 2026 20:42:31 +0800 Subject: [PATCH] fix(lark): preserve image message content Co-authored-by: multica-agent --- .../integrations/lark/feishu_channel_test.go | 17 +++++++++++++++++ .../integrations/lark/ws_frame_decoder.go | 16 ++++++++++------ .../integrations/lark/ws_frame_decoder_test.go | 6 +++--- 3 files changed, 30 insertions(+), 9 deletions(-) diff --git a/server/internal/integrations/lark/feishu_channel_test.go b/server/internal/integrations/lark/feishu_channel_test.go index 6bb1317ad56..1d9c8db0ab9 100644 --- a/server/internal/integrations/lark/feishu_channel_test.go +++ b/server/internal/integrations/lark/feishu_channel_test.go @@ -189,6 +189,23 @@ func TestChannelMessageFromLark_NormalizesAndStashesRaw(t *testing.T) { } } +func TestChannelMessageFromLark_ImageKeepsPlaceholder(t *testing.T) { + cm := channelMessageFromLark(InboundMessage{ + MessageID: "om_image", + ChatID: "oc", + ChatType: ChatTypeP2P, + Body: "[Image]", + MessageType: "image", + }) + + if cm.Type != channel.MsgTypeImage { + t.Fatalf("image must normalize to image, got %q", cm.Type) + } + if cm.Text != "[Image]" { + t.Fatalf("image placeholder lost during normalization: %q", cm.Text) + } +} + func TestChannelMsgType(t *testing.T) { cases := map[string]channel.MsgType{ "": channel.MsgTypeText, diff --git a/server/internal/integrations/lark/ws_frame_decoder.go b/server/internal/integrations/lark/ws_frame_decoder.go index f09dc2b3417..3da9229a7e3 100644 --- a/server/internal/integrations/lark/ws_frame_decoder.go +++ b/server/internal/integrations/lark/ws_frame_decoder.go @@ -91,16 +91,20 @@ func (d *LarkJSONFrameDecoder) Decode(payload []byte, inst Installation) (Inboun botUnionID = inst.BotUnionID.String } - // text + post are flattened synchronously here (no external calls — - // the decoder must stay fast and dependency-free). merge_forward - // leaves Body empty: it needs an HTTP round-trip to expand and is - // handled downstream by the enricher, which keys off MessageType. - // Other types (image, file, …) also leave Body empty in this MVP; - // attachment ingestion is a separate issue. + // All directly renderable types are flattened synchronously here (no + // external calls — the decoder must stay fast and dependency-free). + // Media messages become stable placeholders such as [Image], so an + // image-only event never lands in chat history as an empty message. + // merge_forward alone leaves Body empty: it needs an HTTP round-trip to + // expand and is handled downstream by the enricher. switch evt.Message.MessageType { case "text", "post": msg.Body = resolveMentions(flattenContent(evt.Message.MessageType, evt.Message.Content), evt.Message.Mentions, inst.BotOpenID, botUnionID) + case "merge_forward": + // Expanded by InboundEnricher. + default: + msg.Body = flattenContent(evt.Message.MessageType, evt.Message.Content) } // Snapshot the user's own text as the command source BEFORE any diff --git a/server/internal/integrations/lark/ws_frame_decoder_test.go b/server/internal/integrations/lark/ws_frame_decoder_test.go index dcc51e54aae..669d32eb8cc 100644 --- a/server/internal/integrations/lark/ws_frame_decoder_test.go +++ b/server/internal/integrations/lark/ws_frame_decoder_test.go @@ -429,7 +429,7 @@ func TestLarkJSONFrameDecoderMessageContentEmptyOnInvalidContentJSON(t *testing. } } -func TestLarkJSONFrameDecoderNonTextMessageHasEmptyBody(t *testing.T) { +func TestLarkJSONFrameDecoderImageMessageHasPlaceholderBody(t *testing.T) { t.Parallel() raw := []byte(`{ "type":"event_callback", @@ -443,8 +443,8 @@ func TestLarkJSONFrameDecoderNonTextMessageHasEmptyBody(t *testing.T) { if err != nil || !ok { t.Fatalf("ok=%v err=%v", ok, err) } - if msg.Body != "" { - t.Errorf("Body = %q; non-text messages should have empty body in MVP", msg.Body) + if msg.Body != "[Image]" { + t.Errorf("Body = %q; want [Image]", msg.Body) } if msg.MessageID == "" { t.Error("MessageID should still be populated for non-text events")