From ef3d2cc69285d6db0e31a220908d400ece2b6acd Mon Sep 17 00:00:00 2001 From: Gustavo Madeira Santana Date: Fri, 3 Apr 2026 13:26:55 -0400 Subject: [PATCH] fix: scope approval ambiguity to active accounts --- extensions/matrix/src/exec-approvals.test.ts | 56 +++++++++++++++++++ extensions/matrix/src/exec-approvals.ts | 12 +++- .../telegram/src/exec-approvals.test.ts | 51 +++++++++++++++++ extensions/telegram/src/exec-approvals.ts | 11 +++- 4 files changed, 128 insertions(+), 2 deletions(-) diff --git a/extensions/matrix/src/exec-approvals.test.ts b/extensions/matrix/src/exec-approvals.test.ts index 89c5e0cc7cf..79a0df17653 100644 --- a/extensions/matrix/src/exec-approvals.test.ts +++ b/extensions/matrix/src/exec-approvals.test.ts @@ -398,4 +398,60 @@ describe("matrix exec approvals", () => { }), ).toBe(false); }); + + it("allows unbound foreign-channel approvals when only one matrix account can handle them", () => { + const cfg = { + channels: { + matrix: { + accounts: { + default: { + homeserver: "https://matrix.example.org", + userId: "@bot-default:example.org", + accessToken: "tok-default", + execApprovals: { + enabled: true, + approvers: ["@owner:example.org"], + }, + }, + ops: { + homeserver: "https://matrix.example.org", + userId: "@bot-ops:example.org", + accessToken: "tok-ops", + execApprovals: { + enabled: false, + approvers: ["@owner:example.org"], + }, + }, + }, + }, + }, + } as OpenClawConfig; + const request = { + id: "req-5", + request: { + command: "echo hi", + agentId: "ops-agent", + sessionKey: "agent:ops-agent:missing", + turnSourceChannel: "slack", + turnSourceTo: "channel:C123", + }, + createdAtMs: 0, + expiresAtMs: 1000, + }; + + expect( + shouldHandleMatrixExecApprovalRequest({ + cfg, + accountId: "default", + request, + }), + ).toBe(true); + expect( + shouldHandleMatrixExecApprovalRequest({ + cfg, + accountId: "ops", + request, + }), + ).toBe(false); + }); }); diff --git a/extensions/matrix/src/exec-approvals.ts b/extensions/matrix/src/exec-approvals.ts index c73ff2f906f..63ec8dff7fc 100644 --- a/extensions/matrix/src/exec-approvals.ts +++ b/extensions/matrix/src/exec-approvals.ts @@ -1,6 +1,7 @@ import { createChannelExecApprovalProfile, getExecApprovalReplyMetadata, + isChannelExecApprovalClientEnabledFromConfig, isChannelExecApprovalTargetRecipient, resolveApprovalRequestChannelAccountId, resolveApprovalApprovers, @@ -38,6 +39,15 @@ function resolveMatrixExecApprovalConfig(params: { }; } +function countMatrixExecApprovalHandlerAccounts(cfg: OpenClawConfig): number { + return listMatrixAccountIds(cfg).filter((accountId) => + isChannelExecApprovalClientEnabledFromConfig({ + enabled: resolveMatrixExecApprovalConfig({ cfg, accountId }).enabled, + approverCount: getMatrixExecApprovalApprovers({ cfg, accountId }).length, + }), + ).length; +} + function matchesMatrixRequestAccount(params: { cfg: OpenClawConfig; accountId?: string | null; @@ -50,7 +60,7 @@ function matchesMatrixRequestAccount(params: { channel: "matrix", }); if (turnSourceChannel && turnSourceChannel !== "matrix" && !boundAccountId) { - return listMatrixAccountIds(params.cfg).length <= 1; + return countMatrixExecApprovalHandlerAccounts(params.cfg) <= 1; } return ( !boundAccountId || diff --git a/extensions/telegram/src/exec-approvals.test.ts b/extensions/telegram/src/exec-approvals.test.ts index 9b3157e1a1c..1f0a034f34f 100644 --- a/extensions/telegram/src/exec-approvals.test.ts +++ b/extensions/telegram/src/exec-approvals.test.ts @@ -241,6 +241,57 @@ describe("telegram exec approvals", () => { ).toBe(false); }); + it("allows unbound foreign-channel approvals when only one telegram account can handle them", () => { + const cfg = { + channels: { + telegram: { + accounts: { + default: { + botToken: "tok-default", + execApprovals: { + enabled: true, + approvers: ["123"], + }, + }, + ops: { + botToken: "tok-ops", + execApprovals: { + enabled: false, + approvers: ["123"], + }, + }, + }, + }, + }, + } as OpenClawConfig; + const request = { + id: "req-4", + request: { + command: "echo hi", + sessionKey: "agent:ops:missing", + turnSourceChannel: "slack", + turnSourceTo: "channel:C123", + }, + createdAtMs: 0, + expiresAtMs: 1000, + }; + + expect( + shouldHandleTelegramExecApprovalRequest({ + cfg, + accountId: "default", + request, + }), + ).toBe(true); + expect( + shouldHandleTelegramExecApprovalRequest({ + cfg, + accountId: "ops", + request, + }), + ).toBe(false); + }); + it("only injects approval buttons on eligible telegram targets", () => { const dmCfg = buildConfig({ enabled: true, approvers: ["123"], target: "dm" }); const channelCfg = buildConfig({ enabled: true, approvers: ["123"], target: "channel" }); diff --git a/extensions/telegram/src/exec-approvals.ts b/extensions/telegram/src/exec-approvals.ts index 0fb35e713f0..3102c0a03ba 100644 --- a/extensions/telegram/src/exec-approvals.ts +++ b/extensions/telegram/src/exec-approvals.ts @@ -65,6 +65,15 @@ export function isTelegramExecApprovalTargetRecipient(params: { }); } +function countTelegramExecApprovalHandlerAccounts(cfg: OpenClawConfig): number { + return listTelegramAccountIds(cfg).filter((accountId) => + isChannelExecApprovalClientEnabledFromConfig({ + enabled: resolveTelegramExecApprovalConfig({ cfg, accountId })?.enabled, + approverCount: getTelegramExecApprovalApprovers({ cfg, accountId }).length, + }), + ).length; +} + function matchesTelegramRequestAccount(params: { cfg: OpenClawConfig; accountId?: string | null; @@ -77,7 +86,7 @@ function matchesTelegramRequestAccount(params: { channel: "telegram", }); if (turnSourceChannel && turnSourceChannel !== "telegram" && !boundAccountId) { - return listTelegramAccountIds(params.cfg).length <= 1; + return countTelegramExecApprovalHandlerAccounts(params.cfg) <= 1; } return ( !boundAccountId ||