mirror of
https://github.com/farcasclaudiu/openclaw.git
synced 2026-06-28 17:01:53 +03:00
* fix: enforce Telegram 100-command limit with warning (#5787) Telegram's setMyCommands API rejects requests with more than 100 commands. When skills + custom + plugin commands exceed the limit, truncate to 100 and warn the user instead of silently failing on every startup. * fix: enforce Telegram menu cap + keep hidden commands callable (#15844) (thanks @battman21) --------- Co-authored-by: Peter Steinberger <steipete@gmail.com>
This commit is contained in:
@@ -15,6 +15,7 @@ Docs: https://docs.openclaw.ai
|
|||||||
### Fixes
|
### Fixes
|
||||||
|
|
||||||
- Agents/Heartbeat: stop auto-creating `HEARTBEAT.md` during workspace bootstrap so missing files continue to run heartbeat as documented. (#11766) Thanks @shadril238.
|
- Agents/Heartbeat: stop auto-creating `HEARTBEAT.md` during workspace bootstrap so missing files continue to run heartbeat as documented. (#11766) Thanks @shadril238.
|
||||||
|
- Telegram: cap bot menu registration to Telegram's 100-command limit with an overflow warning while keeping typed hidden commands available. (#15844) Thanks @battman21.
|
||||||
- CLI: lazily load outbound provider dependencies and remove forced success-path exits so commands terminate naturally without killing intentional long-running foreground actions. (#12906) Thanks @DrCrinkle.
|
- CLI: lazily load outbound provider dependencies and remove forced success-path exits so commands terminate naturally without killing intentional long-running foreground actions. (#12906) Thanks @DrCrinkle.
|
||||||
- Auto-reply/Heartbeat: strip sentence-ending `HEARTBEAT_OK` tokens even when followed by up to 4 punctuation characters, while preserving surrounding sentence punctuation. (#15847) Thanks @Spacefish.
|
- Auto-reply/Heartbeat: strip sentence-ending `HEARTBEAT_OK` tokens even when followed by up to 4 punctuation characters, while preserving surrounding sentence punctuation. (#15847) Thanks @Spacefish.
|
||||||
- Clawdock: avoid Zsh readonly variable collisions in helper scripts. (#15501) Thanks @nkelner.
|
- Clawdock: avoid Zsh readonly variable collisions in helper scripts. (#15501) Thanks @nkelner.
|
||||||
|
|||||||
@@ -23,6 +23,67 @@ vi.mock("../pairing/pairing-store.js", () => ({
|
|||||||
}));
|
}));
|
||||||
|
|
||||||
describe("registerTelegramNativeCommands (plugin auth)", () => {
|
describe("registerTelegramNativeCommands (plugin auth)", () => {
|
||||||
|
it("caps menu registration at 100 while leaving hidden plugin handlers available", () => {
|
||||||
|
const specs = Array.from({ length: 101 }, (_, i) => ({
|
||||||
|
name: `cmd_${i}`,
|
||||||
|
description: `Command ${i}`,
|
||||||
|
}));
|
||||||
|
getPluginCommandSpecs.mockReturnValue(specs);
|
||||||
|
matchPluginCommand.mockReset();
|
||||||
|
executePluginCommand.mockReset();
|
||||||
|
deliverReplies.mockReset();
|
||||||
|
|
||||||
|
const handlers: Record<string, (ctx: unknown) => Promise<void>> = {};
|
||||||
|
const setMyCommands = vi.fn().mockResolvedValue(undefined);
|
||||||
|
const log = vi.fn();
|
||||||
|
const bot = {
|
||||||
|
api: {
|
||||||
|
setMyCommands,
|
||||||
|
sendMessage: vi.fn(),
|
||||||
|
},
|
||||||
|
command: (name: string, handler: (ctx: unknown) => Promise<void>) => {
|
||||||
|
handlers[name] = handler;
|
||||||
|
},
|
||||||
|
} as const;
|
||||||
|
|
||||||
|
registerTelegramNativeCommands({
|
||||||
|
bot: bot as unknown as Parameters<typeof registerTelegramNativeCommands>[0]["bot"],
|
||||||
|
cfg: {} as OpenClawConfig,
|
||||||
|
runtime: { log } as RuntimeEnv,
|
||||||
|
accountId: "default",
|
||||||
|
telegramCfg: {} as TelegramAccountConfig,
|
||||||
|
allowFrom: [],
|
||||||
|
groupAllowFrom: [],
|
||||||
|
replyToMode: "off",
|
||||||
|
textLimit: 4000,
|
||||||
|
useAccessGroups: false,
|
||||||
|
nativeEnabled: false,
|
||||||
|
nativeSkillsEnabled: false,
|
||||||
|
nativeDisabledExplicit: false,
|
||||||
|
resolveGroupPolicy: () =>
|
||||||
|
({
|
||||||
|
allowlistEnabled: false,
|
||||||
|
allowed: true,
|
||||||
|
}) as ChannelGroupPolicy,
|
||||||
|
resolveTelegramGroupConfig: () => ({
|
||||||
|
groupConfig: undefined,
|
||||||
|
topicConfig: undefined,
|
||||||
|
}),
|
||||||
|
shouldSkipUpdate: () => false,
|
||||||
|
opts: { token: "token" },
|
||||||
|
});
|
||||||
|
|
||||||
|
const registered = setMyCommands.mock.calls[0]?.[0] as Array<{
|
||||||
|
command: string;
|
||||||
|
description: string;
|
||||||
|
}>;
|
||||||
|
expect(registered).toHaveLength(100);
|
||||||
|
expect(registered[0]).toEqual({ command: "cmd_0", description: "Command 0" });
|
||||||
|
expect(registered[99]).toEqual({ command: "cmd_99", description: "Command 99" });
|
||||||
|
expect(log).toHaveBeenCalledWith(expect.stringContaining("registering first 100"));
|
||||||
|
expect(Object.keys(handlers)).toHaveLength(101);
|
||||||
|
});
|
||||||
|
|
||||||
it("allows requireAuth:false plugin command even when sender is unauthorized", async () => {
|
it("allows requireAuth:false plugin command even when sender is unauthorized", async () => {
|
||||||
const command = {
|
const command = {
|
||||||
name: "plugin",
|
name: "plugin",
|
||||||
|
|||||||
@@ -112,7 +112,7 @@ describe("registerTelegramNativeCommands", () => {
|
|||||||
expect(registeredCommands).toHaveLength(100);
|
expect(registeredCommands).toHaveLength(100);
|
||||||
expect(registeredCommands).toEqual(customCommands.slice(0, 100));
|
expect(registeredCommands).toEqual(customCommands.slice(0, 100));
|
||||||
expect(runtimeLog).toHaveBeenCalledWith(
|
expect(runtimeLog).toHaveBeenCalledWith(
|
||||||
"telegram: truncating 120 commands to 100 (Telegram Bot API limit)",
|
"Telegram limits bots to 100 commands. 120 configured; registering first 100. Use channels.telegram.commands.native: false to disable, or reduce skill/custom commands.",
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -366,25 +366,26 @@ export const registerTelegramNativeCommands = ({
|
|||||||
...pluginCommands,
|
...pluginCommands,
|
||||||
...customCommands,
|
...customCommands,
|
||||||
];
|
];
|
||||||
// Telegram Bot API limits commands to 100 per scope.
|
|
||||||
// Truncate with a warning rather than failing with BOT_COMMANDS_TOO_MUCH.
|
|
||||||
const TELEGRAM_MAX_COMMANDS = 100;
|
const TELEGRAM_MAX_COMMANDS = 100;
|
||||||
if (allCommandsFull.length > TELEGRAM_MAX_COMMANDS) {
|
if (allCommandsFull.length > TELEGRAM_MAX_COMMANDS) {
|
||||||
runtime.log?.(
|
runtime.log?.(
|
||||||
`telegram: truncating ${allCommandsFull.length} commands to ${TELEGRAM_MAX_COMMANDS} (Telegram Bot API limit)`,
|
`Telegram limits bots to ${TELEGRAM_MAX_COMMANDS} commands. ` +
|
||||||
|
`${allCommandsFull.length} configured; registering first ${TELEGRAM_MAX_COMMANDS}. ` +
|
||||||
|
`Use channels.telegram.commands.native: false to disable, or reduce skill/custom commands.`,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
const allCommands = allCommandsFull.slice(0, TELEGRAM_MAX_COMMANDS);
|
// Telegram only limits the setMyCommands payload (menu entries).
|
||||||
|
const commandsToRegister = allCommandsFull.slice(0, TELEGRAM_MAX_COMMANDS);
|
||||||
|
|
||||||
// Clear stale commands before registering new ones to prevent
|
// Clear stale commands before registering new ones to prevent
|
||||||
// leftover commands from deleted skills persisting across restarts (#5717).
|
// leftover commands from deleted skills persisting across restarts (#5717).
|
||||||
// Chain delete → set so a late-resolving delete cannot wipe newly registered commands.
|
// Chain delete → set so a late-resolving delete cannot wipe newly registered commands.
|
||||||
const registerCommands = () => {
|
const registerCommands = () => {
|
||||||
if (allCommands.length > 0) {
|
if (commandsToRegister.length > 0) {
|
||||||
withTelegramApiErrorLogging({
|
withTelegramApiErrorLogging({
|
||||||
operation: "setMyCommands",
|
operation: "setMyCommands",
|
||||||
runtime,
|
runtime,
|
||||||
fn: () => bot.api.setMyCommands(allCommands),
|
fn: () => bot.api.setMyCommands(commandsToRegister),
|
||||||
}).catch(() => {});
|
}).catch(() => {});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -401,7 +402,7 @@ export const registerTelegramNativeCommands = ({
|
|||||||
registerCommands();
|
registerCommands();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (allCommands.length > 0) {
|
if (commandsToRegister.length > 0) {
|
||||||
if (typeof (bot as unknown as { command?: unknown }).command !== "function") {
|
if (typeof (bot as unknown as { command?: unknown }).command !== "function") {
|
||||||
logVerbose("telegram: bot.command unavailable; skipping native handlers");
|
logVerbose("telegram: bot.command unavailable; skipping native handlers");
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
Reference in New Issue
Block a user