fix(telegram): describe reaction emoji syntax (#129080)

* fix(telegram): describe reaction emoji syntax

* fix(telegram): keep reactions cross-channel

* test(telegram): verify disabled reactions remove emoji guidance

---------

Co-authored-by: Peter Steinberger <steipete@gmail.com>
This commit is contained in:
sunlit-deng
2026-08-26 13:00:28 +08:00
committed by GitHub
parent 33c88afc6d
commit bd474922cf
4 changed files with 114 additions and 0 deletions
@@ -498,6 +498,56 @@ describe("telegramMessageActions", () => {
});
});
it("advertises Telegram reaction syntax and emoji discovery in message tool schema", () => {
const cfg = {
channels: {
telegram: {
botToken: "tok",
actions: { reactions: true },
},
},
} as OpenClawConfig;
const discovery = telegramMessageActions.describeMessageTool?.({ cfg });
const contributions = Array.isArray(discovery?.schema)
? discovery.schema
: discovery?.schema
? [discovery.schema]
: [];
const reactionSchema = contributions.find((entry) => "emoji" in entry.properties);
const emojiDescription = (
reactionSchema?.properties.emoji as { description?: string } | undefined
)?.description;
expect(discovery?.actions).toEqual(expect.arrayContaining(["react", "emoji-list"]));
expect(reactionSchema?.actions).toEqual([]);
expect(reactionSchema?.properties.emoji).toMatchObject({
type: "string",
description: expect.stringContaining("custom_emoji_id"),
});
expect(emojiDescription).toContain('action:"emoji-list"');
expect(emojiDescription).toContain("arbitrary Unicode may be rejected");
const disabledDiscovery = telegramMessageActions.describeMessageTool?.({
cfg: {
channels: {
telegram: {
botToken: "tok",
actions: { reactions: false },
},
},
} as OpenClawConfig,
});
const disabledContributions = Array.isArray(disabledDiscovery?.schema)
? disabledDiscovery.schema
: disabledDiscovery?.schema
? [disabledDiscovery.schema]
: [];
expect(disabledDiscovery?.actions).not.toContain("react");
expect(disabledDiscovery?.actions).not.toContain("emoji-list");
expect(disabledContributions.find((entry) => "emoji" in entry.properties)).toBeUndefined();
});
it("matches runtime account-key normalization during SecretRef-tolerant discovery", () => {
const cfg = {
channels: {
@@ -24,6 +24,7 @@ import {
import { isTelegramInlineButtonsEnabled } from "./inline-buttons.js";
import {
createTelegramPollExtraToolSchemas,
createTelegramReactionEmojiSchema,
createTelegramRichSendExtraToolSchemas,
} from "./message-tool-schema.js";
import { rejectTelegramNativeButtonParams } from "./native-button-params.js";
@@ -204,6 +205,14 @@ function describeTelegramMessageTool({
visibility: "all-configured",
});
}
if (discovery.isEnabled("reactions")) {
schema.push({
properties: createTelegramReactionEmojiSchema(),
// The shared emoji parameter keeps react valid across channels; this
// contribution only adds Telegram-specific guidance for that parameter.
actions: [],
});
}
if (discovery.isEnabled("sendMessage")) {
schema.push({
properties: createTelegramRichSendExtraToolSchemas(),
@@ -20,6 +20,19 @@ export function createTelegramPollExtraToolSchemas() {
};
}
/** Schema additions for Telegram reactions through the existing react action. */
export function createTelegramReactionEmojiSchema() {
return {
emoji: Type.Optional(
Type.String({
description:
'Telegram reaction emoji: use a supported Unicode reaction, or pass the numeric custom_emoji_id identifier returned by action:"emoji-list" directly as emoji. ' +
'Use action:"emoji-list" to inspect reactions allowed in the current chat; arbitrary Unicode may be rejected by Telegram.',
}),
),
};
}
/** Schema additions for Telegram-native rich sends through the existing send action. */
export function createTelegramRichSendExtraToolSchemas() {
return {
+42
View File
@@ -4191,6 +4191,48 @@ describe("message tool description", () => {
expect(tool.description).not.toContain("telegram (");
});
it("keeps cross-channel Telegram reactions available when emoji schema is metadata-only", () => {
const signalPlugin = createChannelPlugin({
id: "signal",
label: "Signal",
docsPath: "/channels/signal",
blurb: "Signal test plugin.",
actions: ["send"],
});
const telegramPlugin = createChannelPlugin({
id: "telegram",
label: "Telegram",
docsPath: "/channels/telegram",
blurb: "Telegram test plugin.",
actions: ["send", "react"],
toolSchema: {
actions: [],
properties: {
emoji: Type.Optional(Type.String()),
},
},
});
setActivePluginRegistry(
createTestRegistry([
{ pluginId: "signal", source: "test", plugin: signalPlugin },
{ pluginId: "telegram", source: "test", plugin: telegramPlugin },
]),
);
const tool = createMessageTool({
config: {} as never,
currentChannelProvider: "signal",
});
const properties = getToolProperties(tool);
expect(getActionEnum(properties)).toContain("react");
expect(properties.emoji).toMatchObject({ type: "string" });
expect((properties.emoji as { description?: string }).description).not.toContain(
"custom_emoji_id",
);
});
it("does not advertise cross-channel actions whose params are hidden by current-channel schema", () => {
const signalPlugin = createChannelPlugin({
id: "signal",