diff --git a/plugins/memory/honcho/client.py b/plugins/memory/honcho/client.py index 2a7b07ca1b3..3d31bd7a1fb 100644 --- a/plugins/memory/honcho/client.py +++ b/plugins/memory/honcho/client.py @@ -91,12 +91,17 @@ def _normalize_recall_mode(val: str) -> str: return val if val in _VALID_RECALL_MODES else "hybrid" -def _resolve_bool(host_val, root_val, *, default: bool) -> bool: - """Resolve a bool config field: host wins, then root, then default.""" - if host_val is not None: - return bool(host_val) - if root_val is not None: - return bool(root_val) +def _resolve_bool(*vals, default: bool) -> bool: + """Resolve a bool config field: first non-None wins, else default. + + Variadic to support aliased keys (e.g. ``pinUserPeer`` shadowing + ``pinPeerName`` for backwards compatibility). Pass values in + precedence order: caller's preferred alias first, then fallback + aliases, in (host, root) interleaving as needed. + """ + for val in vals: + if val is not None: + return bool(val) return default @@ -488,7 +493,15 @@ class HonchoClientConfig: peer_name=host_block.get("peerName") or raw.get("peerName"), ai_peer=ai_peer, pin_peer_name=_resolve_bool( + # ``pinUserPeer`` is the clearer name (the resolver pins + # the user-side peer to ``peerName``, ignoring runtime + # identity). ``pinPeerName`` is the original key from + # #14984 and stays accepted for backward compatibility. + # Host-level keys win over root-level; among same-level + # keys, ``pinUserPeer`` wins over ``pinPeerName``. + host_block.get("pinUserPeer"), host_block.get("pinPeerName"), + raw.get("pinUserPeer"), raw.get("pinPeerName"), default=False, ), diff --git a/tests/honcho_plugin/test_pin_peer_name.py b/tests/honcho_plugin/test_pin_peer_name.py index 2cfdfc6cdf6..e1ef5fda082 100644 --- a/tests/honcho_plugin/test_pin_peer_name.py +++ b/tests/honcho_plugin/test_pin_peer_name.py @@ -614,3 +614,70 @@ class TestCrossPlatformMemoryUnification: "multi-user default MUST keep users separate — a regression " "here would silently merge unrelated users' memory" ) + + +class TestPinUserPeerAlias: + """``pinUserPeer`` is the canonical name; ``pinPeerName`` is the + backwards-compatible alias. + + Both keys land on the same internal ``pin_peer_name`` field. When + both appear, the precedence is: host pinUserPeer → host pinPeerName + → root pinUserPeer → root pinPeerName → default. This matches the + rule for every other host/root override in the plugin and lets a + host block explicitly disable a root-level pin even via the legacy + key. + """ + + def test_root_pinUserPeer_true_pins(self, tmp_path): + from plugins.memory.honcho.client import HonchoClientConfig + import json + config_file = tmp_path / "honcho.json" + config_file.write_text(json.dumps({ + "apiKey": "***", + "peerName": "eri", + "pinUserPeer": True, + })) + config = HonchoClientConfig.from_global_config(config_path=config_file) + assert config.pin_peer_name is True + + def test_host_pinUserPeer_wins_over_root_pinPeerName(self, tmp_path): + from plugins.memory.honcho.client import HonchoClientConfig + import json + config_file = tmp_path / "honcho.json" + config_file.write_text(json.dumps({ + "apiKey": "***", + "peerName": "eri", + "pinPeerName": False, + "hosts": {"hermes": {"pinUserPeer": True}}, + })) + config = HonchoClientConfig.from_global_config(config_path=config_file) + assert config.pin_peer_name is True + + def test_host_pinUserPeer_false_disables_root_pinPeerName(self, tmp_path): + from plugins.memory.honcho.client import HonchoClientConfig + import json + config_file = tmp_path / "honcho.json" + config_file.write_text(json.dumps({ + "apiKey": "***", + "peerName": "eri", + "pinPeerName": True, + "hosts": {"hermes": {"pinUserPeer": False}}, + })) + config = HonchoClientConfig.from_global_config(config_path=config_file) + assert config.pin_peer_name is False, ( + "Host-level pinUserPeer=false must override the legacy " + "root-level pinPeerName=true, otherwise a host can never " + "unpin a globally-pinned profile via the new alias." + ) + + def test_pinPeerName_still_works_unchanged(self, tmp_path): + from plugins.memory.honcho.client import HonchoClientConfig + import json + config_file = tmp_path / "honcho.json" + config_file.write_text(json.dumps({ + "apiKey": "***", + "peerName": "eri", + "hosts": {"hermes": {"pinPeerName": True}}, + })) + config = HonchoClientConfig.from_global_config(config_path=config_file) + assert config.pin_peer_name is True