diff --git a/CHANGELOG.md b/CHANGELOG.md index 5a57d4e4c1..9e58cc84de 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,6 +19,7 @@ Docs: https://docs.openclaw.ai ### Fixes +- Discord: add exec approval cleanup option to delete DMs after approval/denial/timeout. (#13205) Thanks @thewilloftheshadow. - Sessions: prune stale entries, cap session store size, rotate large stores, accept duration/size thresholds, default to warn-only maintenance, and prune cron run sessions after retention windows. (#13083) Thanks @skyfallsin, @Glucksberg, @gumadeiras. - CI: Implement pipeline and workflow order. Thanks @quotentiroler. - WhatsApp: preserve original filenames for inbound documents. (#12691) Thanks @akramcodez. diff --git a/docs/channels/discord.md b/docs/channels/discord.md index c520c16fdd..6e8cbd1bc5 100644 --- a/docs/channels/discord.md +++ b/docs/channels/discord.md @@ -356,7 +356,7 @@ ack reaction after the bot replies. - `roles` (role add/remove, default `false`) - `moderation` (timeout/kick/ban, default `false`) - `presence` (bot status/activity, default `false`) -- `execApprovals`: Discord-only exec approval DMs (button UI). Supports `enabled`, `approvers`, `agentFilter`, `sessionFilter`. +- `execApprovals`: Discord-only exec approval DMs (button UI). Supports `enabled`, `approvers`, `agentFilter`, `sessionFilter`, `cleanupAfterResolve`. Reaction notifications use `guilds..reactionNotifications`: diff --git a/src/config/types.discord.ts b/src/config/types.discord.ts index 8aaf1bca00..3935446896 100644 --- a/src/config/types.discord.ts +++ b/src/config/types.discord.ts @@ -95,6 +95,8 @@ export type DiscordExecApprovalConfig = { agentFilter?: string[]; /** Only forward approvals matching these session key patterns (substring or regex). */ sessionFilter?: string[]; + /** Delete approval DMs after approval, denial, or timeout. Default: false. */ + cleanupAfterResolve?: boolean; }; export type DiscordAgentComponentsConfig = { diff --git a/src/config/zod-schema.providers-core.ts b/src/config/zod-schema.providers-core.ts index 8dc2bff6a8..89a19e4138 100644 --- a/src/config/zod-schema.providers-core.ts +++ b/src/config/zod-schema.providers-core.ts @@ -308,6 +308,7 @@ export const DiscordAccountSchema = z approvers: z.array(z.union([z.string(), z.number()])).optional(), agentFilter: z.array(z.string()).optional(), sessionFilter: z.array(z.string()).optional(), + cleanupAfterResolve: z.boolean().optional(), }) .strict() .optional(), diff --git a/src/discord/monitor/agent-components.ts b/src/discord/monitor/agent-components.ts index 6d1939e3f9..39508423ec 100644 --- a/src/discord/monitor/agent-components.ts +++ b/src/discord/monitor/agent-components.ts @@ -323,7 +323,7 @@ export class AgentComponentButton extends Button { accountId: this.ctx.accountId, guildId: rawGuildId, peer: { - kind: isDirectMessage ? "dm" : "channel", + kind: isDirectMessage ? "direct" : "channel", id: isDirectMessage ? userId : channelId, }, parentPeer: parentId ? { kind: "channel", id: parentId } : undefined, @@ -489,7 +489,7 @@ export class AgentSelectMenu extends StringSelectMenu { accountId: this.ctx.accountId, guildId: rawGuildId, peer: { - kind: isDirectMessage ? "dm" : "channel", + kind: isDirectMessage ? "direct" : "channel", id: isDirectMessage ? userId : channelId, }, parentPeer: parentId ? { kind: "channel", id: parentId } : undefined, diff --git a/src/discord/monitor/exec-approvals.ts b/src/discord/monitor/exec-approvals.ts index 4b6fc546b6..294d79314a 100644 --- a/src/discord/monitor/exec-approvals.ts +++ b/src/discord/monitor/exec-approvals.ts @@ -432,7 +432,7 @@ export class DiscordExecApprovalHandler { logDebug(`discord exec approvals: resolved ${resolved.id} with ${resolved.decision}`); - await this.updateMessage( + await this.finalizeMessage( pending.discordChannelId, pending.discordMessageId, formatResolvedEmbed(request, resolved.decision, resolved.resolvedBy), @@ -456,13 +456,39 @@ export class DiscordExecApprovalHandler { logDebug(`discord exec approvals: timeout for ${approvalId}`); - await this.updateMessage( + await this.finalizeMessage( pending.discordChannelId, pending.discordMessageId, formatExpiredEmbed(request), ); } + private async finalizeMessage( + channelId: string, + messageId: string, + embed: ReturnType, + ): Promise { + if (!this.opts.config.cleanupAfterResolve) { + await this.updateMessage(channelId, messageId, embed); + return; + } + + try { + const { rest, request: discordRequest } = createDiscordClient( + { token: this.opts.token, accountId: this.opts.accountId }, + this.opts.cfg, + ); + + await discordRequest( + () => rest.delete(Routes.channelMessage(channelId, messageId)) as Promise, + "delete-approval", + ); + } catch (err) { + logError(`discord exec approvals: failed to delete message: ${String(err)}`); + await this.updateMessage(channelId, messageId, embed); + } + } + private async updateMessage( channelId: string, messageId: string,