Skip to content

fix(feishu): 修复飞书话题群中AI回复不跟帖的问题及open/proactive模式@mention检查 - #31384

Closed
yuanchenglu wants to merge 1 commit into
NousResearch:mainfrom
yuanchenglu:fix/feishu-thread-reply-and-policy
Closed

fix(feishu): 修复飞书话题群中AI回复不跟帖的问题及open/proactive模式@mention检查#31384
yuanchenglu wants to merge 1 commit into
NousResearch:mainfrom
yuanchenglu:fix/feishu-thread-reply-and-policy

Conversation

@yuanchenglu

Copy link
Copy Markdown

问题背景

在飞书话题群中,用户在一个已有话题里发送消息后,Hermes Agent 在处理过程中会发送多条回复消息(如状态更新、中间进度等)。但之前只有最终结果能正确跟随在原话题下方,过程中的中间回复会变成与原话题平级的新话题(新帖子),导致话题群中出现大量零散的小话题,用户体验极差。

根因分析

问题1:_prepare_reply_context 中 reply_to 为空

当用户在话题群中发送一条消息,该消息带有 thread_id(标识它属于某个话题),但飞书 API 返回的消息结构中不一定包含 parent_idupper_message_idroot_id。原来的代码:

reply_to_message_id = (
    getattr(message, "parent_id", None)
    or getattr(message, "upper_message_id", None)
    or getattr(message, "root_id", None)
    or None
)

当这三个字段都为空时,reply_to_message_idNone。Gateway 在发送回复时,如果 reply_to 为空,飞书 API 会将其当做新话题创建,而不是跟帖回复。

问题2:_check_group_policy 中 open/proactive 模式仍需 @mention

_check_group_policy 方法中,原来直接检查 require_mention 标志:

if require_mention and not self._mentions_self(message):
    return "group_policy_rejected"

但在 open/proactive 政策下,管理员意图是所有来自白名单用户的消息都应该被处理,不需要 @mention。原逻辑没有考虑群策略的覆盖。

修复方案

修复1:_prepare_reply_context(~2980行)

当消息有 thread_id 但缺少 parent_id/upper_message_id/root_id 时,使用消息自身的 message_id 作为 reply_to_message_id

if thread_id and not parent_id and not upper_message_id and not root_id:
    reply_to_message_id = message_id
else:
    reply_to_message_id = parent_id or upper_message_id or root_id or None

这样 fallback 到 message_id 后,飞书 API 知道该回复属于原话题线程。

修复2:_check_group_policy(~3979行)+ 新增 _get_effective_policy()

新增 _get_effective_policy() 方法获取群聊的有效政策,然后在 @mention 检查前先判断政策类型:

def _get_effective_policy(self, chat_id: str) -> str:
    """获取群聊的有效政策。"""
    rule = self._group_rules.get(chat_id) if chat_id else None
    if rule:
        return rule.policy
    return self._default_group_policy or self._group_policy

Open/proactive 政策下跳过 @mention 检查:

if not require_mention or self._get_effective_policy(chat_id) in ("open", "proactive"):
    return None
if not self._mentions_self(message):
    return "group_policy_rejected"

修改范围

仅修改 gateway/platforms/feishu.py,+21 / -7 行,无其他文件变动。

验证

  • 在飞书话题群中发送消息测试:所有 AI 回复(包括中间状态消息)均正确跟帖在原话题下方,不再产生独立小话题
  • open/proactive 政策群聊中:白名单用户无需 @mention 即可触发 AI 响应

## 问题背景

在飞书话题群中,用户在一个已有话题里发送消息后,Hermes Agent 在处理过程中会
发送多条回复消息(如状态更新、中间进度等)。但之前的表现是:只有最终结果能
正确跟随在原话题下方,过程中的中间回复会变成与原话题平级的新话题(新帖子),
导致话题群中出现大量零散的小话题,用户体验极差。

## 根因分析

### 问题1:_prepare_reply_context 中 reply_to 为空

当用户在话题群中发送一条消息,该消息带有 thread_id(标识它属于某个话题),但
飞书 API 返回的消息结构中不一定包含 parent_id、upper_message_id 或 root_id。
原来的代码:

    reply_to_message_id = (
        getattr(message, "parent_id", None)
        or getattr(message, "upper_message_id", None)
        or getattr(message, "root_id", None)
        or None
    )

当这三个字段都为空时,reply_to_message_id 为 None。Gateway 在发送回复时,
如果 reply_to 为空,飞书 API 会将其当做新话题创建,而不是跟帖回复。这就是
为什么中间回复会变成独立小话题的原因。

### 问题2:_check_group_policy 中 open/proactive 模式仍需 @mention

在 _check_group_policy 方法中,原来直接检查 require_mention 标志:

    if require_mention and not self._mentions_self(message):
        return "group_policy_rejected"

但在 open/proactive 政策下,管理员意图是所有来自白名单用户的消息都应该被处理,
不需要 @mention。原逻辑没有考虑群策略的覆盖。

## 修复方案

### 修复1:_prepare_reply_context(~2980行)

