From 6dec2e1852381dd5343ab8be6bf8b7b0b04152a5 Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Tue, 28 Apr 2026 11:37:21 +0100 Subject: [PATCH] fix(telegram): scope native approvals by target account --- .../telegram/src/exec-approvals.test.ts | 77 +++++++++++++++++++ extensions/telegram/src/exec-approvals.ts | 46 +++++++++++ 2 files changed, 123 insertions(+) diff --git a/extensions/telegram/src/exec-approvals.test.ts b/extensions/telegram/src/exec-approvals.test.ts index e52d04fecdb..19eb7d9b921 100644 --- a/extensions/telegram/src/exec-approvals.test.ts +++ b/extensions/telegram/src/exec-approvals.test.ts @@ -304,6 +304,83 @@ describe("telegram exec approvals", () => { ).toBe(false); }); + it("scopes native exec approval handling to configured target accountIds", () => { + const cfg = { + ...buildMultiAccountTelegramConfig({}), + approvals: { + exec: { + enabled: true, + mode: "targets", + targets: [{ channel: "telegram", to: "123", accountId: "ops" }], + }, + }, + } as OpenClawConfig; + const request: TelegramExecApprovalRequest = { + id: "req-target-account", + request: { + command: "echo hi", + sessionKey: "agent:ops:main", + }, + createdAtMs: 0, + expiresAtMs: 1000, + }; + + expect( + shouldHandleTelegramExecApprovalRequest({ + cfg, + accountId: "default", + request, + }), + ).toBe(false); + expect( + shouldHandleTelegramExecApprovalRequest({ + cfg, + accountId: "ops", + request, + }), + ).toBe(true); + }); + + it("preserves unscoped telegram targets when mixed with scoped target accountIds", () => { + const cfg = { + ...buildMultiAccountTelegramConfig({}), + approvals: { + exec: { + enabled: true, + mode: "targets", + targets: [ + { channel: "telegram", to: "123" }, + { channel: "telegram", to: "456", accountId: "ops" }, + ], + }, + }, + } as OpenClawConfig; + const request: TelegramExecApprovalRequest = { + id: "req-mixed-target-account", + request: { + command: "echo hi", + sessionKey: "agent:ops:main", + }, + createdAtMs: 0, + expiresAtMs: 1000, + }; + + expect( + shouldHandleTelegramExecApprovalRequest({ + cfg, + accountId: "default", + request, + }), + ).toBe(true); + expect( + shouldHandleTelegramExecApprovalRequest({ + cfg, + accountId: "ops", + request, + }), + ).toBe(true); + }); + it("ignores disabled telegram accounts when checking foreign-channel ambiguity", () => { const cfg = buildMultiAccountTelegramConfig({ opsOverrides: { enabled: false } }); const request = makeForeignChannelApprovalRequest({ id: "req-6" }); diff --git a/extensions/telegram/src/exec-approvals.ts b/extensions/telegram/src/exec-approvals.ts index 187f59840c8..d400b3964dd 100644 --- a/extensions/telegram/src/exec-approvals.ts +++ b/extensions/telegram/src/exec-approvals.ts @@ -109,11 +109,57 @@ function countTelegramExecApprovalEligibleAccounts(params: { }).length; } +function isExecApprovalRequest( + request: ExecApprovalRequest | PluginApprovalRequest, +): request is ExecApprovalRequest { + return "command" in request.request; +} + +function isTargetForwardingMode(mode?: string): boolean { + return mode === "targets" || mode === "both"; +} + +function matchesExplicitTelegramForwardTargetAccount(params: { + cfg: OpenClawConfig; + accountId?: string | null; + request: ExecApprovalRequest | PluginApprovalRequest; +}): boolean | undefined { + const forwardingConfig = isExecApprovalRequest(params.request) + ? params.cfg.approvals?.exec + : params.cfg.approvals?.plugin; + if (!forwardingConfig?.enabled || !isTargetForwardingMode(forwardingConfig.mode)) { + return undefined; + } + const telegramTargets = (forwardingConfig.targets ?? []).filter( + (target) => normalizeLowercaseStringOrEmpty(target.channel) === "telegram", + ); + if (telegramTargets.some((target) => !normalizeOptionalString(target.accountId))) { + return undefined; + } + const scopedTelegramAccountIds = telegramTargets + .map((target) => normalizeOptionalString(target.accountId)) + .filter((accountId): accountId is string => Boolean(accountId)); + if (scopedTelegramAccountIds.length === 0) { + return undefined; + } + const normalizedAccountId = params.accountId ? normalizeAccountId(params.accountId) : ""; + return ( + Boolean(normalizedAccountId) && + scopedTelegramAccountIds.some( + (accountId) => normalizeAccountId(accountId) === normalizedAccountId, + ) + ); +} + function matchesTelegramRequestAccount(params: { cfg: OpenClawConfig; accountId?: string | null; request: ExecApprovalRequest | PluginApprovalRequest; }): boolean { + const explicitTargetMatch = matchesExplicitTelegramForwardTargetAccount(params); + if (explicitTargetMatch !== undefined) { + return explicitTargetMatch; + } const turnSourceChannel = normalizeLowercaseStringOrEmpty( params.request.request.turnSourceChannel, );