From 5cad6d3f9d3619f69c75dba60c85525eee86cece Mon Sep 17 00:00:00 2001 From: Clawdbot Date: Wed, 28 Jan 2026 15:25:14 +0100 Subject: [PATCH] feat(telegram): display thread ID for DM topics in Sessions tab - Add ThreadLabel for DM threads (format: 'Thread: N') - Add unit tests for ThreadLabel in DM threads - Fix UI: read URL session param before syncTabWithLocation overwrites it --- .../bot-message-context.dm-threads.test.ts | 27 +++++++++++++++++++ src/telegram/bot-message-context.ts | 2 ++ ui/src/ui/app-lifecycle.ts | 2 ++ 3 files changed, 31 insertions(+) diff --git a/src/telegram/bot-message-context.dm-threads.test.ts b/src/telegram/bot-message-context.dm-threads.test.ts index d710e0b1b..23dd92f21 100644 --- a/src/telegram/bot-message-context.dm-threads.test.ts +++ b/src/telegram/bot-message-context.dm-threads.test.ts @@ -56,6 +56,33 @@ describe("buildTelegramMessageContext dm thread sessions", () => { expect(ctx?.ctxPayload?.SessionKey).toBe("agent:main:main:thread:42"); }); + it("sets ThreadLabel for dm topics for display in Sessions tab", async () => { + const ctx = await buildContext({ + message_id: 1, + chat: { id: 1234, type: "private" }, + date: 1700000000, + text: "hello", + message_thread_id: 42, + from: { id: 42, first_name: "Alice" }, + }); + + expect(ctx).not.toBeNull(); + expect(ctx?.ctxPayload?.ThreadLabel).toBe("Thread: 42"); + }); + + it("does not set ThreadLabel for dm without thread id", async () => { + const ctx = await buildContext({ + message_id: 1, + chat: { id: 1234, type: "private" }, + date: 1700000000, + text: "hello", + from: { id: 42, first_name: "Alice" }, + }); + + expect(ctx).not.toBeNull(); + expect(ctx?.ctxPayload?.ThreadLabel).toBeUndefined(); + }); + it("keeps legacy dm session key when no thread id", async () => { const ctx = await buildContext({ message_id: 2, diff --git a/src/telegram/bot-message-context.ts b/src/telegram/bot-message-context.ts index abd06cdef..ccc9407cf 100644 --- a/src/telegram/bot-message-context.ts +++ b/src/telegram/bot-message-context.ts @@ -606,6 +606,8 @@ export const buildTelegramMessageContext = async ({ // For groups: use resolvedThreadId (forum topics only); for DMs: use raw messageThreadId MessageThreadId: isGroup ? resolvedThreadId : messageThreadId, IsForum: isForum, + // DM thread label for display in Sessions tab (e.g., "Sender Name (Thread: 42)") + ThreadLabel: !isGroup && messageThreadId != null ? `Thread: ${messageThreadId}` : undefined, // Originating channel for reply routing. OriginatingChannel: "telegram" as const, OriginatingTo: `telegram:${chatId}`, diff --git a/ui/src/ui/app-lifecycle.ts b/ui/src/ui/app-lifecycle.ts index cf5214250..40c6f0b1f 100644 --- a/ui/src/ui/app-lifecycle.ts +++ b/ui/src/ui/app-lifecycle.ts @@ -35,6 +35,8 @@ type LifecycleHost = { export function handleConnected(host: LifecycleHost) { host.basePath = inferBasePath(); + // Apply URL settings FIRST so sessionKey is read before syncTabWithLocation + // overwrites it with the localStorage value applySettingsFromUrl( host as unknown as Parameters[0], );