From 7a0a4c5f2a3b778deec2eacafbeffc45f24579cf Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Tue, 4 Aug 2026 10:21:32 -0700 Subject: [PATCH] fix(telegram): correlate deliveries to explicit topics Use the explicit topic selected by Telegram sends when recording delivery correlation, preventing successful message, poll, and sticker actions from leaving the matching inbound event falsely undelivered. --- .../telegram/src/action-runtime.test.ts | 50 ++++++++++++++++--- extensions/telegram/src/action-runtime.ts | 2 +- 2 files changed, 43 insertions(+), 9 deletions(-) diff --git a/extensions/telegram/src/action-runtime.test.ts b/extensions/telegram/src/action-runtime.test.ts index c3a015c53f34..12d0439d5acd 100644 --- a/extensions/telegram/src/action-runtime.test.ts +++ b/extensions/telegram/src/action-runtime.test.ts @@ -1117,12 +1117,37 @@ describe("handleTelegramAction", () => { endUserRequest(); }); - it("marks topic room-event delivery when send uses a separate thread id", async () => { + it.each([ + { + label: "a separate forum topic", + to: "-100123", + threadId: 77, + expectedTarget: "-100123:topic:77", + }, + { + label: "an explicit forum topic instead of a stale embedded topic", + to: "-100123:topic:271", + threadId: 404, + expectedTarget: "-100123:topic:404", + }, + { + label: "an explicit private-chat topic instead of a stale embedded topic", + to: "123:topic:7", + threadId: 11, + expectedTarget: "123:topic:11", + }, + { + label: "an explicitly selected General forum topic", + to: "-100123:topic:271", + threadId: 1, + expectedTarget: "-100123:topic:1", + }, + ])("marks topic room-event delivery for $label", async ({ to, threadId, expectedTarget }) => { let count = 0; const end = telegramInboundEventDelivery.begin( "telegram-session", { - outboundTo: "-100123:topic:77", + outboundTo: expectedTarget, markInboundEventDelivered: () => { count += 1; }, @@ -1133,14 +1158,16 @@ describe("handleTelegramAction", () => { await handleTelegramAction( { action: "sendMessage", - to: "-100123", - threadId: 77, + to, + threadId, content: "Hello from a room event topic", }, telegramConfig(), { sessionKey: "telegram-session", inboundEventKind: "room_event" }, ); + const sent = mockCall(sendMessageTelegram, 0, "room event topic send"); + expect(requireRecord(sent[2], "room event topic options").messageThreadId).toBe(threadId); expect(count).toBe(1); end(); }); @@ -1177,7 +1204,8 @@ describe("handleTelegramAction", () => { name: "poll", params: { action: "poll", - to: "@testchannel", + to: "-100123:topic:271", + threadId: 404, question: "Ready?", answers: ["Yes", "No"], }, @@ -1187,17 +1215,18 @@ describe("handleTelegramAction", () => { name: "sticker", params: { action: "sendSticker", - to: "@testchannel", + to: "-100123:topic:271", + threadId: 404, fileId: "sticker-1", }, cfg: telegramConfig({ actions: { sticker: true } }), }, - ])("marks room-event delivery after successful $name actions", async ({ params, cfg }) => { + ])("marks room-event delivery after successful $name actions", async ({ name, params, cfg }) => { let count = 0; const end = telegramInboundEventDelivery.begin( "telegram-session", { - outboundTo: "@testchannel", + outboundTo: "-100123:topic:404", markInboundEventDelivered: () => { count += 1; }, @@ -1210,6 +1239,11 @@ describe("handleTelegramAction", () => { inboundEventKind: "room_event", }); + const sent = + name === "poll" + ? mockCall(sendPollTelegram, 0, "room event topic poll") + : mockCall(sendStickerTelegram, 0, "room event topic sticker"); + expect(requireRecord(sent[2], "room event topic options").messageThreadId).toBe(404); expect(count).toBe(1); end(); }); diff --git a/extensions/telegram/src/action-runtime.ts b/extensions/telegram/src/action-runtime.ts index 60836b0a2ec3..f7e0268bb5ac 100644 --- a/extensions/telegram/src/action-runtime.ts +++ b/extensions/telegram/src/action-runtime.ts @@ -161,7 +161,7 @@ function resolveActionTopicNameCacheScope(cfg: OpenClawConfig, accountId?: strin function formatTelegramDeliveryTarget(to: string, messageThreadId?: number | null): string { const parsed = parseTelegramTarget(to); - const topicId = parsed.messageThreadId ?? messageThreadId; + const topicId = messageThreadId ?? parsed.messageThreadId; if (topicId == null) { return to; }