feat(discord): interactive components — buttons, select menus, REST + WebSocket paths#19413
Conversation
f8cf169 to
ca26919
Compare
|
Thanks for this — solid feature with good test coverage. Before we salvage onto current main, two changes we'd like you to make: 1. Gate the Right now the 2. Wrap the
We'll handle the Discord docs page update ourselves during salvage. The deferred items in your "Out of Scope" list (modals, user/role/channel selects, ephemeral) are fine to leave for follow-ups. Once those two changes are pushed we'll cherry-pick onto current main and merge — no need to rebase the branch yourself. |
… WebSocket paths Add Discord interactive UI components (Message Component v1) support to the send_message tool, enabling agents to send messages with clickable buttons and string-select menus. Component interactions route back to the agent as MessageEvent instances. Two send paths: - WebSocket (discord.py View): interactions handled automatically - REST (aiohttp): new on_interaction handler acknowledges and routes orphaned interactions via ComponentStore Changes: - gateway/platforms/discord_components.py (NEW): ComponentStore, AgentComponentView, build_view_from_spec(), helpers - gateway/platforms/discord.py: _normalize_component_spec(), on_interaction handler for REST-sent components, View attachment on channel.send() - tools/send_message_tool.py: components schema, _build_discord_components() REST builder, ComponentStore registration after REST send - tests/gateway/test_discord_components.py (NEW): 62 tests across 10 classes covering all public functions and edge cases
…iew_store access - Strip 'components' property from send_message schema when Discord is not a connected platform, avoiding permanent token tax for non-Discord users (follows browser_navigate web-tools stripping pattern) - Wrap _view_store._views access in on_interaction with try/except (AttributeError, KeyError) so discord.py internal API changes don't silently break component routing
b775560 to
2c15df5
Compare
… gating - Deep-copy schema before stripping components to avoid mutating the registry's base schema (components would vanish for the entire process lifetime after first call with Discord disconnected) - Include Discord connectivity status in cache key so toggling Discord correctly invalidates cached tool definitions - Extract helpers (_discord_components_schema_enabled, _without_send_message_components_schema) for testability - Add 3 tests covering mutation safety, cache invalidation, and schema restoration on Discord reconnect Based on Codex review feedback for PR NousResearch#19413. Co-authored-by: Codex <codex@openai.com>
|
Hey, thanks for reviewing this. Pushed the changes from your feedback. In addition, Fixed a bug where the Let me know if you spot anything else. |
…active components) PR NousResearch#19413 (TerminalSausage, "interactive components — buttons, select menus, REST + WebSocket paths") is being salvaged by maintainer onto current main. That PR owns Discord button emission + interaction dispatch via the existing send_message tool extended with a `components` schema. To avoid an on_interaction handler conflict (discord.py allows only one registered handler per event) and reduce review surface, this PR's button-specific wiring is removed. Removed: - tools/discord_button_tool.py (outbound discord_send_button_message tool) - tests/tools/test_discord_button_message.py - SkillButtonView class in gateway/platforms/discord_interactions.py - handle_skill_button_interaction method - _dispatch_synthetic method (button-only) - _build_button_payload method (button-only) - on_interaction event registration in gateway/platforms/discord.py - Button-specific tests in tests/gateway/test_discord_interactions.py - Button outbound sections in docs/migration/triggers-v1.md - Button send/receive sections in CONTRIBUTING.md - discord_button_tool entry in tests/tools/test_registry.py Retained as framework extension points: - metadata.hermes.triggers.button schema (agent/skill_utils.py) - button event matcher in gateway/skill_resolver.py - make_skill_custom_id / is_skill_custom_id / SKILL_CUSTOM_ID_PREFIX helpers in gateway/platforms/discord_interactions.py - button trigger example in CONTRIBUTING.md (now annotated as bridge target) Rationale: a follow-up PR can plug PR NousResearch#19413's component dispatch into resolve_event_skills('button', payload, skills) via the retained helpers, completing the round-trip from skill declaration → component emission → click → skill dispatch. This PR now covers: skill_resolver + frontmatter parser + Discord inbound reaction routing + Discord mention routing + outbound reaction tools (discord_add_reaction / discord_remove_reaction) + Feishu BC integration. Tests: 132 pass across the trigger framework + reaction tools + event counters + cache-first + mention routing suites. 🤖 Generated with Claude Code Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
What does this PR do?
Add Discord Message Component v1 support to the
send_messagetool, enabling agents to send messages with clickable buttons and string-select menus. Component interactions route back to the agent asMessageEventinstances.Two send paths are supported:
discord.pyattaches adiscord.ui.Viewtochannel.send(), interactions handled automatically viaAgentComponentView_build_discord_components()builds raw JSON payloads; anon_interactionhandler acknowledges and routes orphaned interactions viaComponentStoreRelated Issue
None — feature addition.
Type of Change
Changes Made
gateway/platforms/discord_components.py(NEW) —ComponentStore,AgentComponentView,build_view_from_spec(), helpers (_truncate_custom_id,_format_interaction_text)gateway/platforms/discord.py—_normalize_component_spec()format adapter,on_interactionREST handler, View attachment insend()tools/send_message_tool.py—componentsJSON schema,_build_discord_components()REST builder,_prefix_custom_id(),ComponentStoreregistration after REST sendtests/gateway/test_discord_components.py(NEW) — 62 tests across 10 classes covering all public functions and edge casesHow to Test
pytest tests/gateway/test_discord_components.py -v(62 tests)send_messagewithcomponentsparameter to send button/select messages to a Discord channelMessageEventinstancescustom_id), >5 row truncation, label/custom_id length clamping, empty component specsChecklist
Code
feat(discord):,style(discord):)pytest tests/ -qand all tests passDocumentation & Housekeeping
docs/, docstrings) — N/A (no user-facing docs changes needed; tool schema is self-documenting)cli-config.yaml.exampleif I added/changed config keys — N/A (no new config keys)CONTRIBUTING.mdorAGENTS.mdif I changed architecture or workflows — N/A (additive change, no architecture changes)send_message)🔧 Implementation Details
Features
urlfield, nocustom_id)min_values/max_values, placeholder texthermes:namespace to avoid collisionsTest Coverage (62 tests)
TestTruncateCustomIdTestFormatInteractionTextTestComponentStoreTestIsAgentComponentTestBuildViewFromSpecTestAgentComponentViewBuildFromSpecTestAgentComponentViewMakeButtonTestAgentComponentViewMakeSelectTestBuildDiscordComponentsTestNormalizeComponentSpecsend_messageSchema Addition{ "components": [ { "components": [ { "type": "button", "style": "primary", "label": "Approve", "custom_id": "approve" }, { "type": "button", "style": "link", "label": "Docs", "url": "https://..." } ] }, { "components": [ { "type": "string_select", "custom_id": "priority", "label": "Priority", "placeholder": "Choose...", "options": [ { "label": "High", "value": "high" }, { "label": "Low", "value": "low" } ] } ] } ] }Out of Scope (Future Work)
on_interactionasync handler — requires complex Discord mock setup)