Skip to content

Commit 194bf6a

Browse files
committed
fix(telegram): keep forum topic sessions stable
1 parent bcc21fa commit 194bf6a

7 files changed

Lines changed: 7 additions & 156 deletions

extensions/telegram/src/bot-message-context.dm-threads.test.ts

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -232,7 +232,7 @@ describe("buildTelegramMessageContext group sessions without forum", () => {
232232
expect(resolveStorePath).toHaveBeenCalledTimes(1);
233233
});
234234

235-
it("uses root-message topic session for forum groups with message_thread_id", async () => {
235+
it("uses topic session for forum groups with message_thread_id", async () => {
236236
const ctx = await buildContext({
237237
message_id: 1,
238238
chat: { id: -1001234567890, type: "supergroup", title: "Test Forum", is_forum: true },
@@ -242,10 +242,8 @@ describe("buildTelegramMessageContext group sessions without forum", () => {
242242
from: { id: 42, first_name: "Alice" },
243243
});
244244

245-
// Session key should include the forum topic and root message for concurrent topic requests.
246-
expect(ctx?.ctxPayload?.SessionKey).toBe(
247-
"agent:main:telegram:group:-1001234567890:topic:99:thread:topic:99:message:1",
248-
);
245+
// Session key SHOULD include :topic:99 for forums
246+
expect(ctx?.ctxPayload?.SessionKey).toBe("agent:main:telegram:group:-1001234567890:topic:99");
249247
expect(ctx?.ctxPayload?.MessageThreadId).toBe(99);
250248
expect(ctx?.ctxPayload?.OriginatingTo).toBe("telegram:-1001234567890:topic:99");
251249
});

extensions/telegram/src/bot-message-context.named-account-dm.test-support.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -171,7 +171,7 @@ describe("buildTelegramMessageContext named-account DM fallback", () => {
171171
expect(ctx?.route.accountId).toBe("atlas");
172172
expect(ctx?.route.agentId).toBe("topic-agent");
173173
expect(ctx?.ctxPayload?.SessionKey).toBe(
174-
"agent:topic-agent:telegram:group:-1001234567890:topic:42:thread:topic:42:message:1",
174+
"agent:topic-agent:telegram:group:-1001234567890:topic:42",
175175
);
176176
});
177177

extensions/telegram/src/bot-message-context.native-thread-session.test.ts

Lines changed: 0 additions & 78 deletions
This file was deleted.

extensions/telegram/src/bot-message-context.require-mention.test.ts

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -169,9 +169,7 @@ describe("buildTelegramMessageContext requireMention precedence", () => {
169169
const [activationOptions] = activationCalls[0] ?? [];
170170
expect(activationOptions?.chatId).toBe(-1001234567890);
171171
expect(activationOptions?.messageThreadId).toBe(99);
172-
expect(activationOptions?.sessionKey).toBe(
173-
"agent:main:telegram:group:-1001234567890:topic:99:thread:topic:99:message:1",
174-
);
172+
expect(activationOptions?.sessionKey).toBe("agent:main:telegram:group:-1001234567890:topic:99");
175173
});
176174

177175
it("lets explicit topic requireMention=true override always activation", async () => {

extensions/telegram/src/bot-message-context.test-harness.ts

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,6 @@ type BuildTelegramMessageContextForTestParams = {
2323
message: Record<string, unknown>;
2424
me?: Record<string, unknown>;
2525
allMedia?: TelegramMediaRef[];
26-
replyChain?: BuildTelegramMessageContextParams["replyChain"];
2726
options?: BuildTelegramMessageContextParams["options"];
2827
cfg?: Record<string, unknown>;
2928
accountId?: string;
@@ -113,7 +112,6 @@ export async function buildTelegramMessageContextForTest(
113112
me: { id: 7, username: "bot", ...params.me },
114113
} as never,
115114
allMedia: params.allMedia ?? [],
116-
replyChain: params.replyChain ?? [],
117115
storeAllowFrom: [],
118116
options: params.options ?? {},
119117
bot: {

extensions/telegram/src/bot-message-context.topic-agentid.test.ts

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -64,9 +64,7 @@ describe("buildTelegramMessageContext per-topic agentId routing", () => {
6464
it("uses group-level agent when no topic agentId is set", async () => {
6565
const ctx = await buildForumContext({ topicConfig: { systemPrompt: "Be nice" } });
6666

67-
expect(ctx?.ctxPayload?.SessionKey).toBe(
68-
"agent:main:telegram:group:-1001234567890:topic:3:thread:topic:3:message:1",
69-
);
67+
expect(ctx?.ctxPayload?.SessionKey).toBe("agent:main:telegram:group:-1001234567890:topic:3");
7068
});
7169

7270
it("routes to topic-specific agent when agentId is set", async () => {

extensions/telegram/src/bot-message-context.ts

Lines changed: 1 addition & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -86,50 +86,6 @@ type TelegramStatusReactionController = {
8686
restoreInitial: () => void | Promise<void>;
8787
};
8888

89-
function normalizeTelegramNativeThreadMessageId(value: unknown): string | undefined {
90-
if (typeof value === "number" && Number.isFinite(value)) {
91-
return String(Math.trunc(value));
92-
}
93-
if (typeof value !== "string") {
94-
return undefined;
95-
}
96-
const trimmed = value.trim();
97-
return trimmed ? trimmed : undefined;
98-
}
99-
100-
function resolveTelegramNativeReplyRootThreadId(params: {
101-
isGroup: boolean;
102-
isForum: boolean;
103-
resolvedThreadId?: number;
104-
msg: BuildTelegramMessageContextParams["primaryCtx"]["message"];
105-
replyChain: NonNullable<BuildTelegramMessageContextParams["replyChain"]>;
106-
}): string | undefined {
107-
if (!params.isGroup || !params.isForum) {
108-
return undefined;
109-
}
110-
111-
const chainRootId = params.replyChain
112-
.toReversed()
113-
.map((entry) => normalizeTelegramNativeThreadMessageId(entry.messageId))
114-
.find(Boolean);
115-
const directReplyId =
116-
params.msg.reply_to_message?.forum_topic_created ||
117-
params.msg.reply_to_message?.forum_topic_edited ||
118-
params.msg.reply_to_message?.forum_topic_closed ||
119-
params.msg.reply_to_message?.forum_topic_reopened
120-
? undefined
121-
: normalizeTelegramNativeThreadMessageId(params.msg.reply_to_message?.message_id);
122-
const rootMessageId =
123-
chainRootId ?? directReplyId ?? normalizeTelegramNativeThreadMessageId(params.msg.message_id);
124-
if (!rootMessageId) {
125-
return undefined;
126-
}
127-
128-
const topicPrefix =
129-
params.resolvedThreadId != null ? `topic:${Math.trunc(params.resolvedThreadId)}:` : "";
130-
return `${topicPrefix}message:${rootMessageId}`;
131-
}
132-
13389
export type TelegramMessageContext = {
13490
ctxPayload: TelegramMessageContextPayload["ctxPayload"];
13591
turn: TelegramMessageContextPayload["turn"];
@@ -307,9 +263,6 @@ export const buildTelegramMessageContext = async ({
307263
candidate.matchedBy === "default";
308264
const isNamedAccountFallback = requiresExplicitAccountBinding(route);
309265
const hasExplicitTopicRoute = isGroup && Boolean(topicConfig?.agentId?.trim());
310-
// Named-account groups still require an explicit binding; DMs get a
311-
// per-account fallback session key below to preserve isolation. An explicit
312-
// account-scoped topic agent is a configured group route, not a fallback.
313266
if (isNamedAccountFallback && isGroup && !hasExplicitTopicRoute) {
314267
logInboundDrop({
315268
log: logVerbose,
@@ -459,26 +412,10 @@ export const buildTelegramMessageContext = async ({
459412
dmThreadId,
460413
botHasTopicsEnabled: resolveTelegramBotHasTopicsEnabled(primaryCtx.me),
461414
});
462-
const nativeReplyRootThreadId =
463-
bindingMode.kind !== "none"
464-
? undefined
465-
: resolveTelegramNativeReplyRootThreadId({
466-
isGroup,
467-
isForum,
468-
resolvedThreadId,
469-
msg,
470-
replyChain,
471-
});
472415
const threadKeys =
473416
useDmThreadSession && dmThreadId != null
474417
? resolveThreadSessionKeys({ baseSessionKey, threadId: `${chatId}:${dmThreadId}` })
475-
: nativeReplyRootThreadId
476-
? resolveThreadSessionKeys({
477-
baseSessionKey,
478-
threadId: nativeReplyRootThreadId,
479-
parentSessionKey: baseSessionKey,
480-
})
481-
: null;
418+
: null;
482419
const sessionKey = threadKeys?.sessionKey ?? baseSessionKey;
483420
route = {
484421
...route,

0 commit comments

Comments
 (0)