feat(discord): route GUILD_MEMBER_ADD events to agents with optional welcome trigger#32306
feat(discord): route GUILD_MEMBER_ADD events to agents with optional welcome trigger#32306pdd-cli wants to merge 2 commits intoopenclaw:mainfrom
Conversation
Greptile SummaryThis PR adds a Key points:
Confidence Score: 4/5
Last reviewed commit: 15c1036 |
| const welcomeChannelId = | ||
| guildInfo?.memberJoinChannel?.trim() || | ||
| (typeof data.guild.systemChannelId === "string" ? data.guild.systemChannelId.trim() : null) || | ||
| null; |
There was a problem hiding this comment.
Missing null-guard on data.guild when reading systemChannelId
A few lines earlier the same object is accessed with optional chaining (data.guild?.name on line 678, data.guild?.id on line 686), making it clear that the type of data.guild is nullable. The direct property read data.guild.systemChannelId on line 702 will throw a TypeError at runtime if data.guild is null or undefined.
In practice the control flow guarantees non-null here (a null guild causes resolveDiscordGuildEntry to return null, which then defaults mode to "off" and returns early), but TypeScript's static analysis doesn't track that invariant. This will likely produce a strict-null type error and is a fragile assumption.
| const welcomeChannelId = | |
| guildInfo?.memberJoinChannel?.trim() || | |
| (typeof data.guild.systemChannelId === "string" ? data.guild.systemChannelId.trim() : null) || | |
| null; | |
| const welcomeChannelId = | |
| guildInfo?.memberJoinChannel?.trim() || | |
| data.guild?.systemChannelId?.trim() || | |
| null; |
Prompt To Fix With AI
This is a comment left during a code review.
Path: src/discord/monitor/listeners.ts
Line: 700-703
Comment:
**Missing null-guard on `data.guild` when reading `systemChannelId`**
A few lines earlier the same object is accessed with optional chaining (`data.guild?.name` on line 678, `data.guild?.id` on line 686), making it clear that the type of `data.guild` is nullable. The direct property read `data.guild.systemChannelId` on line 702 will throw a `TypeError` at runtime if `data.guild` is `null` or `undefined`.
In practice the control flow guarantees non-null here (a null guild causes `resolveDiscordGuildEntry` to return `null`, which then defaults `mode` to `"off"` and returns early), but TypeScript's static analysis doesn't track that invariant. This will likely produce a strict-null type error and is a fragile assumption.
```suggestion
const welcomeChannelId =
guildInfo?.memberJoinChannel?.trim() ||
data.guild?.systemChannelId?.trim() ||
null;
```
How can I resolve this? If you propose a fix, please make it concise.There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 15c103648e
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
|
|
||
| const userTag = formatDiscordUserTag(user); | ||
| const text = `Discord member joined: ${userTag} joined ${guildSlug}`; | ||
| const contextKey = `discord:member-add:${data.guild?.id}:${user.id}`; |
There was a problem hiding this comment.
Scope member-add idempotency key by account or event instance
contextKey is reused as the agent call idempotencyKey, but its value only includes guild and user IDs. The gateway dedupes agent requests by agent:${idempotencyKey} in a global cache window, so distinct join events can be dropped (for example, a quick leave/rejoin of the same user, or two configured Discord accounts receiving the same guild join) even though they should each trigger a welcome run.
Useful? React with 👍 / 👎.
| accountId: params.accountId, | ||
| guildId: data.guild?.id, | ||
| memberRoleIds, | ||
| peer: null, |
There was a problem hiding this comment.
Preserve guild/channel session isolation for join-triggered runs
Resolving the route with peer: null collapses these join-triggered agent runs into the agent main session instead of a guild/channel-scoped session. That causes conversation state for welcome generation to be shared across unrelated guilds/channels, so prior joins in one guild can influence welcome messages delivered to another.
Useful? React with 👍 / 👎.
15c1036 to
2cc790e
Compare
Adds a DiscordMemberAddListener that fires when a user joins a Discord guild and delivers a system event to the matched agent session. Gated on the existing intents.guildMembers privileged intent flag (must also be enabled in the Discord Developer Portal). Opt-in per guild via memberJoinNotifications: 'on' (default: 'off' to avoid high-volume floods on existing deployments). Closes openclaw#23978
2cc790e to
5b4897f
Compare
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 5b4897fa0c
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
| const guildInfo = resolveDiscordGuildEntry({ guild: data.guild, guildEntries }); | ||
|
|
||
| // Default is off — must be explicitly enabled per guild | ||
| const mode = guildInfo?.memberJoinNotifications ?? "off"; |
There was a problem hiding this comment.
Resolve member-add guild by ID when guild cache is missing
This handler only resolves guildInfo from data.guild, then defaults memberJoinNotifications to "off" when that object is unavailable; in that case the function returns early and skips both system-event enqueue and welcome triggering even for guilds explicitly configured by ID. Other Discord ingress paths in this file already treat the guild object as optional and key off raw guild IDs, so this listener should also use an ID fallback (for routing/config lookup) instead of hard-depending on a hydrated guild object.
Useful? React with 👍 / 👎.
Closes #23978
Summary
DiscordMemberAddListenerthat fires when a user joins a Discord guild and delivers a system event to the matched agent session.intents.guildMembers: true(existing privileged intent flag — must also be enabled in the Discord Developer Portal).memberJoinNotifications: "on"(default:"off"to avoid floods on existing deployments).memberJoinChannelper guild: if set, triggers a gateway agent call to post a welcome message to that channel. Falls back to Discord'ssystemChannelIdif no explicit channel is configured.Config example
Test plan
memberJoinNotifications: "off"(default) — no event firedmemberJoinNotifications: "on"— system event delivered to matched agentmemberJoinChannelset — agent posts welcome to that channelmemberJoinChannelbut DiscordsystemChannelIdset — falls back correctly