From 9025da2296c11829bf48f0f38c434d6a0ce82fa3 Mon Sep 17 00:00:00 2001 From: Ayaan Zaidi Date: Fri, 30 Jan 2026 11:57:25 +0530 Subject: [PATCH] fix: scope telegram skill commands per bot (#4360) (thanks @robhparker) --- CHANGELOG.md | 1 + src/telegram/bot-native-commands.test.ts | 82 ++++++++++++++++++++++++ src/telegram/bot-native-commands.ts | 10 ++- 3 files changed, 90 insertions(+), 3 deletions(-) create mode 100644 src/telegram/bot-native-commands.test.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index 10bcda92f..4c0549c16 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -73,6 +73,7 @@ Status: stable. ### Fixes - Telegram: avoid silent empty replies by tracking normalization skips before fallback. (#3796) +- Telegram: scope native skill commands to bound agent per bot. (#4360) Thanks @robhparker. - Mentions: honor mentionPatterns even when explicit mentions are present. (#3303) Thanks @HirokiKobayashi-R. - Discord: restore username directory lookup in target resolution. (#3131) Thanks @bonald. - Agents: align MiniMax base URL test expectation with default provider config. (#3131) Thanks @bonald. diff --git a/src/telegram/bot-native-commands.test.ts b/src/telegram/bot-native-commands.test.ts new file mode 100644 index 000000000..dc6b94dcc --- /dev/null +++ b/src/telegram/bot-native-commands.test.ts @@ -0,0 +1,82 @@ +import { beforeEach, describe, expect, it, vi } from "vitest"; + +import type { OpenClawConfig } from "../config/config.js"; +import type { TelegramAccountConfig } from "../config/types.js"; +import type { RuntimeEnv } from "../runtime.js"; +import { registerTelegramNativeCommands } from "./bot-native-commands.js"; + +const { listSkillCommandsForAgents } = vi.hoisted(() => ({ + listSkillCommandsForAgents: vi.fn(() => []), +})); + +vi.mock("../auto-reply/skill-commands.js", () => ({ + listSkillCommandsForAgents, +})); + +describe("registerTelegramNativeCommands", () => { + beforeEach(() => { + listSkillCommandsForAgents.mockReset(); + }); + + const buildParams = (cfg: OpenClawConfig, accountId = "default") => ({ + bot: { + api: { + setMyCommands: vi.fn().mockResolvedValue(undefined), + sendMessage: vi.fn().mockResolvedValue(undefined), + }, + command: vi.fn(), + } as unknown as Parameters[0]["bot"], + cfg, + runtime: {} as RuntimeEnv, + accountId, + telegramCfg: {} as TelegramAccountConfig, + allowFrom: [], + groupAllowFrom: [], + replyToMode: "off" as const, + textLimit: 4096, + useAccessGroups: false, + nativeEnabled: true, + nativeSkillsEnabled: true, + nativeDisabledExplicit: false, + resolveGroupPolicy: () => ({ allowlistEnabled: false, allowed: true }), + resolveTelegramGroupConfig: () => ({ + groupConfig: undefined, + topicConfig: undefined, + }), + shouldSkipUpdate: () => false, + opts: { token: "token" }, + }); + + it("scopes skill commands when account binding exists", () => { + const cfg: OpenClawConfig = { + agents: { + list: [{ id: "main", default: true }, { id: "butler" }], + }, + bindings: [ + { + agentId: "butler", + match: { channel: "telegram", accountId: "bot-a" }, + }, + ], + }; + + registerTelegramNativeCommands(buildParams(cfg, "bot-a")); + + expect(listSkillCommandsForAgents).toHaveBeenCalledWith({ + cfg, + agentIds: ["butler"], + }); + }); + + it("keeps skill commands unscoped without a matching binding", () => { + const cfg: OpenClawConfig = { + agents: { + list: [{ id: "main", default: true }, { id: "butler" }], + }, + }; + + registerTelegramNativeCommands(buildParams(cfg, "bot-a")); + + expect(listSkillCommandsForAgents).toHaveBeenCalledWith({ cfg }); + }); +}); diff --git a/src/telegram/bot-native-commands.ts b/src/telegram/bot-native-commands.ts index c4b78a09a..cd53459e6 100644 --- a/src/telegram/bot-native-commands.ts +++ b/src/telegram/bot-native-commands.ts @@ -257,11 +257,15 @@ export const registerTelegramNativeCommands = ({ shouldSkipUpdate, opts, }: RegisterTelegramNativeCommandsParams) => { - const boundRoute = resolveAgentRoute({ cfg, channel: "telegram", accountId }); - const boundAgentIds = boundRoute?.agentId ? [boundRoute.agentId] : undefined; + const boundRoute = + nativeEnabled && nativeSkillsEnabled + ? resolveAgentRoute({ cfg, channel: "telegram", accountId }) + : null; + const boundAgentIds = + boundRoute && boundRoute.matchedBy.startsWith("binding.") ? [boundRoute.agentId] : null; const skillCommands = nativeEnabled && nativeSkillsEnabled - ? listSkillCommandsForAgents({ cfg, agentIds: boundAgentIds }) + ? listSkillCommandsForAgents(boundAgentIds ? { cfg, agentIds: boundAgentIds } : { cfg }) : []; const nativeCommands = nativeEnabled ? listNativeCommandSpecsForConfig(cfg, { skillCommands, provider: "telegram" })