fix(honcho): pinUserPeer collapses the operator's accounts, not bot authors
with pinUserPeer on, resolve_author_peer_id returned None for every author, so a bot dm's words were written under the human's pinned peer inside the a2a session. the pin exists to unify one person's platform accounts. a bot is not one of them. bot: authors now resolve to their peer before the pin check, so a pinned operator still gets bot speech attributed to the bot.
This commit is contained in:
@@ -123,11 +123,15 @@ class SessionPeersMixin:
|
||||
|
||||
def resolve_author_peer_id(self, key: str, author_id: str | None, author_name: str | None = None) -> str | None:
|
||||
"""Peer ID for the turn's author, or None to keep the session's peer: no author named, the
|
||||
author IS the session's peer, or ``pinPeerName`` collapsing identities by operator request.
|
||||
author IS the session's peer, or ``pinPeerName`` collapsing the operator's accounts. Bot authors
|
||||
are never collapsed: pinned or not, a bot's words go under the bot's peer.
|
||||
``author_name`` is a display name (attacker-influenceable), so it never becomes a peer ID."""
|
||||
runtime_id = str(author_id).strip() if author_id else ""
|
||||
if not runtime_id:
|
||||
return None
|
||||
# The pin collapses the operator's own accounts onto one peer; a bot is never one of those.
|
||||
if runtime_id.startswith(BOT_AUTHOR_PREFIX):
|
||||
return self._peer_id_for_runtime_id(runtime_id)
|
||||
if self._config is not None and bool(getattr(self._config, "peer_name", None)) \
|
||||
and getattr(self._config, "pin_peer_name", False) is True:
|
||||
return None
|
||||
|
||||
@@ -85,8 +85,8 @@ class TestA2aRouting:
|
||||
|
||||
provider._manager.get_or_create.assert_not_called()
|
||||
|
||||
def test_collapsed_bot_peer_keeps_the_default_user_peer(self):
|
||||
"""pinUserPeer returns no author peer; the a2a session then falls back to the resolved peer."""
|
||||
def test_unresolvable_bot_peer_keeps_the_default_user_peer(self):
|
||||
"""When no author peer resolves, the a2a session still opens under the resolved peer."""
|
||||
provider = _provider()
|
||||
provider._manager.resolve_author_peer_id.return_value = None
|
||||
|
||||
|
||||
@@ -99,9 +99,10 @@ class TestResolveAuthorPeerId:
|
||||
mgr = _manager(_config(), runtime_id="7654321")
|
||||
assert mgr.resolve_author_peer_id("telegram:dm1", "bot:") == "bot-"
|
||||
|
||||
def test_pin_peer_name_collapses_bot_authors_too(self):
|
||||
def test_pin_peer_name_does_not_collapse_bot_authors(self):
|
||||
"""The pin unifies the operator's accounts; a bot's words never land under the human's peer."""
|
||||
mgr = _manager(_config(pin_peer_name=True), runtime_id="7654321")
|
||||
assert mgr.resolve_author_peer_id("telegram:dm1", "bot:coder") is None
|
||||
assert mgr.resolve_author_peer_id("telegram:dm1", "bot:coder") == "coder"
|
||||
|
||||
def test_display_name_never_becomes_a_peer_id(self):
|
||||
"""Display names are attacker-influenceable on most platforms."""
|
||||
|
||||
Reference in New Issue
Block a user