Repository navigation
Conversation
…ssion In an observe chat, text turns and observed chatter use a sender-less source, so they share one group session. Slash commands keep the sender's user_id for slash access checks (NousResearch#67816), and with the default group_sessions_per_user that id keyed them to a per-user session: /new, /model, /undo and skill commands acted on a session the observed transcript and the agent's history never reached. SessionSource gains shared_session, set by the Telegram adapter for observe-chat events. build_session_key and is_shared_multi_user_session honour it, so commands keep their sender for authorization but share the group session and render the same session-context prompt as text turns. It round-trips through to_dict/from_dict (written only when set) so events rebuilt from a stored origin key the same session.
The core fix is right, and the regression test is a real one — I mutated all six production predicates this PR touches ( One regression from the flag landing on the command source.
Skill and bundle slash commands are rewritten into a natural-language prompt by
Cheapest fix is excluding commands at the prefix. Worth considering whether the flag belongs on Also checked and clean: the cross-origin |
|
Thanks for the mutation pass. I checked both points on the PR head (dbba06d). No code change from this round. 1. In observe mode, the triggered text turn in the same session is already attributed by the adapter ( 2. With the fix, every turn in the shared observe session renders the same "Multi-user session" context, so the cached prefix stays byte-stable. The test already pins this ( Docs: N/A. Nothing changed in this round. |
…ode parity) Rebase + rework of NousResearch#133418 addressing the review findings (F-001..F-004): NousResearch#133418 (review) Core design change (F-001, F-003): shared-session routing is now declared on the SessionSource (``shared_session: bool``) instead of erasing the caller identity. - Attributed text turns, commands and observed chatter KEEP ``user_id``, so ``_hm_admit_event`` authorizes an already-allowed participant exactly as before — no second chat-grant configuration is required for the feature to work, and observation permission never silently becomes invocation permission. - ``build_session_key`` / ``is_shared_multi_user_session`` honor ``shared_session``, so observed chatter, addressed turns, commands and origins restored through ``to_dict``/``from_dict`` all resolve to ONE shared group lane. ``/new`` and other session-scoped commands now act on the conversation the bot is actually in, while slash-access sender checks keep working. The field round-trips only when set, so existing stored origins stay byte-identical. (Same field name/contract as NousResearch#131336 so the two PRs compose.) F-002: a single effective observe scope (``_whatsapp_observe_scope_active``) governs BOTH collection and attribution: feature flag + explicit ``observe_allowed_chats`` + mention-gated intake (require_mention on, chat not free-response). With ``require_mention: false`` a message is processed once, normally, and never observed; free-response chats keep their original principal and text. F-004: the observer emits a ``datetime`` object, which ``coerce_epoch`` accepts — verified by a real SessionStore/SessionDB round-trip with exact epoch readback and no corrupt-timestamp warning. The WHATSAPP_GROUP_ALLOWED_CHATS authz registration is retained as a general capability (mirrors Telegram's TELEGram_GROUP_ALLOWED_CHATS for user-less group principals) but is no longer required by this feature. Tests (18): observe gating matrix, effective-scope exclusivity (require_mention off / free-response), principal-preserving shared routing with the text/command/observed/restored one-key invariant, real-database timestamp round-trip, history-builder marker recognition. Sibling suites: 118 passed. Driver: live WhatsApp bot validated (unmentioned chatter observed and injected on the next mention; mention-only reply behavior preserved).
What does this PR do?
In a Telegram observe chat (
observe_unmentioned_group_messages), text turns and observed chatter use a sender-less source and share one group session. Slash commands keep the sender'suser_idon purpose so slash access checks can identify the sender (#67816), but with the defaultgroup_sessions_per_userthat same id keyed them to a per-user session./newreset an empty session,/model//undo//compressacted on the wrong one, and skill commands ran without the observed context.The sender id cannot simply be dropped from commands (it is the authorization input), so the session's sharing is declared separately.
SessionSource.shared_sessionis set by the Telegram adapter for every observe-chat event (text, observed chatter, commands).build_session_keydoes not add the participant when it is set, andis_shared_multi_user_sessionreturns True, so the key and the guards that mirror it (_is_shared_session_source) stay in lock-step. Commands keepuser_idfor_check_slash_access/_is_user_authorized, unchanged.Effects beyond the key, all intended:
[sender]prefix of shared sessions; observe text turns already carry[nickname|user_id]and have nouser_name, so they gain no second prefix.shared_sessionround-trips throughto_dict/from_dict, written only when set, so existing stored origins stay byte-identical. An event rebuilt from a stored origin (internal wakes, completions) keys the same shared session even when the last turn was a command. A peer that could set it could already senduser_id: nullfor the same key, so it grants nothing new.Related Issue
Fixes #131335
Type of Change
Changes Made
gateway/session.py:SessionSource.shared_session(serialized only when set);build_session_keyandis_shared_multi_user_sessionhonour it.plugins/platforms/telegram/adapter.py:_telegram_group_observe_shared_sourcesets it;_apply_telegram_group_observe_attributionsets it on commands while keeping their sender.tests/gateway/test_telegram_group_gating.py: one invariant test through the real_handle_text_message/_handle_command. The addressed text turn, the command, the observed-chatter source and the command source restored throughto_dict/from_dictall build one session key; the command still carries the sender id; command and text turns render the same session-context prompt.website/docs/user-guide/messaging/telegram.mdand its zh-Hans mirror now say slash commands in an observe chat act on the shared session while admin-only commands are still checked against the sender.How to Test
scripts/run_tests.sh tests/gateway/test_telegram_group_gating.pymain:AssertionError: {'agent:main:telegram:group:-100', 'agent:main:telegram:group:-100:111'}.is_shared_multi_user_sessionfails the prompt parity.tests/gateway/test_telegram_*.py,tests/plugins/test_telegram_update_admission.py,test_session.py,test_prompt_tail_freeze.py, the slash-session tests (77 files): 735 passed, 3 skipped. Every other test file usingbuild_session_key/SessionSource.from_dict/is_shared_multi_user_session(60 files): 418 passed, 2 failed, both unrelated.test_kanban_wake_acceptance.py::test_push_receipt_requires_real_admission_without_displacing_user[True]fails identically onmainin this env (python-telegram-bot not installed);test_completion_admission.py::test_unavailable_raw_route_is_quiet_without_hiding_invalid_routestimed out a 5 s WAL scan under 10 workers and passes alone. Ruff clean;check_doc_links.pypasses. Python 3.14.Checklist
Code
fix(scope):,feat(scope):, etc.)pytest tests/ -qand all tests pass. I ran only the related files listed above viascripts/run_tests.sh, not the full suite.Documentation & Housekeeping
docs/, docstrings) — or N/Acli-config.yaml.exampleif I added/changed config keys — or N/ACONTRIBUTING.mdorAGENTS.mdif I changed architecture or workflows — or N/A