From 2ea0ca08f67ecbbbfbee1aa12441f1e2a399bc6d Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Thu, 2 Apr 2026 18:09:08 +0100 Subject: [PATCH] test: add cross-provider approval availability coverage (#59776) (thanks @joelnishanth) --- CHANGELOG.md | 1 + .../discord/src/approval-native.test.ts | 41 ++++++++++++++++++ extensions/slack/src/approval-native.test.ts | 43 +++++++++++++++++++ 3 files changed, 85 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4b975c74444..32e6f5bd04c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -95,6 +95,7 @@ Docs: https://docs.openclaw.ai - Telegram/exec approvals: fall back to the origin session key for async approval followups and keep resume-failure status delivery sanitized so Telegram followups still land without leaking raw exec metadata. (#59351) Thanks @seonang. - Node-host/exec approvals: bind `pnpm dlx` invocations through the approval planner's mutable-script path so the effective runtime command is resolved for approval instead of being left unbound. (#58374) - Exec/node hosts: stop forwarding the gateway workspace cwd to remote node exec when no workdir was explicitly requested, so cross-platform node approvals fall back to the node default cwd instead of failing with `SYSTEM_RUN_DENIED`. (#58977) Thanks @Starhappysh. +- Exec approvals/channels: decouple initiating-surface approval availability from native delivery enablement so Telegram, Slack, and Discord still expose approvals when approvers exist and native target routing is configured separately. (#59776) Thanks @joelnishanth. ## 2026.4.2 diff --git a/extensions/discord/src/approval-native.test.ts b/extensions/discord/src/approval-native.test.ts index b955bba00cb..b2c89ea0e50 100644 --- a/extensions/discord/src/approval-native.test.ts +++ b/extensions/discord/src/approval-native.test.ts @@ -21,6 +21,47 @@ function writeStore(store: Record) { } describe("createDiscordNativeApprovalAdapter", () => { + it("keeps approval availability enabled when approvers exist but native delivery is off", () => { + const adapter = createDiscordNativeApprovalAdapter({ + enabled: false, + approvers: ["555555555"], + target: "channel", + } as never); + + expect( + adapter.auth?.getActionAvailabilityState?.({ + cfg: NATIVE_APPROVAL_CFG as never, + accountId: "main", + action: "approve", + }), + ).toEqual({ kind: "enabled" }); + expect( + adapter.native?.describeDeliveryCapabilities({ + cfg: NATIVE_APPROVAL_CFG as never, + accountId: "main", + approvalKind: "exec", + request: { + id: "approval-1", + request: { + command: "pwd", + turnSourceChannel: "discord", + turnSourceTo: "channel:123456789", + turnSourceAccountId: "main", + sessionKey: "agent:main:discord:channel:123456789", + }, + createdAtMs: 1, + expiresAtMs: 2, + }, + }), + ).toEqual({ + enabled: false, + preferredSurface: "origin", + supportsOriginSurface: true, + supportsApproverDmSurface: true, + notifyOriginWhenDmOnly: true, + }); + }); + it("honors ownerAllowFrom fallback when gating approval requests", () => { expect( shouldHandleDiscordApprovalRequest({ diff --git a/extensions/slack/src/approval-native.test.ts b/extensions/slack/src/approval-native.test.ts index 23124b68b30..fe18afd91f7 100644 --- a/extensions/slack/src/approval-native.test.ts +++ b/extensions/slack/src/approval-native.test.ts @@ -33,6 +33,49 @@ function writeStore(store: Record) { } describe("slack native approval adapter", () => { + it("keeps approval availability enabled when approvers exist but native delivery is off", () => { + const cfg = buildConfig({ + execApprovals: { + enabled: false, + approvers: ["U123APPROVER"], + target: "channel", + }, + }); + + expect( + slackNativeApprovalAdapter.auth?.getActionAvailabilityState?.({ + cfg, + accountId: "default", + action: "approve", + }), + ).toEqual({ kind: "enabled" }); + expect( + slackNativeApprovalAdapter.native?.describeDeliveryCapabilities({ + cfg, + accountId: "default", + approvalKind: "exec", + request: { + id: "req-disabled-1", + request: { + command: "echo hi", + turnSourceChannel: "slack", + turnSourceTo: "channel:C123", + turnSourceAccountId: "default", + sessionKey: "agent:main:slack:channel:c123", + }, + createdAtMs: 0, + expiresAtMs: 1000, + }, + }), + ).toEqual({ + enabled: false, + preferredSurface: "origin", + supportsOriginSurface: true, + supportsApproverDmSurface: true, + notifyOriginWhenDmOnly: true, + }); + }); + it("describes native slack approval delivery capabilities", () => { const capabilities = slackNativeApprovalAdapter.native?.describeDeliveryCapabilities({ cfg: buildConfig(),