From fc69726486a4414f24c59a5ff5a49a3b32e0c286 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 17 Jul 2026 13:30:18 +0000 Subject: [PATCH] fix(gateway): restore bespoke telegram/slack/matrix setup dispatch MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Follow-up to #89, found by a fan-out audit of that fix. Scoped narrowly to avoid overlapping with #93 (which independently restores the dingtalk/feishu/wecom/wecom_callback _PLATFORMS entries dropped by the same e39b468 refactor, and — more thoroughly than an earlier draft of this change attempted — the bespoke _setup_feishu() QR flow needed by tests/gateway/test_setup_feishu.py). e39b468 also unwired the bespoke _setup_telegram/_setup_slack/ _setup_matrix flows from _builtin_setup_fn(), even though those functions still exist intact in hermes_cli/setup.py. Without them, _configure_platform() falls through to the generic _setup_standard_platform, which treats the first `vars` entry (token_var) as mandatory and aborts the whole wizard if left empty. For Matrix that's a real functional break, independent of anything #93 touches: the entry's own help text says "leave empty to use password login instead", but the generic flow aborts right there before ever asking for a user ID or password. Re-wired the three bespoke functions back into _builtin_setup_fn() to fix that, and to restore Telegram's token-format validation and Slack's manifest-regeneration prompt. Adds two regression tests: one ties every _PLATFORMS-restored built-in adapter (excluding dingtalk/feishu/wecom/wecom_callback, tracked separately per .plans/missing-platform-plugin-shims.md so this test doesn't depend on merge order with #93) to a picker entry; the other asserts telegram/slack/matrix resolve to their bespoke setup functions rather than silently falling back to the generic flow. Co-Authored-By: Claude Sonnet 5 Claude-Session: https://claude.ai/code/session_01Bym5HhKPQ3CWb5r9bCvBq4 --- hermes_cli/gateway.py | 20 +++--- .../test_gateway_platform_gating.py | 61 +++++++++++++++++++ 2 files changed, 73 insertions(+), 8 deletions(-) diff --git a/hermes_cli/gateway.py b/hermes_cli/gateway.py index ccac2220a6b9..e3fa8d6339b7 100644 --- a/hermes_cli/gateway.py +++ b/hermes_cli/gateway.py @@ -5814,17 +5814,21 @@ def _builtin_setup_fn(key: str): from hermes_cli import setup as _s return { - # telegram moved into the plugin: setup_fn registered by - # plugins/platforms/telegram/adapter.py::register(). #41112. # discord moved into the plugin: setup_fn is registered by # plugins/platforms/discord/adapter.py::register() and dispatched # via the plugin path in _configure_platform(). - # slack moved into the plugin: setup_fn is registered by - # plugins/platforms/slack/adapter.py::register() and dispatched - # via the plugin path in _configure_platform(). #41112. - # matrix moved into the plugin: setup_fn registered by - # plugins/platforms/matrix/adapter.py::register() and dispatched via - # the plugin path in _configure_platform(). #41112. + # + # telegram, slack, and matrix are built-in (not plugin-migrated — + # see the _PLATFORMS NOTE above). They keep their bespoke setup + # flows here because the generic _setup_standard_platform fallback + # treats the first `vars` entry (token_var) as mandatory and aborts + # the whole wizard if it's left empty. That breaks Matrix's + # documented "leave the access token empty for password login" + # path outright, and loses Telegram's token-format validation and + # Slack's manifest-regeneration prompt. + "telegram": _s._setup_telegram, + "slack": _s._setup_slack, + "matrix": _s._setup_matrix, # mattermost moved into the plugin: setup_fn is registered by # plugins/platforms/mattermost/adapter.py::register() and dispatched # via the plugin path in _configure_platform(). diff --git a/tests/hermes_cli/test_gateway_platform_gating.py b/tests/hermes_cli/test_gateway_platform_gating.py index c16875687ce4..ebf648d03a61 100644 --- a/tests/hermes_cli/test_gateway_platform_gating.py +++ b/tests/hermes_cli/test_gateway_platform_gating.py @@ -15,6 +15,67 @@ import sys +# Every built-in adapter under gateway/platforms/ that is expected to have +# a picker entry today. This intentionally excludes dingtalk/feishu/wecom/ +# wecom_callback — those were dropped by the same e39b468 refactor but are +# tracked and restored separately (see .plans/missing-platform-plugin-shims.md +# and the PR that follows it); asserting them here would make this test +# depend on merge order between unrelated PRs. +BUILTIN_ADAPTER_KEYS = frozenset( + { + "telegram", + "slack", + "matrix", + "whatsapp", + "email", + "sms", + "mattermost", + "signal", + "weixin", + "bluebubbles", + "qqbot", + "yuanbao", + } +) + + +class TestBuiltinAdaptersStayInPicker: + def test_every_builtin_adapter_key_is_in_all_platforms(self, monkeypatch): + """Regression guard for e39b468-style silent deletions. + + A future refactor that deletes a _PLATFORMS entry (or claims a + built-in "moved to plugins" without actually creating the plugin) + should fail CI here instead of shipping a picker that's silently + missing a platform. + """ + import hermes_cli.gateway as gateway_mod + + monkeypatch.setattr(gateway_mod.sys, "platform", "linux") + keys = {p["key"] for p in gateway_mod._all_platforms()} + missing = BUILTIN_ADAPTER_KEYS - keys + assert not missing, ( + f"Built-in adapter(s) {sorted(missing)} have no _all_platforms() " + "entry — they exist in gateway/platforms/ but are invisible to " + "`hermes setup gateway`." + ) + + def test_telegram_slack_matrix_use_bespoke_setup_fn(self, monkeypatch): + """Matrix's "leave the token empty for password login" path is real. + + The generic _setup_standard_platform() fallback treats the first + `vars` entry (token_var) as mandatory and aborts the whole wizard + if left empty — which would silently break Matrix's documented + password-login path. These three must resolve to their bespoke + hermes_cli.setup functions, not fall through to the generic flow. + """ + import hermes_cli.gateway as gateway_mod + from hermes_cli import setup as _s + + assert gateway_mod._builtin_setup_fn("telegram") is _s._setup_telegram + assert gateway_mod._builtin_setup_fn("slack") is _s._setup_slack + assert gateway_mod._builtin_setup_fn("matrix") is _s._setup_matrix + + class TestMatrixHiddenOnWindows: def test_matrix_present_on_linux(self, monkeypatch): """Sanity: matrix is still in the picker on Linux/macOS."""