文件最后提交记录最后更新时间
refactor(gateway): migrate Discord adapter to bundled plugin (full Teams parity) First migration of an existing built-in platform adapter to the plugin system established by IRC / Teams / LINE / Google Chat. Closes #24325; advances the umbrella refactor in #3823. Matches Teams' shape exactly — adapter under plugins/platforms/discord/ with the standard __init__.py / adapter.py / plugin.yaml shell, register(ctx) entry point, **no back-compat shim** at the old import path, and full parity for the four hooks Teams uses plus the apply_yaml_config_fn hook that landed in #25443 (the Discord plugin is the first consumer of that hook): * standalone_sender_fn — out-of-process cron delivery via REST API * setup_fn — interactive hermes setup gateway wizard * apply_yaml_config_fn — translate config.yaml discord: keys into DISCORD_* env vars (replaces the hardcoded block in gateway/config.py) * is_connected — declares connection state from DISCORD_BOT_TOKEN * check_fn — lazy-installs discord.py on demand * plus allowed_users_env, allow_all_env, cron_deliver_env_var, max_message_length, emoji, required_env, install_hint * gateway/platforms/discord.py (5,101 LOC) → plugins/platforms/discord/adapter.py (git rename, R090). * New plugins/platforms/discord/{__init__.py, plugin.yaml} with requires_env / optional_env declarations. * Append register(ctx) block + new hook implementations (_standalone_send, interactive_setup, _apply_yaml_config, _clean_discord_user_ids, _is_connected, _build_adapter, plus helpers _DISCORD_CHANNEL_TYPE_PROBE_CACHE etc.) to the adapter. * Replace the Platform.DISCORD elif branch in GatewayRunner._create_adapter() (−9 LOC) with a generic post-creation hook (+6 LOC) in the registry path: any plugin adapter that declares a gateway_runner attribute now gets it auto-injected. Webhook's built-in branch is unchanged (it doesn't go through the registry path). * Move _send_discord (190 LOC) and helpers (_DISCORD_CHANNEL_TYPE_PROBE_CACHE, _remember_channel_is_forum, _probe_is_forum_cached, _derive_forum_thread_name) from tools/send_message_tool.py into the plugin as _standalone_send. * Wire via standalone_sender_fn=_standalone_send (Teams pattern; same gap fixed in #21804 for other plugin platforms). * Replace the Discord elif in tools/send_message_tool.py _send_to_platform with a 10-line registry-hook dispatch. * Drop the DiscordAdapter import and the Platform.DISCORD: DiscordAdapter.MAX_MESSAGE_LENGTH _MAX_LENGTHS entry — the registry's max_message_length=2000 covers it. * Move _setup_discord and _clean_discord_user_ids (68 LOC) from hermes_cli/setup.py into the plugin as interactive_setup. * Wire via setup_fn=interactive_setup. CLI helpers (prompt, print_info, etc.) are lazy-imported so the plugin's module-load surface stays minimal. * Remove "discord": _s._setup_discord from hermes_cli/gateway.py::_builtin_setup_fn. * Remove the entire 32-line _PLATFORMS["discord"] static dict entry — Discord's setup metadata is now discovered dynamically via _all_platforms() from the registry entry. * Move the 59-line discord_cfg YAML→env bridge from gateway/config.py::load_gateway_config() into the plugin as _apply_yaml_config. Covers require_mention, thread_require_mention, free_response_channels, auto_thread, reactions, ignored_channels, allowed_channels, no_thread_channels, ``allow_mentions.{everyone,roles,users, replied_user}, and reply_to_mode`` (including the YAML 1.1 off-as-False coercion and the extra.reply_to_mode fallback). * Wire via apply_yaml_config_fn=_apply_yaml_config. * The hook runs BEFORE _apply_env_overrides and after the generic shared-key loop, exactly as documented in website/docs/developer-guide/adding-platform-adapters.md. * Behavior is preserved exactly — every assignment still uses not os.getenv(...) guards so env vars take precedence over YAML. All 78 references to the old import path are rewritten — no back-compat shim: * 51 from gateway.platforms.discord import Xfrom plugins.platforms.discord.adapter import X * 5 import gateway.platforms.discord as discord_platformimport plugins.platforms.discord.adapter as discord_platform * 1 from gateway.platforms import discord as discord_modfrom plugins.platforms.discord import adapter as discord_mod * 21 mock.patch("gateway.platforms.discord.X") strings → mock.patch("plugins.platforms.discord.adapter.X") * 1 docstring reference in hermes_cli/commands.py * 1 import in tools/send_message_tool.py (now removed entirely) The import-safety test in tests/gateway/test_discord_imports.py is updated to purge the new canonical module name from sys.modules. **38 files changed, +621 / −473** — net positive due to the YAML hook implementation (89 new LOC in the plugin trading for 59 deleted in core), but every line moved has a clear plugin home now. The git rename is detected at R090 because the adapter gained ~340 LOC of moved-in hook implementations (_standalone_send + interactive_setup + _apply_yaml_config + helpers). * All 568 Discord-specific tests pass across 25 test_discord_*.py files plus voice/send/text-batching/reload-skills/stream-consumer/ integration tests. * All 147 tests in the YAML-touching subset (test_discord_reply_mode, test_discord_free_response, test_discord_allowed_channels, test_discord_allowed_mentions, test_discord_channel_controls, test_discord_reactions, test_discord_thread_persistence, test_runtime_footer) pass — this is the strongest signal that the YAML→env hook behaves identically to the legacy block. * Broader gateway/cron/integration sweep (1297 tests) introduces zero new failures vs main. Pre-existing failures in tests/gateway/test_tts_media_routing.py and tests/e2e/test_platform_commands.py reproduce identically on the unchanged main revision. * Plugin discovery sanity check confirms Discord registers alongside the other four platform plugins: Registered platforms: ['discord', 'google_chat', 'irc', 'line', 'teams'] These Discord-shaped tendrils in core were **deliberately not moved** — they are generic platform-registry concerns affecting every platform, not Discord-specific: * gateway/config.py:1205 DISCORD_BOT_TOKEN → config.token env enablement — same shape Telegram has. The existing env_enablement_fn registry hook only seeds extra, not .token, so it can't replace this without an adapter refactor to read from extra["bot_token"]. * gateway/run.py voice-mode hooks (self.adapters.get(Platform.DISCORD) for start_voice_mode/stop_voice_mode), role-based auth, DISCORD_ALLOW_BOTS branch in _is_user_authorized, _UPDATE_ALLOWED_PLATFORMS frozenset, and the per-platform allowlist maps — generic platform-registry concerns. * Platform.DISCORD enum literal — stable identifier used as dict keys throughout the codebase; removing it is a separate refactor with no real benefit. * tools/discord_tool.py and tools/environments/local.py — first-class agent tools and env-passthrough config, neither is the gateway adapter. Each of these is worth its own scoping issue when the time comes. 13 天前
fix(gateway,discord-plugin): _platform_status must respect is_connected=False, not silently fall back to check_fn Two bugs surfaced by PR #24356 migrating Discord into the registry: 1. plugins/platforms/discord/adapter.py::_is_connected — read DISCORD_BOT_TOKEN via hermes_cli.gateway.get_env_value (the abstraction tests patch) instead of os.getenv directly. The legacy non-registry path used get_env_value; bypassing it broke test_setup_openclaw_migration which patches gateway_mod.get_env_value to simulate a hermetic env. 2. hermes_cli/gateway.py::_platform_status — when entry.is_connected is defined and returns False, return 'not configured' immediately. Don't fall back to entry.check_fn(), which would let 'SDK is installed' override 'no token configured' and incorrectly report the platform as ready. The fallback to check_fn is the right behaviour only when is_connected is None (not registered). Fixes 5 test failures observed on CI for PR #24356: - tests/hermes_cli/test_setup.py::test_setup_gateway_skips_service_install_when_systemctl_missing - tests/hermes_cli/test_setup.py::test_setup_gateway_in_container_shows_docker_guidance - tests/hermes_cli/test_setup_irc.py::TestIRCGatewaySetupFreshInstall::test_setup_gateway_irc_counts_as_messaging_platform - tests/hermes_cli/test_setup_openclaw_migration.py::TestGetSectionConfigSummary::test_gateway_returns_none_without_tokens - tests/hermes_cli/test_setup_openclaw_migration.py::TestSetupWizardSkipsConfiguredSections::test_sections_skipped_when_migration_imported_settings Same _platform_status bug exists for sibling plugin platforms (teams, google_chat) whose check_fn returns true on SDK install alone; their tests just never exercised the registry path before. The bug only became test-visible when Discord migrated into the registry. Validation: 11,167 tests across tests/gateway/ + tests/cron/ + tests/tools/test_send_message_tool.py + tests/hermes_cli/ pass with zero failures. 13 天前
refactor(gateway): migrate Discord adapter to bundled plugin (full Teams parity) First migration of an existing built-in platform adapter to the plugin system established by IRC / Teams / LINE / Google Chat. Closes #24325; advances the umbrella refactor in #3823. Matches Teams' shape exactly — adapter under plugins/platforms/discord/ with the standard __init__.py / adapter.py / plugin.yaml shell, register(ctx) entry point, **no back-compat shim** at the old import path, and full parity for the four hooks Teams uses plus the apply_yaml_config_fn hook that landed in #25443 (the Discord plugin is the first consumer of that hook): * standalone_sender_fn — out-of-process cron delivery via REST API * setup_fn — interactive hermes setup gateway wizard * apply_yaml_config_fn — translate config.yaml discord: keys into DISCORD_* env vars (replaces the hardcoded block in gateway/config.py) * is_connected — declares connection state from DISCORD_BOT_TOKEN * check_fn — lazy-installs discord.py on demand * plus allowed_users_env, allow_all_env, cron_deliver_env_var, max_message_length, emoji, required_env, install_hint * gateway/platforms/discord.py (5,101 LOC) → plugins/platforms/discord/adapter.py (git rename, R090). * New plugins/platforms/discord/{__init__.py, plugin.yaml} with requires_env / optional_env declarations. * Append register(ctx) block + new hook implementations (_standalone_send, interactive_setup, _apply_yaml_config, _clean_discord_user_ids, _is_connected, _build_adapter, plus helpers _DISCORD_CHANNEL_TYPE_PROBE_CACHE etc.) to the adapter. * Replace the Platform.DISCORD elif branch in GatewayRunner._create_adapter() (−9 LOC) with a generic post-creation hook (+6 LOC) in the registry path: any plugin adapter that declares a gateway_runner attribute now gets it auto-injected. Webhook's built-in branch is unchanged (it doesn't go through the registry path). * Move _send_discord (190 LOC) and helpers (_DISCORD_CHANNEL_TYPE_PROBE_CACHE, _remember_channel_is_forum, _probe_is_forum_cached, _derive_forum_thread_name) from tools/send_message_tool.py into the plugin as _standalone_send. * Wire via standalone_sender_fn=_standalone_send (Teams pattern; same gap fixed in #21804 for other plugin platforms). * Replace the Discord elif in tools/send_message_tool.py _send_to_platform with a 10-line registry-hook dispatch. * Drop the DiscordAdapter import and the Platform.DISCORD: DiscordAdapter.MAX_MESSAGE_LENGTH _MAX_LENGTHS entry — the registry's max_message_length=2000 covers it. * Move _setup_discord and _clean_discord_user_ids (68 LOC) from hermes_cli/setup.py into the plugin as interactive_setup. * Wire via setup_fn=interactive_setup. CLI helpers (prompt, print_info, etc.) are lazy-imported so the plugin's module-load surface stays minimal. * Remove "discord": _s._setup_discord from hermes_cli/gateway.py::_builtin_setup_fn. * Remove the entire 32-line _PLATFORMS["discord"] static dict entry — Discord's setup metadata is now discovered dynamically via _all_platforms() from the registry entry. * Move the 59-line discord_cfg YAML→env bridge from gateway/config.py::load_gateway_config() into the plugin as _apply_yaml_config. Covers require_mention, thread_require_mention, free_response_channels, auto_thread, reactions, ignored_channels, allowed_channels, no_thread_channels, ``allow_mentions.{everyone,roles,users, replied_user}, and reply_to_mode`` (including the YAML 1.1 off-as-False coercion and the extra.reply_to_mode fallback). * Wire via apply_yaml_config_fn=_apply_yaml_config. * The hook runs BEFORE _apply_env_overrides and after the generic shared-key loop, exactly as documented in website/docs/developer-guide/adding-platform-adapters.md. * Behavior is preserved exactly — every assignment still uses not os.getenv(...) guards so env vars take precedence over YAML. All 78 references to the old import path are rewritten — no back-compat shim: * 51 from gateway.platforms.discord import Xfrom plugins.platforms.discord.adapter import X * 5 import gateway.platforms.discord as discord_platformimport plugins.platforms.discord.adapter as discord_platform * 1 from gateway.platforms import discord as discord_modfrom plugins.platforms.discord import adapter as discord_mod * 21 mock.patch("gateway.platforms.discord.X") strings → mock.patch("plugins.platforms.discord.adapter.X") * 1 docstring reference in hermes_cli/commands.py * 1 import in tools/send_message_tool.py (now removed entirely) The import-safety test in tests/gateway/test_discord_imports.py is updated to purge the new canonical module name from sys.modules. **38 files changed, +621 / −473** — net positive due to the YAML hook implementation (89 new LOC in the plugin trading for 59 deleted in core), but every line moved has a clear plugin home now. The git rename is detected at R090 because the adapter gained ~340 LOC of moved-in hook implementations (_standalone_send + interactive_setup + _apply_yaml_config + helpers). * All 568 Discord-specific tests pass across 25 test_discord_*.py files plus voice/send/text-batching/reload-skills/stream-consumer/ integration tests. * All 147 tests in the YAML-touching subset (test_discord_reply_mode, test_discord_free_response, test_discord_allowed_channels, test_discord_allowed_mentions, test_discord_channel_controls, test_discord_reactions, test_discord_thread_persistence, test_runtime_footer) pass — this is the strongest signal that the YAML→env hook behaves identically to the legacy block. * Broader gateway/cron/integration sweep (1297 tests) introduces zero new failures vs main. Pre-existing failures in tests/gateway/test_tts_media_routing.py and tests/e2e/test_platform_commands.py reproduce identically on the unchanged main revision. * Plugin discovery sanity check confirms Discord registers alongside the other four platform plugins: Registered platforms: ['discord', 'google_chat', 'irc', 'line', 'teams'] These Discord-shaped tendrils in core were **deliberately not moved** — they are generic platform-registry concerns affecting every platform, not Discord-specific: * gateway/config.py:1205 DISCORD_BOT_TOKEN → config.token env enablement — same shape Telegram has. The existing env_enablement_fn registry hook only seeds extra, not .token, so it can't replace this without an adapter refactor to read from extra["bot_token"]. * gateway/run.py voice-mode hooks (self.adapters.get(Platform.DISCORD) for start_voice_mode/stop_voice_mode), role-based auth, DISCORD_ALLOW_BOTS branch in _is_user_authorized, _UPDATE_ALLOWED_PLATFORMS frozenset, and the per-platform allowlist maps — generic platform-registry concerns. * Platform.DISCORD enum literal — stable identifier used as dict keys throughout the codebase; removing it is a separate refactor with no real benefit. * tools/discord_tool.py and tools/environments/local.py — first-class agent tools and env-passthrough config, neither is the gateway adapter. Each of these is worth its own scoping issue when the time comes. 13 天前