fix: filter telegram native command names (#1558) (thanks @Glucksberg)
This commit is contained in:
parent
c2940adc80
commit
185ffca274
@ -551,6 +551,7 @@ Notes:
|
|||||||
- Commands are registered globally and work across all channels
|
- Commands are registered globally and work across all channels
|
||||||
- Command names are case-insensitive (`/MyStatus` matches `/mystatus`)
|
- Command names are case-insensitive (`/MyStatus` matches `/mystatus`)
|
||||||
- Command names must start with a letter and contain only letters, numbers, hyphens, and underscores
|
- Command names must start with a letter and contain only letters, numbers, hyphens, and underscores
|
||||||
|
- Telegram native commands only allow `a-z0-9_` (max 32 chars). Use underscores (not hyphens) if you want a plugin command to appear in Telegram’s native command list.
|
||||||
- Reserved command names (like `help`, `status`, `reset`, etc.) cannot be overridden by plugins
|
- Reserved command names (like `help`, `status`, `reset`, etc.) cannot be overridden by plugins
|
||||||
- Duplicate command registration across plugins will fail with a diagnostic error
|
- Duplicate command registration across plugins will fail with a diagnostic error
|
||||||
|
|
||||||
|
|||||||
@ -8,6 +8,7 @@ import {
|
|||||||
listChatCommandsForConfig,
|
listChatCommandsForConfig,
|
||||||
listNativeCommandSpecs,
|
listNativeCommandSpecs,
|
||||||
listNativeCommandSpecsForConfig,
|
listNativeCommandSpecsForConfig,
|
||||||
|
normalizeNativeCommandSpecsForSurface,
|
||||||
normalizeCommandBody,
|
normalizeCommandBody,
|
||||||
parseCommandArgs,
|
parseCommandArgs,
|
||||||
resolveCommandArgMenu,
|
resolveCommandArgMenu,
|
||||||
@ -45,6 +46,20 @@ describe("commands registry", () => {
|
|||||||
expect(specs.find((spec) => spec.name === "compact")).toBeFalsy();
|
expect(specs.find((spec) => spec.name === "compact")).toBeFalsy();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("normalizes telegram native command specs", () => {
|
||||||
|
const specs = [
|
||||||
|
{ name: "OK", description: "Ok", acceptsArgs: false },
|
||||||
|
{ name: "bad-name", description: "Bad", acceptsArgs: false },
|
||||||
|
{ name: "fine_name", description: "Fine", acceptsArgs: false },
|
||||||
|
{ name: "ok", description: "Dup", acceptsArgs: false },
|
||||||
|
];
|
||||||
|
const normalized = normalizeNativeCommandSpecsForSurface({
|
||||||
|
surface: "telegram",
|
||||||
|
specs,
|
||||||
|
});
|
||||||
|
expect(normalized.map((spec) => spec.name)).toEqual(["ok", "fine_name"]);
|
||||||
|
});
|
||||||
|
|
||||||
it("filters commands based on config flags", () => {
|
it("filters commands based on config flags", () => {
|
||||||
const disabled = listChatCommandsForConfig({
|
const disabled = listChatCommandsForConfig({
|
||||||
commands: { config: false, debug: false },
|
commands: { config: false, debug: false },
|
||||||
|
|||||||
@ -4,6 +4,10 @@ import { getChatCommands, getNativeCommandSurfaces } from "./commands-registry.d
|
|||||||
import { getPluginCommandSpecs } from "../plugins/commands.js";
|
import { getPluginCommandSpecs } from "../plugins/commands.js";
|
||||||
import { DEFAULT_MODEL, DEFAULT_PROVIDER } from "../agents/defaults.js";
|
import { DEFAULT_MODEL, DEFAULT_PROVIDER } from "../agents/defaults.js";
|
||||||
import { resolveConfiguredModelRef } from "../agents/model-selection.js";
|
import { resolveConfiguredModelRef } from "../agents/model-selection.js";
|
||||||
|
import {
|
||||||
|
normalizeTelegramCommandName,
|
||||||
|
TELEGRAM_COMMAND_NAME_PATTERN,
|
||||||
|
} from "../config/telegram-custom-commands.js";
|
||||||
import type {
|
import type {
|
||||||
ChatCommandDefinition,
|
ChatCommandDefinition,
|
||||||
CommandArgChoiceContext,
|
CommandArgChoiceContext,
|
||||||
@ -143,6 +147,37 @@ export function listNativeCommandSpecsForConfig(
|
|||||||
return extras.length > 0 ? [...base, ...extras] : base;
|
return extras.length > 0 ? [...base, ...extras] : base;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function normalizeNativeCommandNameForSurface(name: string, surface: string): string | null {
|
||||||
|
const trimmed = name.trim();
|
||||||
|
if (!trimmed) return null;
|
||||||
|
if (surface === "telegram") {
|
||||||
|
const normalized = normalizeTelegramCommandName(trimmed);
|
||||||
|
if (!normalized) return null;
|
||||||
|
if (!TELEGRAM_COMMAND_NAME_PATTERN.test(normalized)) return null;
|
||||||
|
return normalized;
|
||||||
|
}
|
||||||
|
return trimmed;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function normalizeNativeCommandSpecsForSurface(params: {
|
||||||
|
surface: string;
|
||||||
|
specs: NativeCommandSpec[];
|
||||||
|
}): NativeCommandSpec[] {
|
||||||
|
const surface = params.surface.toLowerCase();
|
||||||
|
if (!surface) return params.specs;
|
||||||
|
const normalized: NativeCommandSpec[] = [];
|
||||||
|
const seen = new Set<string>();
|
||||||
|
for (const spec of params.specs) {
|
||||||
|
const normalizedName = normalizeNativeCommandNameForSurface(spec.name, surface);
|
||||||
|
if (!normalizedName) continue;
|
||||||
|
const key = normalizedName.toLowerCase();
|
||||||
|
if (seen.has(key)) continue;
|
||||||
|
seen.add(key);
|
||||||
|
normalized.push(normalizedName === spec.name ? spec : { ...spec, name: normalizedName });
|
||||||
|
}
|
||||||
|
return normalized;
|
||||||
|
}
|
||||||
|
|
||||||
export function findCommandByNativeName(name: string): ChatCommandDefinition | undefined {
|
export function findCommandByNativeName(name: string): ChatCommandDefinition | undefined {
|
||||||
const normalized = name.trim().toLowerCase();
|
const normalized = name.trim().toLowerCase();
|
||||||
return getChatCommands().find(
|
return getChatCommands().find(
|
||||||
|
|||||||
@ -6,6 +6,7 @@ import {
|
|||||||
findCommandByNativeName,
|
findCommandByNativeName,
|
||||||
listNativeCommandSpecs,
|
listNativeCommandSpecs,
|
||||||
listNativeCommandSpecsForConfig,
|
listNativeCommandSpecsForConfig,
|
||||||
|
normalizeNativeCommandSpecsForSurface,
|
||||||
parseCommandArgs,
|
parseCommandArgs,
|
||||||
resolveCommandArgMenu,
|
resolveCommandArgMenu,
|
||||||
} from "../auto-reply/commands-registry.js";
|
} from "../auto-reply/commands-registry.js";
|
||||||
@ -84,13 +85,28 @@ export const registerTelegramNativeCommands = ({
|
|||||||
}: RegisterTelegramNativeCommandsParams) => {
|
}: RegisterTelegramNativeCommandsParams) => {
|
||||||
const skillCommands =
|
const skillCommands =
|
||||||
nativeEnabled && nativeSkillsEnabled ? listSkillCommandsForAgents({ cfg }) : [];
|
nativeEnabled && nativeSkillsEnabled ? listSkillCommandsForAgents({ cfg }) : [];
|
||||||
const nativeCommands = nativeEnabled
|
const rawNativeCommands = nativeEnabled
|
||||||
? listNativeCommandSpecsForConfig(cfg, { skillCommands })
|
? listNativeCommandSpecsForConfig(cfg, { skillCommands })
|
||||||
: [];
|
: [];
|
||||||
|
const nativeCommands = normalizeNativeCommandSpecsForSurface({
|
||||||
|
surface: "telegram",
|
||||||
|
specs: rawNativeCommands,
|
||||||
|
});
|
||||||
const reservedCommands = new Set(
|
const reservedCommands = new Set(
|
||||||
listNativeCommandSpecs().map((command) => command.name.toLowerCase()),
|
normalizeNativeCommandSpecsForSurface({
|
||||||
|
surface: "telegram",
|
||||||
|
specs: listNativeCommandSpecs(),
|
||||||
|
}).map((command) => command.name.toLowerCase()),
|
||||||
);
|
);
|
||||||
for (const command of skillCommands) {
|
const reservedSkillSpecs = normalizeNativeCommandSpecsForSurface({
|
||||||
|
surface: "telegram",
|
||||||
|
specs: skillCommands.map((command) => ({
|
||||||
|
name: command.name,
|
||||||
|
description: command.description,
|
||||||
|
acceptsArgs: true,
|
||||||
|
})),
|
||||||
|
});
|
||||||
|
for (const command of reservedSkillSpecs) {
|
||||||
reservedCommands.add(command.name.toLowerCase());
|
reservedCommands.add(command.name.toLowerCase());
|
||||||
}
|
}
|
||||||
const customResolution = resolveTelegramCustomCommands({
|
const customResolution = resolveTelegramCustomCommands({
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user