diff --git a/src/infra/exec-approval-forwarder.test.ts b/src/infra/exec-approval-forwarder.test.ts index fa0c6c536fa5..fc9afb1e537b 100644 --- a/src/infra/exec-approval-forwarder.test.ts +++ b/src/infra/exec-approval-forwarder.test.ts @@ -137,6 +137,34 @@ describe("exec approval forwarder", () => { expect(getFirstDeliveryText(deliver)).toContain("Command:\n```\necho `uname`\necho done\n```"); }); + it("skips discord forwarding when discord exec approvals target channel", async () => { + vi.useFakeTimers(); + const deliver = vi.fn().mockResolvedValue([]); + const cfg = { + approvals: { exec: { enabled: true, mode: "session" } }, + channels: { + discord: { + execApprovals: { + enabled: true, + target: "channel", + approvers: ["123"], + }, + }, + }, + } as OpenClawConfig; + + const forwarder = createExecApprovalForwarder({ + getConfig: () => cfg, + deliver, + nowMs: () => 1000, + resolveSessionTarget: () => ({ channel: "discord", to: "channel:123" }), + }); + + await forwarder.handleRequested(baseRequest); + + expect(deliver).not.toHaveBeenCalled(); + }); + it("uses a longer fence when command already contains triple backticks", async () => { vi.useFakeTimers(); const deliver = vi.fn().mockResolvedValue([]); diff --git a/src/infra/exec-approval-forwarder.ts b/src/infra/exec-approval-forwarder.ts index 51e785b60aa5..a72938132aa2 100644 --- a/src/infra/exec-approval-forwarder.ts +++ b/src/infra/exec-approval-forwarder.ts @@ -98,6 +98,15 @@ function buildTargetKey(target: ExecApprovalForwardTarget): string { return [channel, target.to, accountId, threadId].join(":"); } +function shouldSkipDiscordForwarding(cfg: OpenClawConfig): boolean { + const discordConfig = cfg.channels?.discord?.execApprovals; + if (!discordConfig?.enabled) { + return false; + } + const target = discordConfig.target ?? "dm"; + return target === "channel" || target === "both"; +} + function formatApprovalCommand(command: string): { inline: boolean; text: string } { if (!command.includes("\n") && !command.includes("`")) { return { inline: true, text: `\`${command}\`` }; @@ -265,7 +274,11 @@ export function createExecApprovalForwarder( } } - if (targets.length === 0) { + const filteredTargets = shouldSkipDiscordForwarding(cfg) + ? targets.filter((target) => normalizeMessageChannel(target.channel) !== "discord") + : targets; + + if (filteredTargets.length === 0) { return; } @@ -283,7 +296,7 @@ export function createExecApprovalForwarder( }, expiresInMs); timeoutId.unref?.(); - const pendingEntry: PendingApproval = { request, targets, timeoutId }; + const pendingEntry: PendingApproval = { request, targets: filteredTargets, timeoutId }; pending.set(request.id, pendingEntry); if (pending.get(request.id) !== pendingEntry) { @@ -293,7 +306,7 @@ export function createExecApprovalForwarder( const text = buildRequestMessage(request, nowMs()); await deliverToTargets({ cfg, - targets, + targets: filteredTargets, text, deliver, shouldSend: () => pending.get(request.id) === pendingEntry,