Discord: add exec approval cleanup option (#13205)

This commit is contained in:
Shadow
2026-02-10 00:39:42 -06:00
committed by GitHub
parent 656a467518
commit 8ff1618bfc
6 changed files with 35 additions and 5 deletions
+1
View File
@@ -19,6 +19,7 @@ Docs: https://docs.openclaw.ai
### Fixes ### 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. - 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. - CI: Implement pipeline and workflow order. Thanks @quotentiroler.
- WhatsApp: preserve original filenames for inbound documents. (#12691) Thanks @akramcodez. - WhatsApp: preserve original filenames for inbound documents. (#12691) Thanks @akramcodez.
+1 -1
View File
@@ -356,7 +356,7 @@ ack reaction after the bot replies.
- `roles` (role add/remove, default `false`) - `roles` (role add/remove, default `false`)
- `moderation` (timeout/kick/ban, default `false`) - `moderation` (timeout/kick/ban, default `false`)
- `presence` (bot status/activity, 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.<id>.reactionNotifications`: Reaction notifications use `guilds.<id>.reactionNotifications`:
+2
View File
@@ -95,6 +95,8 @@ export type DiscordExecApprovalConfig = {
agentFilter?: string[]; agentFilter?: string[];
/** Only forward approvals matching these session key patterns (substring or regex). */ /** Only forward approvals matching these session key patterns (substring or regex). */
sessionFilter?: string[]; sessionFilter?: string[];
/** Delete approval DMs after approval, denial, or timeout. Default: false. */
cleanupAfterResolve?: boolean;
}; };
export type DiscordAgentComponentsConfig = { export type DiscordAgentComponentsConfig = {
+1
View File
@@ -308,6 +308,7 @@ export const DiscordAccountSchema = z
approvers: z.array(z.union([z.string(), z.number()])).optional(), approvers: z.array(z.union([z.string(), z.number()])).optional(),
agentFilter: z.array(z.string()).optional(), agentFilter: z.array(z.string()).optional(),
sessionFilter: z.array(z.string()).optional(), sessionFilter: z.array(z.string()).optional(),
cleanupAfterResolve: z.boolean().optional(),
}) })
.strict() .strict()
.optional(), .optional(),
+2 -2
View File
@@ -323,7 +323,7 @@ export class AgentComponentButton extends Button {
accountId: this.ctx.accountId, accountId: this.ctx.accountId,
guildId: rawGuildId, guildId: rawGuildId,
peer: { peer: {
kind: isDirectMessage ? "dm" : "channel", kind: isDirectMessage ? "direct" : "channel",
id: isDirectMessage ? userId : channelId, id: isDirectMessage ? userId : channelId,
}, },
parentPeer: parentId ? { kind: "channel", id: parentId } : undefined, parentPeer: parentId ? { kind: "channel", id: parentId } : undefined,
@@ -489,7 +489,7 @@ export class AgentSelectMenu extends StringSelectMenu {
accountId: this.ctx.accountId, accountId: this.ctx.accountId,
guildId: rawGuildId, guildId: rawGuildId,
peer: { peer: {
kind: isDirectMessage ? "dm" : "channel", kind: isDirectMessage ? "direct" : "channel",
id: isDirectMessage ? userId : channelId, id: isDirectMessage ? userId : channelId,
}, },
parentPeer: parentId ? { kind: "channel", id: parentId } : undefined, parentPeer: parentId ? { kind: "channel", id: parentId } : undefined,
+28 -2
View File
@@ -432,7 +432,7 @@ export class DiscordExecApprovalHandler {
logDebug(`discord exec approvals: resolved ${resolved.id} with ${resolved.decision}`); logDebug(`discord exec approvals: resolved ${resolved.id} with ${resolved.decision}`);
await this.updateMessage( await this.finalizeMessage(
pending.discordChannelId, pending.discordChannelId,
pending.discordMessageId, pending.discordMessageId,
formatResolvedEmbed(request, resolved.decision, resolved.resolvedBy), formatResolvedEmbed(request, resolved.decision, resolved.resolvedBy),
@@ -456,13 +456,39 @@ export class DiscordExecApprovalHandler {
logDebug(`discord exec approvals: timeout for ${approvalId}`); logDebug(`discord exec approvals: timeout for ${approvalId}`);
await this.updateMessage( await this.finalizeMessage(
pending.discordChannelId, pending.discordChannelId,
pending.discordMessageId, pending.discordMessageId,
formatExpiredEmbed(request), formatExpiredEmbed(request),
); );
} }
private async finalizeMessage(
channelId: string,
messageId: string,
embed: ReturnType<typeof formatExpiredEmbed>,
): Promise<void> {
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<void>,
"delete-approval",
);
} catch (err) {
logError(`discord exec approvals: failed to delete message: ${String(err)}`);
await this.updateMessage(channelId, messageId, embed);
}
}
private async updateMessage( private async updateMessage(
channelId: string, channelId: string,
messageId: string, messageId: string,