From fe7037e4adcffbade8678fcb52e279b0d428175e Mon Sep 17 00:00:00 2001 From: oudecheng <13802883547@139.com> Date: Thu, 6 Aug 2026 10:08:36 +0800 Subject: [PATCH] =?UTF-8?q?fix(feishu,channel):=20=E4=BF=AE=E6=AD=A3=20rea?= =?UTF-8?q?ction=20=E8=AF=AD=E4=B9=89=E5=B9=B6=E5=A2=9E=E5=AE=B9=20bus=20?= =?UTF-8?q?=E9=98=9F=E5=88=97=EF=BC=8C=E9=81=BF=E5=85=8D=E4=B8=A2=E6=9C=80?= =?UTF-8?q?=E5=90=8E=E4=B8=80=E6=9D=A1=E6=B6=88=E6=81=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - feishu.rs: 仅终态消息(AssistantResponse | ErrorNotification)成功发送 后才移除 reaction,ToolCall 等中间过程不再触碰;发送失败/空内容 保留 reaction 作为异常信号,解决『reaction 被提前移除掩盖最终响应 丢失』的语义错位问题 - manager.rs: MessageBus 容量从 100 扩至 256,降低多 topic 并发场景下 bus 级 try_send 的瞬时积压丢弃概率 与前一提交的 outbound_dispatcher 双队列优先级重构配合:AssistantResponse / ErrorNotification 走独立 high 队列必达,终态消息送达后才移除 reaction, 彻底解决『飞书经常丢最后一条消息且 reaction 状态误导』的问题。 --- src/channels/feishu.rs | 45 ++++++++++++++++++++++++++++++++++------- src/channels/manager.rs | 2 +- 2 files changed, 39 insertions(+), 8 deletions(-) diff --git a/src/channels/feishu.rs b/src/channels/feishu.rs index 1132f2b..c311ae3 100644 --- a/src/channels/feishu.rs +++ b/src/channels/feishu.rs @@ -2504,6 +2504,15 @@ impl Channel for FeishuChannel { "open_id" }; + // reaction 语义:仅"终态消息"成功发送后才移除 reaction。 + // 终态 = AssistantResponse(最终响应)或 ErrorNotification(agent 异常终止的错误通知)。 + // ToolCall 等中间过程事件不触碰 reaction——让 reaction 真实反映用户是否已收到 + // agent 的最终产出(reaction 持续 = 尚未收到响应或错误;reaction 消失 = 已收到)。 + // 发送失败时保留 reaction 作为异常信号;空内容不视为送达,保留 reaction。 + let is_terminal = matches!( + msg.event_kind, + OutboundEventKind::AssistantResponse | OutboundEventKind::ErrorNotification + ); let remove_reaction = async { self.remove_reaction_from_metadata(&msg.metadata).await; }; @@ -2514,7 +2523,14 @@ impl Channel for FeishuChannel { // Empty content if content.is_empty() { - remove_reaction.await; + // 空最终响应是异常:保留 reaction 让用户察觉,仅记录 warn。 + // 非最终响应(如 ToolCall 空内容)本来就不触碰 reaction。 + if is_terminal { + tracing::warn!( + chat_id = %msg.chat_id, + "Final response has empty content, keeping reaction as anomaly signal" + ); + } return Ok(()); } @@ -2526,7 +2542,10 @@ impl Channel for FeishuChannel { let result = self .dispatch_send(receive_id, receive_id_type, "text", content, reply_to) .await; - remove_reaction.await; + // 仅最终响应且发送成功才移除 reaction + if is_terminal && result.is_ok() { + remove_reaction.await; + } return result; } MsgFormat::Post => { @@ -2535,7 +2554,9 @@ impl Channel for FeishuChannel { let result = self .dispatch_send(receive_id, receive_id_type, "post", &post_body, reply_to) .await; - remove_reaction.await; + if is_terminal && result.is_ok() { + remove_reaction.await; + } return result; } MsgFormat::Interactive => { @@ -2561,11 +2582,17 @@ impl Channel for FeishuChannel { reply_to, ) .await; - remove_reaction.await; + // 回退成功才移除 reaction;回退失败则保留作为异常信号 + if is_terminal && result.is_ok() { + remove_reaction.await; + } return result; } } - remove_reaction.await; + // 所有 chunk 成功:仅最终响应移除 reaction + if is_terminal { + remove_reaction.await; + } return Ok(()); } } @@ -2618,19 +2645,23 @@ impl Channel for FeishuChannel { Ok(()) => sent_media += 1, Err(error) => { tracing::warn!(error = %error, path = %path, media_type = %media_item.media_type, "Failed to send media message to Feishu"); + // 媒体失败:保留 reaction 作为异常信号(不调用 remove_reaction) return Err(error); } } } if msg.content.trim().is_empty() && sent_media == 0 { - remove_reaction.await; + // 无内容无媒体:保留 reaction 作为异常信号 return Err(ChannelError::Other( "No supported media items were sent to Feishu".to_string(), )); } - remove_reaction.await; + // 全部成功:仅最终响应移除 reaction + if is_terminal { + remove_reaction.await; + } Ok(()) } } diff --git a/src/channels/manager.rs b/src/channels/manager.rs index b480b1c..5da4b35 100644 --- a/src/channels/manager.rs +++ b/src/channels/manager.rs @@ -26,7 +26,7 @@ impl ChannelManager { Self { channels: Arc::new(RwLock::new(channels)), - bus: MessageBus::new(100), + bus: MessageBus::new(256), websocket_channel, } }