mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-06-01 07:01:41 +00:00
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 X`` → ``from plugins.platforms.discord.adapter import X`` * 5 ``import gateway.platforms.discord as discord_platform`` → ``import plugins.platforms.discord.adapter as discord_platform`` * 1 ``from gateway.platforms import discord as discord_mod`` → ``from 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. |
||
|---|---|---|
| .. | ||
| platforms | ||
| __init__.py | ||
| _plugin_adapter_loader.py | ||
| conftest.py | ||
| feishu_helpers.py | ||
| restart_test_helpers.py | ||
| test_7100_transient_failure_transcript.py | ||
| test_active_session_text_merge.py | ||
| test_agent_cache.py | ||
| test_allowed_channels_widening.py | ||
| test_allowlist_startup_check.py | ||
| test_api_server.py | ||
| test_api_server_bind_guard.py | ||
| test_api_server_jobs.py | ||
| test_api_server_multimodal.py | ||
| test_api_server_normalize.py | ||
| test_api_server_runs.py | ||
| test_api_server_toolset.py | ||
| test_approve_deny_commands.py | ||
| test_auth_fallback.py | ||
| test_auto_continue.py | ||
| test_background_command.py | ||
| test_background_process_notifications.py | ||
| test_base_topic_sessions.py | ||
| test_bluebubbles.py | ||
| test_bundles_command.py | ||
| test_busy_session_ack.py | ||
| test_busy_session_auth_bypass.py | ||
| test_cancel_background_drain.py | ||
| test_channel_directory.py | ||
| test_clean_shutdown_marker.py | ||
| test_command_bypass_active_session.py | ||
| test_complete_path_at_filter.py | ||
| test_compress_command.py | ||
| test_compress_focus.py | ||
| test_compress_plugin_engine.py | ||
| test_config.py | ||
| test_config_cwd_bridge.py | ||
| test_config_env_bridge_authority.py | ||
| test_debug_command.py | ||
| test_delivery.py | ||
| test_destructive_slash_confirm.py | ||
| test_dingtalk.py | ||
| test_discord_allowed_channels.py | ||
| test_discord_allowed_mentions.py | ||
| test_discord_attachment_download.py | ||
| test_discord_bot_auth_bypass.py | ||
| test_discord_bot_filter.py | ||
| test_discord_channel_controls.py | ||
| test_discord_channel_prompts.py | ||
| test_discord_channel_skills.py | ||
| test_discord_clarify_buttons.py | ||
| test_discord_component_auth.py | ||
| test_discord_connect.py | ||
| test_discord_document_handling.py | ||
| test_discord_free_response.py | ||
| test_discord_imports.py | ||
| test_discord_lazy_install_views.py | ||
| test_discord_media_metadata.py | ||
| test_discord_model_picker.py | ||
| test_discord_opus.py | ||
| test_discord_race_polish.py | ||
| test_discord_reactions.py | ||
| test_discord_reply_mode.py | ||
| test_discord_roles_dm_scope.py | ||
| test_discord_send.py | ||
| test_discord_slash_auth.py | ||
| test_discord_slash_commands.py | ||
| test_discord_system_messages.py | ||
| test_discord_thread_persistence.py | ||
| test_display_config.py | ||
| test_dm_topics.py | ||
| test_document_cache.py | ||
| test_duplicate_reply_suppression.py | ||
| test_email.py | ||
| test_ephemeral_reply.py | ||
| test_extract_local_files.py | ||
| test_fallback_eviction.py | ||
| test_fast_command.py | ||
| test_feishu.py | ||
| test_feishu_approval_buttons.py | ||
| test_feishu_bot_admission.py | ||
| test_feishu_bot_auth_bypass.py | ||
| test_feishu_comment.py | ||
| test_feishu_comment_rules.py | ||
| test_feishu_onboard.py | ||
| test_fresh_reset_skill_injection.py | ||
| test_gateway_command_help.py | ||
| test_gateway_inactivity_timeout.py | ||
| test_gateway_shutdown.py | ||
| test_goal_max_turns_config.py | ||
| test_goal_status_notice.py | ||
| test_goal_verdict_send.py | ||
| test_google_chat.py | ||
| test_home_target_env_var.py | ||
| test_homeassistant.py | ||
| test_hooks.py | ||
| test_insights_unicode_flags.py | ||
| test_internal_event_bypass_pairing.py | ||
| test_interrupt_key_match.py | ||
| test_irc_adapter.py | ||
| test_kanban_notifier.py | ||
| test_keep_typing_timeout.py | ||
| test_line_plugin.py | ||
| test_load_transcript_db_only.py | ||
| test_matrix.py | ||
| test_matrix_exec_approval.py | ||
| test_matrix_mention.py | ||
| test_matrix_voice.py | ||
| test_mattermost.py | ||
| test_media_download_retry.py | ||
| test_media_extraction.py | ||
| test_memory_monitor.py | ||
| test_message_deduplicator.py | ||
| test_mirror.py | ||
| test_model_command_custom_providers.py | ||
| test_model_switch_persistence.py | ||
| test_msgraph_webhook.py | ||
| test_native_image_buffer_isolation.py | ||
| test_notice_delivery.py | ||
| test_pairing.py | ||
| test_pending_drain_no_recursion.py | ||
| test_pending_drain_race.py | ||
| test_pending_event_none.py | ||
| test_pii_redaction.py | ||
| test_platform_base.py | ||
| test_platform_connected_checkers.py | ||
| test_platform_http_client_limits.py | ||
| test_platform_reconnect.py | ||
| test_platform_registry.py | ||
| test_plugin_platform_interface.py | ||
| test_post_delivery_callback_chaining.py | ||
| test_pre_gateway_dispatch.py | ||
| test_proxy_mode.py | ||
| test_qqbot.py | ||
| test_queue_consumption.py | ||
| test_reasoning_command.py | ||
| test_reload_skills_command.py | ||
| test_reload_skills_discord_resync.py | ||
| test_replay_entry_fields.py | ||
| test_reply_to_injection.py | ||
| test_restart_drain.py | ||
| test_restart_notification.py | ||
| test_restart_redelivery_dedup.py | ||
| test_restart_resume_pending.py | ||
| test_resume_command.py | ||
| test_retry_replacement.py | ||
| test_retry_response.py | ||
| test_run_cleanup_progress.py | ||
| test_run_progress_interrupt.py | ||
| test_run_progress_topics.py | ||
| test_runner_fatal_adapter.py | ||
| test_runner_startup_failures.py | ||
| test_running_agent_session_toggles.py | ||
| test_runtime_env_reload_config_authority.py | ||
| test_runtime_footer.py | ||
| test_safe_adapter_disconnect.py | ||
| test_send_image_file.py | ||
| test_send_multiple_images.py | ||
| test_send_retry.py | ||
| test_send_voice_reply_notify.py | ||
| test_session.py | ||
| test_session_boundary_hooks.py | ||
| test_session_boundary_security_state.py | ||
| test_session_dm_thread_seeding.py | ||
| test_session_env.py | ||
| test_session_hygiene.py | ||
| test_session_info.py | ||
| test_session_list_allowed_sources.py | ||
| test_session_model_override_routing.py | ||
| test_session_model_reset.py | ||
| test_session_race_guard.py | ||
| test_session_reset_notify.py | ||
| test_session_split_brain_11016.py | ||
| test_session_state_cleanup.py | ||
| test_session_store_prune.py | ||
| test_setup_feishu.py | ||
| test_shared_group_sender_prefix.py | ||
| test_shutdown_cache_cleanup.py | ||
| test_shutdown_forensics.py | ||
| test_shutdown_memory_provider_messages.py | ||
| test_signal.py | ||
| test_signal_format.py | ||
| test_signal_rate_limit.py | ||
| test_simplex_plugin.py | ||
| test_slack.py | ||
| test_slack_approval_buttons.py | ||
| test_slack_channel_skills.py | ||
| test_slack_mention.py | ||
| test_slash_access.py | ||
| test_slash_access_dispatch.py | ||
| test_sms.py | ||
| test_sse_agent_cancel.py | ||
| test_ssl_certs.py | ||
| test_status.py | ||
| test_status_command.py | ||
| test_steer_command.py | ||
| test_step_callback_compat.py | ||
| test_sticker_cache.py | ||
| test_stream_consumer.py | ||
| test_stream_consumer_draft.py | ||
| test_stream_consumer_fresh_final.py | ||
| test_stream_consumer_thread_routing.py | ||
| test_stt_config.py | ||
| test_stuck_loop.py | ||
| test_teams.py | ||
| test_teams_pipeline_runtime_wiring.py | ||
| test_telegram_approval_buttons.py | ||
| test_telegram_audio_vs_voice.py | ||
| test_telegram_callback_auth_fail_closed.py | ||
| test_telegram_caption_merge.py | ||
| test_telegram_channel_posts.py | ||
| test_telegram_clarify_buttons.py | ||
| test_telegram_conflict.py | ||
| test_telegram_documents.py | ||
| test_telegram_format.py | ||
| test_telegram_forum_commands.py | ||
| test_telegram_group_gating.py | ||
| test_telegram_max_doc_bytes.py | ||
| test_telegram_mention_boundaries.py | ||
| test_telegram_model_picker.py | ||
| test_telegram_network.py | ||
| test_telegram_network_reconnect.py | ||
| test_telegram_noise_filter.py | ||
| test_telegram_photo_interrupts.py | ||
| test_telegram_progress_edit_transient.py | ||
| test_telegram_reactions.py | ||
| test_telegram_reply_mode.py | ||
| test_telegram_reply_quote.py | ||
| test_telegram_slash_confirm.py | ||
| test_telegram_text_batch_perf.py | ||
| test_telegram_text_batching.py | ||
| test_telegram_thread_fallback.py | ||
| test_telegram_topic_mode.py | ||
| test_telegram_webhook_secret.py | ||
| test_text_batching.py | ||
| test_title_command.py | ||
| test_transcript_offset.py | ||
| test_tts_media_routing.py | ||
| test_unauthorized_dm_behavior.py | ||
| test_unavailable_skill_hint.py | ||
| test_unknown_command.py | ||
| test_update_command.py | ||
| test_update_streaming.py | ||
| test_usage_command.py | ||
| test_verbose_command.py | ||
| test_vision_memory_leak.py | ||
| test_voice_command.py | ||
| test_voice_mode_platform_isolation.py | ||
| test_weak_credential_guard.py | ||
| test_webhook_adapter.py | ||
| test_webhook_deliver_only.py | ||
| test_webhook_dynamic_routes.py | ||
| test_webhook_integration.py | ||
| test_webhook_signature_rate_limit.py | ||
| test_wecom.py | ||
| test_wecom_callback.py | ||
| test_weixin.py | ||
| test_whatsapp_connect.py | ||
| test_whatsapp_formatting.py | ||
| test_whatsapp_group_gating.py | ||
| test_whatsapp_reply_prefix.py | ||
| test_ws_auth_retry.py | ||
| test_yolo_command.py | ||