fix: multi-bot Telegram routing uses account_id, not first-match on allowed_users#2183
Merged
Conversation
Co-authored-by: zhongxiong <[email protected]>
…llowed_users resolve_with_context only looked up the generic "Telegram" key in channel_defaults, but keys are stored as "Telegram:<account_id>" when multiple bots are configured. A user present in both bots' allowed_users always matched the first-registered bot regardless of which token received the message. Fix: before falling back to the generic channel key, probe the account-specific key "Telegram:<account_id>" when ctx.account_id is present. Adds a regression test covering the exact scenario from #2140. Fixes #2140
houko
enabled auto-merge (squash)
April 8, 2026 06:22
houko
added a commit
that referenced
this pull request
May 24, 2026
…lation) Channel-side slash commands (/btw, /new, /reboot, /compact, /model, /stop, /usage, /think, plus the model: interactive-button callback) called the context-less AgentRouter::resolve, which has no account_id parameter and only probes the plain channel key. With per-bot defaults stored under qualified "<channel>:<account_id>" keys (since #2183 / #4861), the lookup fell through to the system default — collapsing every bot's /command to the first-registered agent. /agent <name> stored its override via set_user_default keyed by platform_id only, with no channel scope, so a /agent in bot-a silently overrode every other bot's default_agent for that user until daemon restart (user_defaults is in-memory DashMap, never persisted). Fix re-keys user_defaults as (Option<channel_account_key>, user_key); resolve_with_context probes the per-(channel,account) slot first and falls back to the legacy global slot. handle_command gains an account_id parameter, builds a BindingContext per call via a resolve_for_command closure, and writes /agent's selection via the new set_user_default_for_channel helper. The model: button callback and resolve_or_fallback's auto-bind path are migrated identically — otherwise the fallback path would silently re-introduce the leak. 5 regression tests pin the fix: - router::user_default_does_not_leak_across_bots - router::channel_scoped_user_default_overrides_global - router::legacy_set_user_default_is_channel_agnostic - bridge::command_resolution_respects_account_id (records set_model dispatch agent_ids; /model from bot-b then bot-c records [agent_b, agent_c] — pre-fix this would be [agent_a, agent_a]) - bridge::agent_command_does_not_leak_across_bots cargo test -p librefang-channels --lib → 465/465 green. cargo clippy -p librefang-channels --all-targets -- -D warnings clean. Closes #5672
houko
added a commit
that referenced
this pull request
May 24, 2026
…lation) (#5688) Channel-side slash commands (/btw, /new, /reboot, /compact, /model, /stop, /usage, /think, plus the model: interactive-button callback) called the context-less AgentRouter::resolve, which has no account_id parameter and only probes the plain channel key. With per-bot defaults stored under qualified "<channel>:<account_id>" keys (since #2183 / #4861), the lookup fell through to the system default — collapsing every bot's /command to the first-registered agent. /agent <name> stored its override via set_user_default keyed by platform_id only, with no channel scope, so a /agent in bot-a silently overrode every other bot's default_agent for that user until daemon restart (user_defaults is in-memory DashMap, never persisted). Fix re-keys user_defaults as (Option<channel_account_key>, user_key); resolve_with_context probes the per-(channel,account) slot first and falls back to the legacy global slot. handle_command gains an account_id parameter, builds a BindingContext per call via a resolve_for_command closure, and writes /agent's selection via the new set_user_default_for_channel helper. The model: button callback and resolve_or_fallback's auto-bind path are migrated identically — otherwise the fallback path would silently re-introduce the leak. 5 regression tests pin the fix: - router::user_default_does_not_leak_across_bots - router::channel_scoped_user_default_overrides_global - router::legacy_set_user_default_is_channel_agnostic - bridge::command_resolution_respects_account_id (records set_model dispatch agent_ids; /model from bot-b then bot-c records [agent_b, agent_c] — pre-fix this would be [agent_a, agent_a]) - bridge::agent_command_does_not_leak_across_bots cargo test -p librefang-channels --lib → 465/465 green. cargo clippy -p librefang-channels --all-targets -- -D warnings clean. Closes #5672
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
resolve_with_contextlooked upchannel_defaultswith the plain key"Telegram", but multi-bot entries are stored under"Telegram:<account_id>"— so account-specific defaults never matchedallowed_users) always hit the first-registered bot regardless of which token received the message"Telegram:<account_id>"before falling back to the generic keyTest plan
test_multi_bot_account_id_routingcovers the exact scenario from the issuecargo test -p librefang-channels routerall passFixes #2140