feat(discord): add agent-controlled interactive components#11254
Conversation
| // Only check guild allowlists if this is a guild interaction | ||
| if (rawGuildId) { | ||
| const channelConfig = resolveDiscordChannelConfigWithFallback({ | ||
| guildInfo, |
There was a problem hiding this comment.
DM policy bypass
Both component handlers only enforce allowlists when rawGuildId is present (if (rawGuildId) { ... }). In DMs (no guild_id), any user can click/select and enqueueSystemEvent(...) into the DM route (peer.kind="dm", id=userId) even if channels.discord.dm.enabled=false or dm.policy=pairing/allowFrom would normally restrict DM input. This makes agent components an unguarded DM input surface; it likely needs the same DM authorization/pairing gating as native commands.
Prompt To Fix With AI
This is a comment left during a code review.
Path: src/discord/monitor/agent-components.ts
Line: 176:179
Comment:
**DM policy bypass**
Both component handlers only enforce allowlists when `rawGuildId` is present (`if (rawGuildId) { ... }`). In DMs (no `guild_id`), any user can click/select and `enqueueSystemEvent(...)` into the DM route (`peer.kind="dm"`, `id=userId`) even if `channels.discord.dm.enabled=false` or `dm.policy=pairing`/`allowFrom` would normally restrict DM input. This makes agent components an unguarded DM input surface; it likely needs the same DM authorization/pairing gating as native commands.
How can I resolve this? If you propose a fix, please make it concise.| const guild = interaction.guild; | ||
| const guildInfo = resolveDiscordGuildEntry({ | ||
| guild: guild ?? undefined, | ||
| guildEntries: this.ctx.guildEntries, | ||
| }); |
There was a problem hiding this comment.
Guild allowlist skipped
guildInfo is derived from resolveDiscordGuildEntry({ guild: interaction.guild ?? undefined, ... }), but resolveDiscordGuildEntry returns null unless a Guild object is present. When interaction.guild is null (cache miss) but interaction.rawData.guild_id is set, guildInfo becomes null, so resolveDiscordChannelConfigWithFallback(...) can’t resolve channelUsers and the allowlist check is skipped. If the intent is to trust rawData.guild_id, this should key the lookup off that ID so unauthorized users can’t inject events during cold cache.
Prompt To Fix With AI
This is a comment left during a code review.
Path: src/discord/monitor/agent-components.ts
Line: 144:148
Comment:
**Guild allowlist skipped**
`guildInfo` is derived from `resolveDiscordGuildEntry({ guild: interaction.guild ?? undefined, ... })`, but `resolveDiscordGuildEntry` returns `null` unless a `Guild` object is present. When `interaction.guild` is `null` (cache miss) but `interaction.rawData.guild_id` is set, `guildInfo` becomes `null`, so `resolveDiscordChannelConfigWithFallback(...)` can’t resolve `channelUsers` and the allowlist check is skipped. If the intent is to trust `rawData.guild_id`, this should key the lookup off that ID so unauthorized users can’t inject events during cold cache.
How can I resolve this? If you propose a fix, please make it concise.| } | ||
|
|
||
| // Only check guild allowlists if this is a guild interaction | ||
| if (rawGuildId) { |
|
😍 |
|
Addressed Greptile feedback (commit 90afa4b) Added DM authorization checks that were missing:
This closes the authorization gap flagged in the review. DM interactions now follow the same policy as DM messages. |
📸 Live Test ScreenshotsScreenshots of the components rendering in Discord and the ephemeral acknowledgments are available in the PR thread. Here's what was tested: Test 1 — Button Styles: Primary (blue), Secondary (gray), Success (green), Danger (red), Link (opens URL) All 4 interaction events were received as system events in the agent session within <1 second of clicking. The ephemeral ✓ acknowledgments appear instantly and are only visible to the user who clicked. Event format examples:Tested on OpenClaw |
4620b6e to
c7cbda8
Compare
c7cbda8 to
e0a371f
Compare
|
Landed via temp rebase onto main.
Thanks @PLACEHOLDER_CONTRIB! |
|
Correction:
Thanks @thedudeabidesai! |
* 'main' of github.com:YanHaidao/clawdbot: (94 commits) fix(auto-reply): prevent sender spoofing in group prompts Discord: add exec approval cleanup option (openclaw#13205) CI: extend stale timelines to be contributor-friendly (openclaw#13209) fix: enforce Discord agent component DM auth (openclaw#11254) (thanks @thedudeabidesai) refactor(security,config): split oversized files (openclaw#13182) Commands: add commands.allowFrom config CI: configure stale automation fix(signal): enforce mention gating for group messages (openclaw#13124) fix(ui): prioritize displayName over label in webchat session picker (openclaw#13108) Chore: add testflight auto-response Docker: include A2UI sources for bundle (openclaw#13114) fix: unify session maintenance and cron run pruning (openclaw#13083) docs: expand vulnerability reporting guidelines in SECURITY.md docs: add vulnerability reporting guidelines to CONTRIBUTING.md refactor: consolidate fetchWithTimeout into shared utility fix(memory): default batch embeddings to off Improve code analyzer for independent packages, CI: only run release-check on push to main fix(tools): correct Grok response parsing for xAI Responses API (openclaw#13049) chore(deps): update dependencies, remove hono pinning Update contributing, deduplicate more functions ...
…thedudeabidesai) (cherry picked from commit 4537ebc)
…thedudeabidesai) (cherry picked from commit 4537ebc) # Conflicts: # CHANGELOG.md # src/discord/monitor/provider.ts
Summary
Add support for agent-controlled Discord buttons and select menus that inject system events into chat sessions when interacted with.
Features
agent:componentId=<id>agentsel:componentId=<id>[Discord component: <componentId> clicked by <username> (<userId>)][Discord select menu: <componentId> interacted by <username> (<userId>) (selected: <values>)]channels.discord.agentComponents.enabled(default: true)dmPolicyandallowFrom(same rules as message handler)Security
interaction.rawData.channel_id/guild_idinstead of potentially-null cached objectsdmPolicy(disabled/pairing/allowlist/open) andallowFromfor DM interactionsUse Case
Allows agents to create interactive UI components in Discord messages and receive click/selection events as context in the next turn. Example: meal planning confirmations, quick-action buttons, multi-select preferences.
Live Testing Results ✅
Tested end-to-end on a local OpenClaw instance running this branch:
Components Tested
System Events Received
All events include username, user ID, component ID, and selected values. The agent receives these as system events and can act on them in the next turn.
Build & Lint
pnpm build✅pnpm check(tsgo + lint + format) ✅pnpm test— 5381 passed (2 unrelated failures:security/audit.test.tslocal env + type coverage)Files Changed
src/discord/monitor/agent-components.tssrc/config/types.discord.tsagentComponentsconfig typesrc/discord/monitor/provider.tsallowFrom/dmPolicyNotes
Developed by The Dude 🎳 (AI Chief of Staff) with human oversight from @whistlermike. Code reviewed by OpenAI Codex 5.3 (3 rounds) — security fixes for customId parsing, guild_id resolution, and thread parent metadata. DM authorization gap caught by Greptile, fixed in follow-up commit.
Happy to iterate based on feedback. The Dude abides.