From 88ff64be48335af3fbf90382cf2d52773378bc98 Mon Sep 17 00:00:00 2001 From: chengyongru Date: Fri, 15 May 2026 10:33:18 +0800 Subject: [PATCH] =?UTF-8?q?feat(pairing):=20allow=20omitted=20allowFrom=20?= =?UTF-8?q?=E2=80=94=20pairing-only=20mode=20by=20default?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Previously _validate_allow_from raised SystemExit when allowFrom was missing, forcing every channel to declare an explicit allowlist. With the pairing feature this is no longer necessary: a channel with no allowFrom simply operates in pairing-only mode, letting users approve senders via /pairing approve from the WebUI or CLI. - Replace SystemExit with an info log in _validate_allow_from - Add test_validate_allow_from_allows_missing_allow_from --- nanobot/channels/manager.py | 8 ++++--- tests/channels/test_channel_plugins.py | 30 ++++++++++++++++++++++++++ 2 files changed, 35 insertions(+), 3 deletions(-) diff --git a/nanobot/channels/manager.py b/nanobot/channels/manager.py index b63510fae..c310943cd 100644 --- a/nanobot/channels/manager.py +++ b/nanobot/channels/manager.py @@ -144,9 +144,11 @@ class ChannelManager: else: allow = getattr(cfg, "allow_from", None) if allow is None: - raise SystemExit( - f'Error: "{name}" is missing or null allowFrom. ' - f'Set ["*"] to allow everyone, or add specific user IDs.' + # allowFrom omitted → pairing-only mode. Unapproved senders + # receive a pairing code instead of being silently ignored. + logger.info( + '"{}" has no allowFrom; unapproved users will receive a pairing code', + name, ) def _should_send_progress(self, channel_name: str, *, tool_hint: bool = False) -> bool: diff --git a/tests/channels/test_channel_plugins.py b/tests/channels/test_channel_plugins.py index 9b6e79783..2309df2c2 100644 --- a/tests/channels/test_channel_plugins.py +++ b/tests/channels/test_channel_plugins.py @@ -1010,6 +1010,36 @@ async def test_validate_allow_from_allows_empty_dict_allow_from(): mgr._validate_allow_from() +@pytest.mark.asyncio +async def test_validate_allow_from_allows_missing_allow_from(): + """Omitted allowFrom is valid — channel operates in pairing-only mode.""" + fake_config = SimpleNamespace( + channels=ChannelsConfig(), + providers=SimpleNamespace(groq=SimpleNamespace(api_key="")), + ) + + class _NoAllowFromChannel(BaseChannel): + name = "noallow" + display_name = "No Allow" + + async def start(self) -> None: + pass + + async def stop(self) -> None: + pass + + async def send(self, msg: OutboundMessage) -> None: + pass + + mgr = ChannelManager.__new__(ChannelManager) + mgr.config = fake_config + mgr.channels = {"test": _NoAllowFromChannel({"enabled": True}, None)} + mgr._dispatch_task = None + + # Should not raise — pairing-only mode + mgr._validate_allow_from() + + @pytest.mark.asyncio async def test_get_channel_returns_channel_if_exists(): """get_channel should return the channel if it exists."""