Skip to content

fix(feishu): support bot-to-bot group mentions - #29662

Open
ohmyskyhigh wants to merge 1 commit into
NousResearch:mainfrom
ohmyskyhigh:fix/feishu-agent-to-agent-group-chat
Open

fix(feishu): support bot-to-bot group mentions#29662
ohmyskyhigh wants to merge 1 commit into
NousResearch:mainfrom
ohmyskyhigh:fix/feishu-agent-to-agent-group-chat

Conversation

@ohmyskyhigh

@ohmyskyhigh ohmyskyhigh commented May 21, 2026

Copy link
Copy Markdown

Summary

Adds the missing Feishu/Lark gateway pieces needed for trusted Hermes bot-to-bot collaboration in group chats:

  • Supports native outbound @mentions from Hermes Feishu messages via FEISHU_MENTION_ALIASES / FEISHU_OUTBOUND_MENTION_ALIASES, converting configured @Name aliases into Feishu post at entities.
  • Keeps native outbound mentions working even when the response contains Markdown-table-shaped text, instead of downgrading to plain text and losing the mention entity.
  • Loads the profile-local .env before send_message constructs a Feishu adapter, so cron/tool-driven Feishu sends see the same mention-alias configuration as the gateway process.
  • Adds FEISHU_DM_POLICY=disabled for agents that should participate only in groups and ignore direct Feishu DMs.
  • Improves bot-event admission diagnostics by logging bot drop reasons at info level.
  • Normalizes Feishu mention IDs when the platform provides a bare ou_... or u_... string without id_type, so strict group mention admission can still recognize the receiving bot.

Problem

Hermes Feishu bots can be placed together in shared collaboration groups, but the existing behavior made agent-to-agent handoff unreliable:

  1. Outbound text such as @MOSS please inspect was sent as literal text, not a native Feishu mention, so the receiving bot was not necessarily notified or admitted by mention-gated group logic.
  2. The fallback to plain text for Markdown-table-shaped content could silently strip the rich-text mention entity that Feishu requires for a real @mention.
  3. send_message calls from non-gateway processes did not necessarily load profile-local Feishu mention aliases before building the adapter.
  4. Some Feishu payloads represent mentions as bare ID strings; those were treated as missing IDs and could cause FEISHU_REQUIRE_MENTION=true / bot-to-bot mention gates to drop otherwise valid messages.
  5. Group-only agents needed a simple way to reject Feishu DMs without disabling their group participation.

Solution

  • Parse configured outbound mention aliases from env and split outbound message rows into Feishu rich-text elements, emitting {"tag": "at", "user_id": ..., "user_name": ...} for matching aliases.
  • Avoid alias replacement inside inline-code spans and require sensible mention boundaries so emails/fragments are not converted accidentally.
  • Prefer rich-text post payloads when an outbound native mention is present; use plain text elements inside that post where needed so table-shaped content does not force a downgrade.
  • Load get_hermes_home() / ".env" in the Feishu send_message path before constructing the adapter.
  • Add FEISHU_DM_POLICY parsing and admission handling (open by default, disabled to reject p2p messages).
  • Normalize mention IDs through _extract_mention_ids() and reuse that logic in _message_mentions_bot() so bare ou_... / u_... mention strings are recognized.

Related issues

Refs #21366 — the bare-ID normalization addresses one concrete Feishu mention-shape case where the gateway mention gate can misclassify a valid bot mention.

Refs #25728 — this does not implement message buffering, but it improves the underlying Feishu group-mention reliability needed for mention-triggered group workflows.

Refs #29245 — this does not fully solve Markdown table rendering, but it prevents table-shaped content from forcing a plain-text downgrade when a native Feishu @mention must be preserved.

Refs #17847 — related multi-bot Feishu safety context; this PR keeps bot sender admission opt-in and improves diagnostics rather than broadly processing all bot-originated events.

Tests

Focused tests run locally:

python -m pytest tests/gateway/test_feishu.py::TestFeishuOutboundMentions tests/gateway/test_feishu_bot_admission.py -q -o 'addopts='

Result:

71 passed, 2 warnings in 1.42s

