feat(gateway): per-account session identity (#8287, 2/6) - #86532
Conversation
First slice of the account-aware gateway: configuration surface only, no runtime behavior change. - TELEGRAM_BOT_TOKEN_<ACCOUNT> env vars declare additional bot accounts (lowercased names); the unsuffixed TELEGRAM_BOT_TOKEN remains the default account, so single-bot setups parse byte-identically. Tokens are secrets: env/.env is their supported home. - platforms.telegram.accounts.<name> in config.yaml carries the behavioral per-account settings (display names, allowlists, home channels) and merges with env tokens on the account name; the block arrives top-level or bridged into extra (the same two-route pattern as gateway_restart_notification) and round-trips through to_dict. Registry, session-key, and routing slices follow in this branch per the acceptance architecture in the NousResearch#10455 review.
Second slice: the same chat reached through two bot accounts is two sessions. - SessionSource.account carries which bot received the message (stamped by the adapter in the upcoming registry slice); wire-invisible when unset, serialized like profile. - The account rides in the session-key NAMESPACE slot — the same mechanism profiles use: agent:main@support / agent:coder@support. Positional parsers keep their layout (parts[2] == platform), and single-bot gateways produce byte-identical keys (locked by test). - build_session_key reads the account from the SOURCE, not a caller parameter, so the adapter-level guard and the session store derive the same key for the same event — per-key guards diverging is the NousResearch#64934 bug class, and this keeps that door shut by construction. - The two namespace readers are account-aware via a shared helper: _profile_from_session_key strips the suffix instead of resolving 'main@support' as a profile name, and _parse_session_key accepts the suffixed default namespace (named-profile keys stay excluded). - Account names are charset-restricted at config parse ([a-z0-9][a-z0-9_-]*) so ':' and '@' can never reach a key. Includes the NousResearch#10455-review isolation test: same chat + user via two accounts yields distinct keys.
feat(gateway): per-account session identity (#8287, 2/6)
|
Second slice of the #67455 split (#8287, triage-recorded
best fix). Follows #86497.Stacks on 1/6. GitHub won't let a fork PR target another fork branch, so this contains #86497's commit as well as its own. Once #86497 merges this shrinks to its own ~190 lines automatically — review just the second commit (
per-account session identity) if 1/6 is still open.What it does
Makes the account part of session identity, so two bots sharing a chat id don't collide.
SessionSourcecarries anaccountfield, and the session key derivation folds it in. Without this, a message to the support bot and a message to the sales bot in the same Telegram chat resolve to the same session — they'd share transcript history and interleave turns. That's the same class of many-to-one key collision as #64934, just reached through a different door.accountisNonefor the default/single-bot adapter, and the derived key is byte-identical to today in that case. Single-bot deployments are unaffected.Why it isn't independently useful yet
Nothing populates
source.accountuntil 3/6 (adapter registry +build_source()stamping) — so on its own this widens the identity contract without exercising it. Same disclosure as 1/6: I'd rather state it than have it found in review.Worth flagging that 3/6 is where your earlier review finding lands — the original PR stamped
accountonly on Telegram's auth-helper paths, so ordinary inbound traffic keptaccount=Noneand the whole feature silently no-op'd for real messages. The stamp moves intoBasePlatformAdapter.build_source(), the single construction site every platform's normal event flows through.Sequence
1/6 config parsing (#86497) → 2/6 session identity (this) → 3/6 adapter registry + inbound stamping → 4/6 delivery routing + reconnect → 5/6 per-account
send_message→ 6/6 cron targets, home broadcasts, setup UX.Tests
tests/gateway/test_telegram_multi_account_sessions.py— 9 tests: account folded into the key, distinct accounts in one chat get distinct sessions,account=Nonereproduces the current key byte-for-byte, and round-tripping throughSessionSource.17 passed across both slices' test files. Full
tests/gateway/run: the sorted set of erroring modules is identical to pristineupstream/main(13 either way — pre-existing optional-dep gaps on my Windows box). Compared by test-id set rather than count, since collection aborts early and raw counts drift between runs.