diff --git a/src/controller.test.ts b/src/controller.test.ts index af9973e..662f6cb 100644 --- a/src/controller.test.ts +++ b/src/controller.test.ts @@ -4051,6 +4051,46 @@ describe("Discord controller flows", () => { expect(result).toEqual({ handled: false }); }); + it("claims inbound Telegram topic messages when the event carries chat id plus thread id", async () => { + const { controller } = await createControllerHarness(); + await (controller as any).store.upsertBinding({ + conversation: { + channel: "telegram", + accountId: "default", + conversationId: "123:topic:456", + parentConversationId: "123", + }, + sessionKey: "session-1", + threadId: "thread-1", + workspaceDir: "/repo/openclaw", + updatedAt: Date.now(), + }); + const startTurn = vi.fn(() => ({ + result: Promise.resolve({ + threadId: "thread-1", + text: "hello", + }), + getThreadId: () => "thread-1", + queueMessage: vi.fn(async () => true), + interrupt: vi.fn(async () => {}), + isAwaitingInput: () => false, + submitPendingInput: vi.fn(async () => false), + submitPendingInputPayload: vi.fn(async () => false), + })); + (controller as any).client.startTurn = startTurn; + + const result = await controller.handleInboundClaim({ + content: "There?", + channel: "telegram", + accountId: "default", + conversationId: "123", + threadId: 456, + }); + + expect(result).toEqual({ handled: true }); + expect(startTurn).toHaveBeenCalled(); + }); + it("uses a raw Discord channel id for the typing lease on inbound claims", async () => { const { controller, discordTypingStart } = await createControllerHarness(); await (controller as any).store.upsertBinding({ diff --git a/src/controller.ts b/src/controller.ts index 4ba6fb1..e3d73c1 100644 --- a/src/controller.ts +++ b/src/controller.ts @@ -559,6 +559,34 @@ function toConversationTargetFromCommand(ctx: PluginCommandContext): Conversatio return null; } +function normalizeTelegramThreadId(threadId: string | number | undefined): number | undefined { + if (typeof threadId === "number") { + return Number.isFinite(threadId) ? threadId : undefined; + } + if (typeof threadId === "string") { + const parsed = Number(threadId); + return Number.isFinite(parsed) ? parsed : undefined; + } + return undefined; +} + +function buildTelegramTopicConversationId( + conversationId: string | undefined, + threadId: number | undefined, +): string | undefined { + if (!conversationId) { + return undefined; + } + if (threadId == null) { + return conversationId; + } + const topicSeparator = ":topic:"; + const baseConversationId = conversationId.includes(topicSeparator) + ? conversationId.split(topicSeparator)[0] + : conversationId; + return `${baseConversationId}:topic:${threadId}`; +} + function toConversationTargetFromInbound(event: { channel: string; accountId?: string; @@ -573,6 +601,7 @@ function toConversationTargetFromInbound(event: { } const channel = event.channel.trim().toLowerCase(); const conversationIdRaw = event.conversationId?.trim(); + const telegramThreadId = normalizeTelegramThreadId(event.threadId); const conversationId = channel === "discord" ? (() => { @@ -588,10 +617,18 @@ function toConversationTargetFromInbound(event: { const isChannel = Boolean(event.parentConversationId?.trim() || event.isGroup || guildId); return `${isChannel ? "channel" : "user"}:${normalized}`; })() + : channel === "telegram" + ? buildTelegramTopicConversationId( + normalizeTelegramChatId(conversationIdRaw), + telegramThreadId, + ) : event.conversationId; const parentConversationId = channel === "discord" ? normalizeDiscordConversationId(event.parentConversationId) + : channel === "telegram" && telegramThreadId != null + ? normalizeTelegramChatId(event.parentConversationId) ?? + normalizeTelegramChatId(conversationIdRaw)?.split(":topic:")[0] : event.parentConversationId; if (!conversationId) { return null; @@ -604,13 +641,7 @@ function toConversationTargetFromInbound(event: { threadId: channel === "discord" ? undefined - : typeof event.threadId === "number" - ? event.threadId - : typeof event.threadId === "string" - ? Number.isFinite(Number(event.threadId)) - ? Number(event.threadId) - : undefined - : undefined, + : telegramThreadId, }; }