The covered cases include:

  • outbound alias conversion into Feishu at rich-text entities;
  • native mentions preserved with Markdown-table-shaped content;
  • alias matching does not trigger on email/fragments;
  • aliases inside inline code remain plain text;
  • FEISHU_DM_POLICY parsing, defaulting, and invalid-value warning behavior;
  • p2p rejection when DM policy is disabled while group admission still works;
  • bot mention admission for bare ou_... and u_... mention IDs;
  • bot-to-bot group admission with allow_bots=mentions and strict mention gating.

@daimon-nous daimon-nous Bot added type/feature New feature or request comp/gateway Gateway runner, session dispatch, delivery platform/feishu Feishu / Lark adapter P3 Low — cosmetic, nice to have labels May 21, 2026
@ohmyskyhigh
ohmyskyhigh force-pushed the fix/feishu-agent-to-agent-group-chat branch from dce4c9a to 2127c4f Compare May 21, 2026 08:28

@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 the focused Feishu reliability work. The underlying outbound-mention and bare-ID issues are still present on current main, but this needs a careful port.

Problems

  • The alias regex at gateway/platforms/feishu.py:647 only checks the character after an alias. user@MOSS.com therefore matches @MOSS because . is allowed, despite the stated email-safety goal.
  • The new p2p return at gateway/platforms/feishu.py:4110 must preserve main's current p2p pairing/allowlist checks at plugins/platforms/feishu/adapter.py:4256-4268 when salvaged.
  • The live adapter moved to plugins/platforms/feishu/adapter.py in 5600105478ffde29d7566b45421b100eaa29c4ef.

Suggested changes

  • Port the implementation to the bundled plugin, add a left-boundary email regression, and layer DM disabling ahead of the existing p2p authorization path.

Automated hermes-sweeper review.

