Skip to content

feat(discord): add agent-controlled interactive components#11254

Merged
thewilloftheshadow merged 1 commit intoopenclaw:mainfrom
thedudeabidesai:feat/discord-agent-components
Feb 10, 2026
Merged

feat(discord): add agent-controlled interactive components#11254
thewilloftheshadow merged 1 commit intoopenclaw:mainfrom
thedudeabidesai:feat/discord-agent-components

Conversation

@thedudeabidesai
Copy link
Copy Markdown

@thedudeabidesai thedudeabidesai commented Feb 7, 2026

Summary

Add support for agent-controlled Discord buttons and select menus that inject system events into chat sessions when interacted with.

Features

  • AgentComponentButton: Handles button clicks with customId format agent:componentId=<id>
  • AgentSelectMenu: Handles select menu interactions with customId format agentsel:componentId=<id>
  • System events injected as: [Discord component: <componentId> clicked by <username> (<userId>)]
  • Select menus include selected values: [Discord select menu: <componentId> interacted by <username> (<userId>) (selected: <values>)]
  • Config option: channels.discord.agentComponents.enabled (default: true)
  • DM authorization enforced via dmPolicy and allowFrom (same rules as message handler)

Security

  • Channel spoofing prevention: Uses trusted interaction.rawData.channel_id / guild_id instead of potentially-null cached objects
  • User allowlist enforcement: Checks guild/channel allowlists before processing any component interaction
  • DM authorization: Enforces dmPolicy (disabled/pairing/allowlist/open) and allowFrom for DM interactions
  • Thread inheritance: Resolves parent channel config for thread-scoped allowlists

Use 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

Test Component Result
Button styles Primary, Secondary, Success, Danger, Link All render correctly ✅
Select menu Single-select with descriptions Works, selected value in event ✅
Combo message Buttons + Select in same message Both work independently ✅
Ephemeral ack Checkmark shown to user after click Clean ✅

System Events Received

[Discord component: style_danger clicked by whistlermike (848643907914563589)]
[Discord select menu: meal_choice interacted by whistlermike (848643907914563589) (selected: carbonara)]
[Discord component: menu_reject clicked by whistlermike (848643907914563589)]
[Discord select menu: portion_size interacted by whistlermike (848643907914563589) (selected: small)]

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.ts local env + type coverage)

Files Changed

File Change
src/discord/monitor/agent-components.ts New — Button + SelectMenu handlers, customId builders, event formatting
src/config/types.discord.ts Added agentComponents config type
src/discord/monitor/provider.ts Wire up components, pass allowFrom/dmPolicy

Notes

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.

@openclaw-barnacle openclaw-barnacle bot added the channel: discord Channel integration: discord label Feb 7, 2026
Copy link
Copy Markdown
Contributor

@greptile-apps greptile-apps bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

1 file reviewed, 2 comments

Edit Code Review Agent Settings | Greptile

Comment on lines +176 to +179
// Only check guild allowlists if this is a guild interaction
if (rawGuildId) {
const channelConfig = resolveDiscordChannelConfigWithFallback({
guildInfo,
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment on lines +144 to +148
const guild = interaction.guild;
const guildInfo = resolveDiscordGuildEntry({
guild: guild ?? undefined,
guildEntries: this.ctx.guildEntries,
});
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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) {
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@thewilloftheshadow
Copy link
Copy Markdown
Member

😍

@thewilloftheshadow thewilloftheshadow self-assigned this Feb 7, 2026
@thedudeabidesai
Copy link
Copy Markdown
Author

Addressed Greptile feedback (commit 90afa4b)

Added DM authorization checks that were missing:

  • Check dmPolicy before allowing DM component interactions
  • Enforce allowFrom list for DM users (when policy is not 'open')
  • Block interactions entirely when dmPolicy is 'disabled'

This closes the authorization gap flagged in the review. DM interactions now follow the same policy as DM messages.

@thedudeabidesai
Copy link
Copy Markdown
Author

📸 Live Test Screenshots

Screenshots 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)
Test 2 — Select Menu: Dropdown with meal options + descriptions, returns selected value
Test 3 — Combo: Buttons + Select menu in the same message, both fire independent events

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:

[Discord component: style_danger clicked by whistlermike (848643907914563589)]
[Discord select menu: meal_choice interacted by whistlermike (848643907914563589) (selected: carbonara)]

Tested on OpenClaw 2026.2.6-3 (this branch) with Discord bot gateway + Carbon component handler.

@thewilloftheshadow thewilloftheshadow force-pushed the feat/discord-agent-components branch from 4620b6e to c7cbda8 Compare February 10, 2026 06:21
@thewilloftheshadow thewilloftheshadow force-pushed the feat/discord-agent-components branch from c7cbda8 to e0a371f Compare February 10, 2026 06:26
@thewilloftheshadow thewilloftheshadow merged commit 4537ebc into openclaw:main Feb 10, 2026
3 checks passed
@thewilloftheshadow
Copy link
Copy Markdown
Member

Landed via temp rebase onto main.

  • Related tests: pnpm vitest run --config vitest.unit.config.ts src/discord/monitor/agent-components.test.ts
  • Land commit: PLACEHOLDER_LAND
  • Merge commit: PLACEHOLDER_MERGE

Thanks @PLACEHOLDER_CONTRIB!

@thewilloftheshadow
Copy link
Copy Markdown
Member

Correction:

  • Related tests: pnpm vitest run --config vitest.unit.config.ts src/discord/monitor/agent-components.test.ts
  • Land commit: 4537ebc
  • Merge commit: 4537ebc

Thanks @thedudeabidesai!

YanHaidao added a commit to YanHaidao/clawdbot that referenced this pull request Feb 10, 2026
* '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
  ...
Raycc-lang pushed a commit to Raycc-lang/openclaw that referenced this pull request Feb 12, 2026
hughdidit pushed a commit to hughdidit/DAISy-Agency that referenced this pull request Mar 1, 2026
hughdidit pushed a commit to hughdidit/DAISy-Agency that referenced this pull request Mar 3, 2026
…thedudeabidesai)

(cherry picked from commit 4537ebc)

# Conflicts:
#	CHANGELOG.md
#	src/discord/monitor/provider.ts
dustin-olenslager pushed a commit to dustin-olenslager/ironclaw-supreme that referenced this pull request Mar 24, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

channel: discord Channel integration: discord

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants