diff --git a/extensions/telegram/api.ts b/extensions/telegram/api.ts index d5960350c39..88ef86a6a53 100644 --- a/extensions/telegram/api.ts +++ b/extensions/telegram/api.ts @@ -3,6 +3,7 @@ export * from "./src/accounts.js"; export * from "./src/allow-from.js"; export * from "./src/api-fetch.js"; export * from "./src/exec-approvals.js"; +export * from "./src/group-policy.js"; export * from "./src/inline-buttons.js"; export * from "./src/model-buttons.js"; export * from "./src/normalize.js"; diff --git a/extensions/telegram/src/channel.test.ts b/extensions/telegram/src/channel.test.ts index 6c1f4da5e73..c9e8df40be0 100644 --- a/extensions/telegram/src/channel.test.ts +++ b/extensions/telegram/src/channel.test.ts @@ -154,6 +154,42 @@ afterEach(() => { vi.restoreAllMocks(); }); +describe("telegramPlugin groups", () => { + it("uses plugin-owned group policy resolvers", () => { + const cfg = { + channels: { + telegram: { + botToken: "telegram-test", + groups: { + "-1001": { + requireMention: true, + tools: { allow: ["message.send"] }, + topics: { + "77": { + requireMention: false, + }, + }, + }, + }, + }, + }, + } as OpenClawConfig; + + expect( + telegramPlugin.groups?.resolveRequireMention?.({ + cfg, + groupId: "-1001:topic:77", + }), + ).toBe(false); + expect( + telegramPlugin.groups?.resolveToolPolicy?.({ + cfg, + groupId: "-1001:topic:77", + }), + ).toEqual({ allow: ["message.send"] }); + }); +}); + describe("telegramPlugin duplicate token guard", () => { it("marks secondary account as not configured when token is shared", async () => { const cfg = createCfg(); diff --git a/extensions/telegram/src/channel.ts b/extensions/telegram/src/channel.ts index 56a2256f9c0..f8b982e5276 100644 --- a/extensions/telegram/src/channel.ts +++ b/extensions/telegram/src/channel.ts @@ -20,8 +20,6 @@ import { PAIRING_APPROVED_MESSAGE, projectCredentialSnapshotFields, resolveConfiguredFromCredentialStatuses, - resolveTelegramGroupRequireMention, - resolveTelegramGroupToolPolicy, type ChannelPlugin, type ChannelMessageActionAdapter, type OpenClawConfig, @@ -38,6 +36,10 @@ import { isTelegramExecApprovalClientEnabled, resolveTelegramExecApprovalTarget, } from "./exec-approvals.js"; +import { + resolveTelegramGroupRequireMention, + resolveTelegramGroupToolPolicy, +} from "./group-policy.js"; import { monitorTelegramProvider } from "./monitor.js"; import { looksLikeTelegramTargetId, normalizeTelegramMessagingTarget } from "./normalize.js"; import { sendTelegramPayloadMessages } from "./outbound-adapter.js"; diff --git a/extensions/telegram/src/group-policy.test.ts b/extensions/telegram/src/group-policy.test.ts new file mode 100644 index 00000000000..c93018132bc --- /dev/null +++ b/extensions/telegram/src/group-policy.test.ts @@ -0,0 +1,40 @@ +import { describe, expect, it } from "vitest"; +import { + resolveTelegramGroupRequireMention, + resolveTelegramGroupToolPolicy, +} from "./group-policy.js"; + +describe("telegram group policy", () => { + it("resolves topic-level requireMention and chat-level tools for topic ids", () => { + const telegramCfg = { + channels: { + telegram: { + botToken: "telegram-test", + groups: { + "-1001": { + requireMention: true, + tools: { allow: ["message.send"] }, + topics: { + "77": { + requireMention: false, + }, + }, + }, + "*": { + requireMention: true, + }, + }, + }, + }, + // oxlint-disable-next-line typescript/no-explicit-any + } as any; + expect( + resolveTelegramGroupRequireMention({ cfg: telegramCfg, groupId: "-1001:topic:77" }), + ).toBe(false); + expect(resolveTelegramGroupToolPolicy({ cfg: telegramCfg, groupId: "-1001:topic:77" })).toEqual( + { + allow: ["message.send"], + }, + ); + }); +}); diff --git a/extensions/telegram/src/group-policy.ts b/extensions/telegram/src/group-policy.ts new file mode 100644 index 00000000000..a90e930a4a5 --- /dev/null +++ b/extensions/telegram/src/group-policy.ts @@ -0,0 +1,91 @@ +import { + resolveChannelGroupRequireMention, + resolveChannelGroupToolsPolicy, + type GroupToolPolicyConfig, +} from "openclaw/plugin-sdk/channel-policy"; +import { type ChannelGroupContext } from "openclaw/plugin-sdk/channel-runtime"; + +function parseTelegramGroupId(value?: string | null) { + const raw = value?.trim() ?? ""; + if (!raw) { + return { chatId: undefined, topicId: undefined }; + } + const parts = raw.split(":").filter(Boolean); + if ( + parts.length >= 3 && + parts[1] === "topic" && + /^-?\d+$/.test(parts[0]) && + /^\d+$/.test(parts[2]) + ) { + return { chatId: parts[0], topicId: parts[2] }; + } + if (parts.length >= 2 && /^-?\d+$/.test(parts[0]) && /^\d+$/.test(parts[1])) { + return { chatId: parts[0], topicId: parts[1] }; + } + return { chatId: raw, topicId: undefined }; +} + +function resolveTelegramRequireMention(params: { + cfg: ChannelGroupContext["cfg"]; + chatId?: string; + topicId?: string; +}): boolean | undefined { + const { cfg, chatId, topicId } = params; + if (!chatId) { + return undefined; + } + const groupConfig = cfg.channels?.telegram?.groups?.[chatId]; + const groupDefault = cfg.channels?.telegram?.groups?.["*"]; + const topicConfig = topicId && groupConfig?.topics ? groupConfig.topics[topicId] : undefined; + const defaultTopicConfig = + topicId && groupDefault?.topics ? groupDefault.topics[topicId] : undefined; + if (typeof topicConfig?.requireMention === "boolean") { + return topicConfig.requireMention; + } + if (typeof defaultTopicConfig?.requireMention === "boolean") { + return defaultTopicConfig.requireMention; + } + if (typeof groupConfig?.requireMention === "boolean") { + return groupConfig.requireMention; + } + if (typeof groupDefault?.requireMention === "boolean") { + return groupDefault.requireMention; + } + return undefined; +} + +export function resolveTelegramGroupRequireMention( + params: ChannelGroupContext, +): boolean | undefined { + const { chatId, topicId } = parseTelegramGroupId(params.groupId); + const requireMention = resolveTelegramRequireMention({ + cfg: params.cfg, + chatId, + topicId, + }); + if (typeof requireMention === "boolean") { + return requireMention; + } + return resolveChannelGroupRequireMention({ + cfg: params.cfg, + channel: "telegram", + groupId: chatId ?? params.groupId, + accountId: params.accountId, + }); +} + +export function resolveTelegramGroupToolPolicy( + params: ChannelGroupContext, +): GroupToolPolicyConfig | undefined { + const { chatId } = parseTelegramGroupId(params.groupId); + return resolveChannelGroupToolsPolicy({ + cfg: params.cfg, + channel: "telegram", + groupId: chatId ?? params.groupId, + accountId: params.accountId, + senderId: params.senderId, + senderName: params.senderName, + senderUsername: params.senderUsername, + senderE164: params.senderE164, + }); +} diff --git a/src/channels/plugins/group-mentions.test.ts b/src/channels/plugins/group-mentions.test.ts index b942cf5a63b..7375112ac34 100644 --- a/src/channels/plugins/group-mentions.test.ts +++ b/src/channels/plugins/group-mentions.test.ts @@ -4,45 +4,8 @@ import { resolveBlueBubblesGroupToolPolicy, resolveLineGroupRequireMention, resolveLineGroupToolPolicy, - resolveTelegramGroupRequireMention, - resolveTelegramGroupToolPolicy, } from "./group-mentions.js"; -describe("group mentions (telegram)", () => { - it("resolves topic-level requireMention and chat-level tools for topic ids", () => { - const telegramCfg = { - channels: { - telegram: { - botToken: "telegram-test", - groups: { - "-1001": { - requireMention: true, - tools: { allow: ["message.send"] }, - topics: { - "77": { - requireMention: false, - }, - }, - }, - "*": { - requireMention: true, - }, - }, - }, - }, - // oxlint-disable-next-line typescript/no-explicit-any - } as any; - expect( - resolveTelegramGroupRequireMention({ cfg: telegramCfg, groupId: "-1001:topic:77" }), - ).toBe(false); - expect(resolveTelegramGroupToolPolicy({ cfg: telegramCfg, groupId: "-1001:topic:77" })).toEqual( - { - allow: ["message.send"], - }, - ); - }); -}); - describe("group mentions (bluebubbles)", () => { it("uses generic channel group policy helpers", () => { const blueBubblesCfg = { diff --git a/src/channels/plugins/group-mentions.ts b/src/channels/plugins/group-mentions.ts index ed432d8deb6..c3268496b03 100644 --- a/src/channels/plugins/group-mentions.ts +++ b/src/channels/plugins/group-mentions.ts @@ -1,4 +1,3 @@ -import type { OpenClawConfig } from "../../config/config.js"; import { resolveChannelGroupRequireMention, resolveChannelGroupToolsPolicy, @@ -9,55 +8,6 @@ import type { ChannelGroupContext } from "./types.js"; type GroupMentionParams = ChannelGroupContext; -function parseTelegramGroupId(value?: string | null) { - const raw = value?.trim() ?? ""; - if (!raw) { - return { chatId: undefined, topicId: undefined }; - } - const parts = raw.split(":").filter(Boolean); - if ( - parts.length >= 3 && - parts[1] === "topic" && - /^-?\d+$/.test(parts[0]) && - /^\d+$/.test(parts[2]) - ) { - return { chatId: parts[0], topicId: parts[2] }; - } - if (parts.length >= 2 && /^-?\d+$/.test(parts[0]) && /^\d+$/.test(parts[1])) { - return { chatId: parts[0], topicId: parts[1] }; - } - return { chatId: raw, topicId: undefined }; -} - -function resolveTelegramRequireMention(params: { - cfg: OpenClawConfig; - chatId?: string; - topicId?: string; -}): boolean | undefined { - const { cfg, chatId, topicId } = params; - if (!chatId) { - return undefined; - } - const groupConfig = cfg.channels?.telegram?.groups?.[chatId]; - const groupDefault = cfg.channels?.telegram?.groups?.["*"]; - const topicConfig = topicId && groupConfig?.topics ? groupConfig.topics[topicId] : undefined; - const defaultTopicConfig = - topicId && groupDefault?.topics ? groupDefault.topics[topicId] : undefined; - if (typeof topicConfig?.requireMention === "boolean") { - return topicConfig.requireMention; - } - if (typeof defaultTopicConfig?.requireMention === "boolean") { - return defaultTopicConfig.requireMention; - } - if (typeof groupConfig?.requireMention === "boolean") { - return groupConfig.requireMention; - } - if (typeof groupDefault?.requireMention === "boolean") { - return groupDefault.requireMention; - } - return undefined; -} - type ChannelGroupPolicyChannel = | "telegram" | "whatsapp" @@ -96,26 +46,6 @@ function resolveChannelToolPolicyForSender( }); } -export function resolveTelegramGroupRequireMention( - params: GroupMentionParams, -): boolean | undefined { - const { chatId, topicId } = parseTelegramGroupId(params.groupId); - const requireMention = resolveTelegramRequireMention({ - cfg: params.cfg, - chatId, - topicId, - }); - if (typeof requireMention === "boolean") { - return requireMention; - } - return resolveChannelGroupRequireMention({ - cfg: params.cfg, - channel: "telegram", - groupId: chatId ?? params.groupId, - accountId: params.accountId, - }); -} - export function resolveWhatsAppGroupRequireMention(params: GroupMentionParams): boolean { return resolveChannelRequireMention(params, "whatsapp"); } @@ -138,13 +68,6 @@ export function resolveBlueBubblesGroupRequireMention(params: GroupMentionParams return resolveChannelRequireMention(params, "bluebubbles"); } -export function resolveTelegramGroupToolPolicy( - params: GroupMentionParams, -): GroupToolPolicyConfig | undefined { - const { chatId } = parseTelegramGroupId(params.groupId); - return resolveChannelToolPolicyForSender(params, "telegram", chatId ?? params.groupId); -} - export function resolveWhatsAppGroupToolPolicy( params: GroupMentionParams, ): GroupToolPolicyConfig | undefined { diff --git a/src/plugin-sdk/channel-policy.ts b/src/plugin-sdk/channel-policy.ts index b7166262eb6..c59643a4e4b 100644 --- a/src/plugin-sdk/channel-policy.ts +++ b/src/plugin-sdk/channel-policy.ts @@ -14,7 +14,11 @@ export { collectOpenProviderGroupPolicyWarnings, } from "../channels/plugins/group-policy-warnings.js"; export { buildAccountScopedDmSecurityPolicy } from "../channels/plugins/helpers.js"; -export { resolveChannelGroupRequireMention, resolveToolsBySender } from "../config/group-policy.js"; +export { + resolveChannelGroupRequireMention, + resolveChannelGroupToolsPolicy, + resolveToolsBySender, +} from "../config/group-policy.js"; export { DM_GROUP_ACCESS_REASON, readStoreAllowFromForDmPolicy, diff --git a/src/plugin-sdk/telegram.ts b/src/plugin-sdk/telegram.ts index 9a94e7c2d1c..0b539cf7057 100644 --- a/src/plugin-sdk/telegram.ts +++ b/src/plugin-sdk/telegram.ts @@ -57,7 +57,7 @@ export { export { resolveTelegramGroupRequireMention, resolveTelegramGroupToolPolicy, -} from "../channels/plugins/group-mentions.js"; +} from "../../extensions/telegram/src/group-policy.js"; export { TelegramConfigSchema } from "../config/zod-schema.providers-core.js"; export { buildTokenChannelStatusSummary } from "./status-helpers.js";