Comment thread gateway/platforms/feishu.py Outdated
if not names:
return [{"tag": text_tag, "text": text}]
pattern = re.compile(
r"@(" + "|".join(re.escape(name) for name in names) + r")(?=$|[\s\t\n\r.,;:!?、,。;:!?()\[\]{}<>\"'`])"

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 only checks the trailing boundary. With alias MOSS, user@MOSS.com matches because . is allowed here, so an email address is converted into an at entity despite the email-safety claim. Add a preceding boundary check and a regression test.

@@ -3965,6 +4108,8 @@ def _admit(self, sender: Any, message: Any) -> Optional[RejectReason]:
return "bot_not_mentioned"

if not is_group:

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.

When salvaging this onto current main, keep the existing p2p pairing/allowlist checks after this policy rejection. Current main performs those checks in plugins/platforms/feishu/adapter.py:4256-4268; returning unconditionally here after the disabled check would bypass them.

@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
@ohmyskyhigh
ohmyskyhigh force-pushed the fix/feishu-agent-to-agent-group-chat branch from 2127c4f to b99c6c1 Compare July 14, 2026 15:43
@ohmyskyhigh

Copy link
Copy Markdown
Author

Ported and force-pushed this PR onto current origin/main.

Addressed the sweeper/admin feedback:

  • moved the implementation from the old gateway/platforms/feishu.py path to plugins/platforms/feishu/adapter.py;
  • added a left-boundary email regression (user@MOSS.com) so outbound alias matching does not create a native at inside email/fragments;
  • layered FEISHU_DM_POLICY=disabled ahead of the existing p2p allow-all / pairing / allowlist checks, preserving the current p2p authorization path when DM policy is open;
  • kept FEISHU_MENTION_ALIASES / FEISHU_OUTBOUND_MENTION_ALIASES compatibility with both Name=ou_xxx and Name:ou_xxx entries;
  • normalized bare ou_... / u_... mention IDs and made _message_mentions_bot() reuse the same ID extractor.

Validation on the rebased branch:

python -m compileall -q plugins/platforms/feishu/adapter.py tests/gateway/test_feishu.py tests/gateway/test_feishu_bot_admission.py tests/gateway/feishu_helpers.py
# passed

git diff --check
# passed

ruff check plugins/platforms/feishu/adapter.py tests/gateway/test_feishu.py tests/gateway/test_feishu_bot_admission.py tests/gateway/feishu_helpers.py
# All checks passed!

python -m pytest tests/gateway/test_feishu.py::TestFeishuOutboundMentions tests/gateway/test_feishu_bot_admission.py -q -o 'addopts='
# 82 passed, 5 warnings in 1.38s

python -m pytest tests/gateway/test_feishu.py tests/gateway/test_feishu_bot_admission.py tests/gateway/test_feishu_bot_auth_bypass.py -q -o 'addopts='
# 298 passed, 5 warnings in 3.44s

python -m pytest tests/tools/test_send_message_tool.py -q -o 'addopts='
# 152 passed, 1 warning in 1.91s

The PR is now mergeable from GitHub's perspective.

@ohmyskyhigh

Copy link
Copy Markdown
Author

Adding a code map for maintainers/admins so the test files in this PR are easier to orient around.

Why the tests/gateway/* changes are included

They are regression coverage for the exact Feishu behavior changed here, not unrelated test-only churn:

  • tests/gateway/test_feishu.py

    • Covers the outbound path: agent text like @MOSS ... → Feishu post payload with a native at tag.
    • Includes the review-requested left-boundary regression (user@MOSS.com) so alias matching does not create false mentions inside emails/fragments.
    • Also pins table-shaped content, inline-code exclusion, longest-alias precedence, and fail-soft parsing of bad alias entries.
  • tests/gateway/test_feishu_bot_admission.py

    • Covers the inbound/admission path for bot-to-bot group messages.
    • Pins the bare mention ID normalization case where Feishu gives mention.id = "ou_..." or "u_..." with no id_type; those now resolve through _extract_mention_ids() and are recognized by _message_mentions_bot().
    • Pins FEISHU_DM_POLICY=disabled as p2p-only, ahead of the existing p2p allow-all / pairing / allowlist logic, while group admission remains available.
  • tests/gateway/feishu_helpers.py

    • Existing shared test fixture file; this PR only extends the adapter skeleton with _dm_policy so the p2p/group admission regression can exercise the same attribute the real adapter sets via FeishuAdapterSettings.

Runtime flow to review

Outbound native mention path:

agent response text
  -> FeishuAdapter._build_outbound_payload()
  -> _content_has_outbound_mention_alias()
  -> _build_outbound_mention_text_post_payload()
  -> Feishu post content containing {"tag": "at", "user_id": ..., "user_name": ...}

Inbound bot-to-bot group admission path:

Feishu message event
  -> _admit(sender, message)
  -> bot sender policy: FEISHU_ALLOW_BOTS={none|mentions|all}
  -> group mention gate when FEISHU_REQUIRE_MENTION=true
  -> _message_mentions_bot()
  -> _extract_mention_ids() handles typed IDs and bare ou_/u_ strings

DM policy path:

p2p message
  -> FEISHU_DM_POLICY=disabled rejects p2p early
  -> otherwise existing p2p allow-all / pairing-mode / allowlist behavior is preserved

group message
  -> unaffected by DM policy; continues through group policy + mention admission

This should make the PR easier to review file-by-file: adapter.py contains the behavior, while the three test files pin the outbound payload construction, inbound mention normalization/admission, and the small shared fixture needed for that admission matrix.

@ohmyskyhigh

Copy link
Copy Markdown
Author

For maintainers reviewing the code changes, here is the shortest file-by-file map of the implementation.

plugins/platforms/feishu/adapter.py

This is the only runtime file changed in the PR. The changes are intentionally split into three independent paths:

  1. Outbound native @mention construction

    • New helpers parse FEISHU_MENTION_ALIASES / FEISHU_OUTBOUND_MENTION_ALIASES.
    • _build_outbound_payload() now checks whether the outgoing text contains a configured alias.
    • If it does, the adapter sends a Feishu post payload with real at elements instead of literal text.
    • Boundary checks prevent false positives such as user@MOSS.com, and inline-code spans are left as plain text.

    Review path:

    _build_outbound_payload()
      -> _content_has_outbound_mention_alias()
      -> _build_outbound_mention_text_post_payload()
      -> _split_text_with_outbound_mentions()
    
  2. Inbound bot-to-bot mention admission

    • _extract_mention_ids() now accepts Feishu's bare mention string forms:
      • ou_... as open_id
      • u_... as user_id
    • _message_mentions_bot() reuses _extract_mention_ids() so the same normalization is used by the group mention gate.

    Review path:

    _admit()
      -> _message_mentions_bot()
      -> _extract_mention_ids()
    
  3. DM disabling without changing group behavior

    • FEISHU_DM_POLICY is parsed into FeishuAdapterSettings.
    • FEISHU_DM_POLICY=disabled rejects only p2p messages.
    • Existing p2p allow-all / pairing / allowlist logic is preserved when DM policy is open.
    • Group messages continue through the existing group policy + mention gate.

    Review path:

    FeishuAdapter._load_settings()
      -> FeishuAdapter._apply_settings()
      -> _admit() p2p branch
    
  4. Standalone send_message parity

    • _standalone_send() loads the profile-local .env before constructing the transient Feishu adapter.
    • This keeps cron/tool sends consistent with the live gateway for outbound mention aliases.

Tests

The tests are included to make each changed runtime path reviewable and to prevent regressions:

  • tests/gateway/test_feishu.py

    • outbound alias conversion to Feishu native at tags;
    • table-shaped content preserving native mentions;
    • inline-code exclusion;
    • false-positive protection for email/fragments, including the review-requested user@MOSS.com case;
    • longest-alias matching and bad-entry fail-soft behavior.
  • tests/gateway/test_feishu_bot_admission.py

    • settings parsing for FEISHU_DM_POLICY;
    • bare ou_... / u_... mention ID normalization;
    • _message_mentions_bot() recognition of those normalized IDs;
    • strict bot-to-bot group admission with FEISHU_ALLOW_BOTS=mentions and FEISHU_REQUIRE_MENTION=true;
    • p2p-only DM rejection while group admission still works.
  • tests/gateway/feishu_helpers.py

    • shared fixture support for the admission matrix; the PR only extends the existing skeleton with the same _dm_policy attribute the real adapter sets.

Practical review order

A quick way to review the PR is:

  1. Read _build_outbound_payload() and the outbound alias helpers.
  2. Read _extract_mention_ids() and _message_mentions_bot().
  3. Read the p2p branch in _admit() for FEISHU_DM_POLICY=disabled.
  4. Skim the two focused test sections above to see each behavior pinned directly.

@ohmyskyhigh
ohmyskyhigh force-pushed the fix/feishu-agent-to-agent-group-chat branch from b99c6c1 to 65e9c5a Compare July 14, 2026 18:12
@ohmyskyhigh

Copy link
Copy Markdown
Author

Rebased and force-pushed this branch onto current origin/main again.

Current head: 65e9c5a831fe60fe6fad6916b205e11a5b0b9a37

GitHub now reports:

mergeable: MERGEABLE
mergeStateStatus: BLOCKED

BLOCKED appears to be policy/review-state related rather than a merge conflict; the branch itself is no longer conflicted.

I rechecked the admin/sweeper requirements after the rebase:

  • implementation is on the current live adapter path: plugins/platforms/feishu/adapter.py;
  • added/kept the left-boundary regression for user@MOSS.com so outbound alias matching does not create native at tags inside email/fragments;
  • FEISHU_DM_POLICY=disabled is layered ahead of p2p only, preserving the existing p2p allow-all / pairing / allowlist behavior when open;
  • group admission remains independent of DM disabling;
  • FEISHU_MENTION_ALIASES / FEISHU_OUTBOUND_MENTION_ALIASES compatibility is preserved;
  • bare ou_... / u_... mention IDs normalize through the same extractor used by _message_mentions_bot();
  • the test files remain included because they are direct regression coverage for the runtime paths above.

Validation on the rebased branch:

git diff --check
# passed

python -m compileall -q plugins/platforms/feishu/adapter.py tests/gateway/test_feishu.py tests/gateway/test_feishu_bot_admission.py tests/gateway/feishu_helpers.py
# passed

ruff check plugins/platforms/feishu/adapter.py tests/gateway/test_feishu.py tests/gateway/test_feishu_bot_admission.py tests/gateway/feishu_helpers.py
# All checks passed!

python -m pytest tests/gateway/test_feishu.py::TestFeishuOutboundMentions tests/gateway/test_feishu_bot_admission.py -q -o 'addopts='
# 82 passed, 5 warnings in 1.34s

python -m pytest tests/gateway/test_feishu.py tests/gateway/test_feishu_bot_admission.py tests/gateway/test_feishu_bot_auth_bypass.py -q -o 'addopts='
# 298 passed, 5 warnings in 3.18s

python -m pytest tests/tools/test_send_message_tool.py -q -o 'addopts='
# 152 passed, 1 warning in 1.71s

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 P3 Low — cosmetic, nice to have 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/feature New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants