diff --git a/extensions/telegram/src/bot-message-context.body.test.ts b/extensions/telegram/src/bot-message-context.body.test.ts index 097b9c675627..f7cd04be9566 100644 --- a/extensions/telegram/src/bot-message-context.body.test.ts +++ b/extensions/telegram/src/bot-message-context.body.test.ts @@ -98,6 +98,76 @@ describe("resolveTelegramInboundBody", () => { expect(result?.bodyText).toBe("[unsupported Telegram rich_message received]"); }); + it("extracts text from rich-message-only updates", async () => { + const result = await resolveTelegramBody({ + msg: { + message_id: 0, + date: 1_700_000_000, + chat: { id: 42, type: "private", first_name: "Pat" }, + from: { id: 42, first_name: "Pat" }, + rich_message: { + blocks: [ + { + type: "paragraph", + text: [{ type: "plain", text: "Forwarded rich text" }], + }, + ], + }, + } as never, + }); + + expect(result?.rawBody).toBe("Forwarded rich text"); + expect(result?.bodyText).toBe("Forwarded rich text"); + }); + + it("preserves whitespace across rich-message inline text spans", async () => { + const result = await resolveTelegramBody({ + msg: { + message_id: 0, + date: 1_700_000_000, + chat: { id: 42, type: "private", first_name: "Pat" }, + from: { id: 42, first_name: "Pat" }, + rich_message: { + blocks: [ + { + type: "paragraph", + text: [ + { type: "plain", text: "Forwarded " }, + { type: "bold", text: "rich text" }, + ], + }, + ], + }, + } as never, + }); + + expect(result?.rawBody).toBe("Forwarded rich text"); + }); + + it("extracts markdown and html rich-message text", async () => { + const markdownResult = await resolveTelegramBody({ + msg: { + message_id: 0, + date: 1_700_000_000, + chat: { id: 42, type: "private", first_name: "Pat" }, + from: { id: 42, first_name: "Pat" }, + rich_message: { markdown: "Forwarded **markdown**" }, + } as never, + }); + const htmlResult = await resolveTelegramBody({ + msg: { + message_id: 0, + date: 1_700_000_000, + chat: { id: 42, type: "private", first_name: "Pat" }, + from: { id: 42, first_name: "Pat" }, + rich_message: { html: "
Forwarded html
" }, + } as never, + }); + + expect(markdownResult?.rawBody).toBe("Forwarded **markdown**"); + expect(htmlResult?.rawBody).toBe("Forwarded html"); + }); + it("keeps rich-message placeholders quiet in requireMention groups", async () => { const logger = { info: vi.fn() }; const result = await resolveTelegramBody({ @@ -127,6 +197,76 @@ describe("resolveTelegramInboundBody", () => { expect(result).toBeNull(); }); + it("routes rich-message-only updates that match group mention patterns", async () => { + const logger = { info: vi.fn() }; + const result = await resolveTelegramBody({ + cfg: { + channels: { telegram: {} }, + messages: { groupChat: { mentionPatterns: ["\\btelegram\\b"] } }, + } as never, + msg: { + message_id: 1, + date: 1_700_000_001, + chat: { id: -1001234567890, type: "supergroup", title: "Test Group" }, + from: { id: 42, first_name: "Pat" }, + rich_message: { + blocks: [ + { + type: "paragraph", + text: [{ type: "plain", text: "telegram please read this" }], + }, + ], + }, + } as never, + isGroup: true, + chatId: -1001234567890, + senderId: "42", + groupConfig: { requireMention: true } as never, + requireMention: true, + logger, + }); + + expect(logger.info).not.toHaveBeenCalledWith( + { chatId: -1001234567890, reason: "no-mention" }, + "skipping group message", + ); + expect(result?.rawBody).toBe("telegram please read this"); + expect(result?.effectiveWasMentioned).toBe(true); + }); + + it("routes rich-message-only updates that mention the bot username", async () => { + const logger = { info: vi.fn() }; + const result = await resolveTelegramBody({ + msg: { + message_id: 1, + date: 1_700_000_001, + chat: { id: -1001234567890, type: "supergroup", title: "Test Group" }, + from: { id: 42, first_name: "Pat" }, + rich_message: { + blocks: [ + { + type: "paragraph", + text: [{ type: "plain", text: "@bot please read this" }], + }, + ], + }, + } as never, + isGroup: true, + chatId: -1001234567890, + senderId: "42", + groupConfig: { requireMention: true } as never, + requireMention: true, + logger, + }); + + expect(logger.info).not.toHaveBeenCalledWith( + { chatId: -1001234567890, reason: "no-mention" }, + "skipping group message", + ); + expect(result?.rawBody).toBe("@bot please read this"); + expect(result?.effectiveWasMentioned).toBe(true); + }); + it("renders Telegram text entities before building the agent body", async () => { const result = await resolveTelegramBody({ msg: { diff --git a/extensions/telegram/src/bot/helpers.test.ts b/extensions/telegram/src/bot/helpers.test.ts index 319fbd9be4e4..0b85eb67af75 100644 --- a/extensions/telegram/src/bot/helpers.test.ts +++ b/extensions/telegram/src/bot/helpers.test.ts @@ -525,6 +525,31 @@ describe("describeReplyTarget", () => { expect(result?.quoteSourceText).toBeUndefined(); }); + it("describes rich-message-only reply targets with rich text", () => { + const result = describeReplyTarget({ + message_id: 2, + date: 1000, + chat: { id: 1, type: "private" }, + reply_to_message: { + message_id: 1, + date: 900, + chat: { id: 1, type: "private" }, + rich_message: { + blocks: [ + { + type: "paragraph", + text: [{ type: "plain", text: "Forwarded reply text" }], + }, + ], + }, + from: { id: 42, first_name: "Alice", is_bot: false }, + }, + } as never); + + expect(result?.body).toBe("Forwarded reply text"); + expect(result?.quoteSourceText).toBeUndefined(); + }); + it("drops binary reply captions with no safe fallback", () => { const result = describeReplyTarget({ message_id: 2, diff --git a/extensions/telegram/src/message-cache.test.ts b/extensions/telegram/src/message-cache.test.ts index 3f2309c60870..925791535e99 100644 --- a/extensions/telegram/src/message-cache.test.ts +++ b/extensions/telegram/src/message-cache.test.ts @@ -600,6 +600,56 @@ describe("telegram message cache", () => { }); }); + it("preserves rich-message text in subsequent conversation context", async () => { + const cache = createTelegramMessageCache(); + const chat = { id: 7, type: "private", first_name: "Nora" } as const; + await cache.record({ + accountId: "default", + chatId: 7, + msg: { + chat, + message_id: 45, + date: 1736380745, + rich_message: { + blocks: [ + { + type: "paragraph", + text: [{ type: "plain", text: "Forwarded cache text" }], + }, + ], + }, + from: { id: 1, is_bot: false, first_name: "Nora" }, + } as Message, + }); + await cache.record({ + accountId: "default", + chatId: 7, + msg: { + chat, + message_id: 46, + date: 1736380746, + text: "What did I just send?", + from: { id: 1, is_bot: false, first_name: "Nora" }, + } as Message, + }); + + const context = await buildTelegramConversationContext({ + cache, + accountId: "default", + chatId: 7, + messageId: "46", + replyChainNodes: [], + recentLimit: 10, + replyTargetWindowSize: 2, + }); + + expect(context).toHaveLength(1); + expect(context[0]?.node).toMatchObject({ + messageId: "45", + body: "Forwarded cache text", + }); + }); + it("returns nearby messages around a stale reply target", async () => { const cache = createTelegramMessageCache(); for (const id of [100, 101, 102, 200, 201]) {