当消息有 thread_id 但缺少 parent_id/upper_message_id/root_id 时,
使用消息自身的 message_id 作为 reply_to_message_id:

    thread_id = getattr(message, "thread_id", None) or getattr(message, "root_id", None) or None
    parent_id = getattr(message, "parent_id", None)
    upper_message_id = getattr(message, "upper_message_id", None)
    root_id = getattr(message, "root_id", None)

    # 在话题群中,如果消息有 thread_id 但没有 parent/upper/root,
    # 用消息自身 message_id 作为 reply_to,确保回复归属原话题
    if thread_id and not parent_id and not upper_message_id and not root_id:
        reply_to_message_id = message_id
    else:
        reply_to_message_id = parent_id or upper_message_id or root_id or None

这样 fallback 到 message_id 后,飞书 API 知道该回复属于原话题线程。

### 修复2:_check_group_policy(~3979行)

新增 _get_effective_policy() 方法获取群聊的有效政策,然后在 @mention 检查前
先判断政策类型:

    # Open/proactive 模式:跳过 @mention 检查
    if not require_mention or self._get_effective_policy(chat_id) in ("open", "proactive"):
        return None
    if not self._mentions_self(message):
        return "group_policy_rejected"

### 新增方法:_get_effective_policy()

    def _get_effective_policy(self, chat_id: str) -> str:
        """获取群聊的有效政策。"""
        rule = self._group_rules.get(chat_id) if chat_id else None
        if rule:
            return rule.policy
        return self._default_group_policy or self._group_policy

该方法返回指定 chat 的有效政策字符串,优先使用群级别规则,其次为默认政策。

## 修改范围

仅修改 gateway/platforms/feishu.py,+21 / -7 行,无其他文件变动。

## 验证

- 在飞书话题群中发送消息测试:所有 AI 回复(包括中间状态消息)均正确跟帖在
  原话题下方,不再产生独立小话题
- open/proactive 政策群聊中:白名单用户无需 @mention 即可触发 AI 响应
@alt-glitch alt-glitch added type/bug Something isn't working P2 Medium — degraded but workaround exists comp/gateway Gateway runner, session dispatch, delivery platform/feishu Feishu / Lark adapter labels May 24, 2026
@alt-glitch

Copy link
Copy Markdown
Collaborator

Related prior work on Feishu topic thread replies: #11433 (thread_id as reply target when reply_to empty), #16018 (superseded by #18121), #18121 (comprehensive topic thread fix, closed), #23729/#24290 (keep group replies in group chats). The _prepare_reply_context fallback to message_id overlaps with #11433's approach. The _check_group_policy open/proactive fix appears to be a new contribution not covered by prior PRs.

@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 two concrete Feishu workflows.

Problems

  • The open policy bypass at gateway/platforms/feishu.py:3979 changes an intentional contract: current docs require an @mention in every group-policy mode (website/docs/user-guide/messaging/feishu.md:238), while require_mention: false is the supported opt-out (:240). Current coverage deliberately asserts this for open (tests/gateway/test_feishu.py:920).
  • The topic-routing portion is already covered on current main: gateway/run.py:17921-17931 carries the triggering message id through all metadata-only Feishu paths, and plugins/platforms/feishu/adapter.py:4609-4634 routes replies or thread-targeted creates inside the topic. tests/gateway/test_run_progress_topics.py:451-491 covers progress routing.

Suggested changes

  • Re-scope any remaining report to the current plugin adapter and use the existing per-group require_mention: false setting instead of coupling mention behavior to open.

Automated hermes-sweeper review.

return "group_policy_rejected"
if require_mention and not self._mentions_self(message):
# Open/proactive mode: accept all messages from allowed users without @mention
if not require_mention or self._get_effective_policy(chat_id) in ("open", "proactive"):

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.

open is intentionally independent of mention gating: current docs require a mention in all group-policy modes (website/docs/user-guide/messaging/feishu.md:238), and current tests enforce that for open (tests/gateway/test_feishu.py:920). Use the existing global/per-chat require_mention: false override instead; current coverage is at tests/gateway/test_feishu_bot_admission.py:463-479.

@yuanchenglu

Copy link
Copy Markdown
Author

@teknium1 Thanks for the thorough review — appreciate the detailed analysis on both points.

Topic routing (change 1): Understood that how the triggering message_id is now carried through Feishu metadata-only paths on main (run.py:17921-17931), and the plugin adapter routes thread replies (adapter.py:4609-4634). Withdrawing this change as it is already covered.

Open/proactive @mention bypass (change 2): You are correct — coupling mention behavior to the policy mode is the wrong approach. The existing per-group require_mention: false setting is the proper way to opt out of @mention requirements. I misinterpreted the relationship between policy and mention gating. Retracting this change.

Since both changes in this PR are either already addressed on main or follow an incorrect approach, I will close this PR. Thanks again for the clear guidance.

@teknium1 teknium1 added sweeper:risk-message-delivery Sweeper risk: may drop, duplicate, misroute, or suppress messages sweeper:risk-security-boundary Sweeper risk: may affect sandboxing, auth, credentials, or sensitive data sweeper:risk-compatibility Sweeper risk: may break existing users, config, migrations, defaults, or upgrades sweeper:blast-moderate Sweeper blast radius: moderate — a subsystem or single platform labels Jul 13, 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-compatibility Sweeper risk: may break existing users, config, migrations, defaults, or upgrades sweeper:risk-message-delivery Sweeper risk: may drop, duplicate, misroute, or suppress messages sweeper:risk-security-boundary Sweeper risk: may affect sandboxing, auth, credentials, or sensitive data type/bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants