Skip to content

fix: isolate empty-mention waiter by sender without changing default session scope - #9378

Open
hedssaz wants to merge 1 commit into
AstrBotDevs:masterfrom
hedssaz:fix/9377
Open

fix: isolate empty-mention waiter by sender without changing default session scope#9378
hedssaz wants to merge 1 commit into
AstrBotDevs:masterfrom
hedssaz:fix/9377

Conversation

@hedssaz

@hedssaz hedssaz commented Jul 24, 2026

Copy link
Copy Markdown

Fixes #9377

In group chats, an empty mention (@bot with no text) starts a 60s
session_waiter to wait for follow-up content. The waiter is registered with
DefaultSessionFilter, whose key is unified_msg_origin (group-level) and does
not include the sender. As a result, the first non-empty message from any
other member
during the wait window is intercepted, gets a bot At prepended,
and is re-dispatched — producing a reply to the wrong user / wrong context. Full
analysis, reproduction and captured logs are in #9377.

This PR fixes the empty-mention flow without changing the default session
scope
, so third-party plugins that rely on the current group-level default are
unaffected.

Why not simply change DefaultSessionFilter

Changing the default from unified_msg_origin to include the sender would
change the default runtime behavior of session_waiter, which is exported to
plugins through astrbot.api.util. The group-level default has been in place
since v3.5.4 (~15 months, 138 stable releases) and is relied upon by real
plugins that omit session_filter=, for example:

  • astrbot_plugin_kimi_datasource_api registers a waiter manually with
    SessionWaiter(DefaultSessionFilter(), event.unified_msg_origin, ...). The
    registration key is the raw UMO while lookups go through
    DefaultSessionFilter.filter(event). If the default changed, the keys would no
    longer match and its login waiter would never trigger.
  • astrbot_plugin_decision_roulette and astrbot_plugin_TurtleSoup use the
    default filter to collect input from multiple group members; a per-sender
    default would only accept the initiator.

So the fix is scoped to the one place that actually needs per-sender isolation.

Modifications

  • astrbot/core/utils/session_waiter.py: add SenderSessionFilter, a new
    optional SessionFilter keyed by unified_msg_origin + sender_id.
    DefaultSessionFilter is unchanged, and no new names are added to the
    public astrbot.api.util surface. This is purely additive.
    The key uses a length-prefixed encoding (f"{len(umo)}:{umo}:{sender}")
    rather than plain concatenation: both the UMO and the sender id are arbitrary
    platform strings (e.g. Telegram topic-group UMOs contain #), so a plain
    separator would allow constructible key collisions that defeat the isolation.
  • astrbot/builtin_stars/astrbot/main.py: the empty-mention waiter now passes
    session_filter=SenderSessionFilter(). When the sender id is unavailable it
    skips waiting entirely, to avoid silently degrading back to group scope.
  • tests/test_session_waiter_cross_user.py (new): 7 session-key regression
    tests (module loaded standalone).
  • tests/unit/test_empty_mention_sender_scope.py (new): 3 production-wiring
    tests driving the real Main.handle_empty_mention and
    Main.handle_session_control_agent (following the existing
    Main.__new__(Main) pattern in tests/unit/test_group_chat_context_wiring.py).

The other issues noted in #9377 (empty-message_str messages being swallowed
during the wait window; _cleanup / FILTERS behavior with same-key waiters)
are independent of the session key and will be handled in separate PRs.

Screenshots or Test Results

$ ruff format --check <changed files + test files> && ruff check <same>
All checks passed!

$ pytest tests/test_session_waiter_cross_user.py \
         tests/unit/test_empty_mention_sender_scope.py -o asyncio_mode=auto -q
..........                                                               [100%]
10 passed

Session-key coverage (tests/test_session_waiter_cross_user.py):

  1. test_default_filter_returns_umo_unchanged — the default filter still returns
    unified_msg_origin (locks the default semantics in place);
  2. test_default_manual_umo_registration_still_triggers — reproduces the
    kimi_datasource_api manual-registration pattern and asserts it still
    triggers (guards against regressing plugins that depend on the group default);
  3. test_sender_filter_isolates_users_in_group — another member's message is no
    longer intercepted (primary goal);
  4. test_sender_filter_accepts_original_sender — the original sender's follow-up
    is still captured;
  5. test_sender_filter_key_is_collision_free — UMOs / sender ids containing
    separator characters (#, :) cannot produce colliding keys;
  6. test_sender_filter_isolates_users_with_hash_in_umo — isolation holds for
    Telegram topic-group style UMOs that contain #;
  7. test_sender_filter_does_not_leak_across_conversations — the same user's
    messages in other groups / DMs are not intercepted.

Production-wiring coverage (tests/unit/test_empty_mention_sender_scope.py,
drives the real handlers rather than a re-implementation):

  1. test_other_members_message_passes_through — BOB's message is not stopped,
    not modified, and not re-enqueued while ALICE's waiter is active;
  2. test_original_senders_followup_is_redispatched — ALICE's follow-up gets the
    bot At injected, is stopped and re-enqueued, and the waiter is cleaned up;
  3. test_empty_sender_id_skips_waiting — with no reliable sender id the
    handler finishes immediately and registers no waiter.

Verified that the wiring tests actually guard the fix: with the main.py
change reverted to master, tests 8 and 10 fail; with the fix in place all 10
pass.


  • This is NOT a breaking change.

The default session scope is unchanged; only the built-in empty-mention waiter
opts into per-sender isolation. SenderSessionFilter is an additive core
utility and is not exported through astrbot.api.util.

Checklist

Summary by Sourcery

Fix empty-mention session handling in group chats by isolating the waiter per sender while preserving existing group-level session semantics.

Bug Fixes:

  • Prevent empty-mention waiters in group chats from intercepting follow-up messages from other members and replying in the wrong context.
  • Ensure empty-mention handling skips session waiting when no reliable sender ID is available to avoid unintended group-scoped behavior.

Enhancements:

  • Introduce an optional SenderSessionFilter that scopes session keys by conversation origin and sender using a collision-safe encoding without changing the default session filter.
  • Strengthen session key behavior with explicit guarantees against key collisions and cross-conversation leakage.

Tests:

  • Add regression tests that lock in DefaultSessionFilter behavior and validate SenderSessionFilter isolation, collision safety, and cross-conversation separation.
  • Add production wiring tests for empty-mention handling to verify correct per-sender redispatch, non-interference with other members, and behavior when sender ID is missing.

@gemini-code-assist

Copy link
Copy Markdown
Contributor

Caution

The consumer version of Gemini Code Assist on GitHub has been sunset. All code review activity has officially ceased.

@dosubot dosubot Bot added size:M This PR changes 30-99 lines, ignoring generated files. area:core The bug / feature is about astrbot's core, backend labels Jul 24, 2026

@sourcery-ai sourcery-ai Bot 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.

Hey - I've reviewed your changes and they look great!


Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area:core The bug / feature is about astrbot's core, backend size:M This PR changes 30-99 lines, ignoring generated files.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug] 群聊中空提及等待器会截获其他成员的消息(session_waiter 未绑定发送者)

1 participant