Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 22 additions & 3 deletions src/gateway/helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,21 +51,40 @@ function getChatId(thread: any, message: any): string {

/**
* Resolve the agent-facing thread ID from a chat message.
* Private/DM → "main", group → "group:<chatId>"
* Private/DM → "main", group → "group:<chatId>", forum topic → "group:<chatId>:topic:<topicId>"
*/
export function resolveAgentThreadId(thread: any, message: any): string {
const chatType = String(message?.chat?.type ?? thread?.chat?.type ?? thread?.type ?? "").toLowerCase();

// Extract topic ID if present (forum threads)
const topicId = message?.message_thread_id ?? thread?.messageThreadId ?? extractTopicFromThreadId(thread?.id);

if (["private", "dm", "direct", "im"].includes(chatType)) return "main";
if (["group", "supergroup", "channel"].includes(chatType)) return `group:${getChatId(thread, message)}`;
if (["group", "supergroup", "channel"].includes(chatType)) {
const base = `group:${getChatId(thread, message)}`;
return topicId ? `${base}:topic:${topicId}` : base;
}

const telegramChatId = telegramChatIdFromThreadId(thread?.id);
if (telegramChatId !== null) {
return telegramChatId < 0 ? `group:${telegramChatId}` : "main";
const base = telegramChatId < 0 ? `group:${telegramChatId}` : "main";
return (topicId && telegramChatId < 0) ? `${base}:topic:${topicId}` : base;
}

return String(thread?.id ?? "main");
}

/** Extract message_thread_id from thread ID string like "telegram:chatId:topicId" */
function extractTopicFromThreadId(threadId: string | undefined): number | undefined {
if (!threadId) return undefined;
const parts = threadId.split(":");
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Guard topic parsing against non-string thread IDs

resolveAgentThreadId now always calls extractTopicFromThreadId(thread?.id) before any transport checks, but extractTopicFromThreadId assumes threadId is a string and unconditionally does threadId.split(":"). If a transport/event supplies a numeric or otherwise non-string thread.id, this throws at runtime and prevents message routing entirely, whereas the previous logic safely fell back to String(thread?.id ?? "main"). Please add a typeof threadId === "string" guard before splitting so non-string IDs don't crash routing.

Useful? React with 👍 / 👎.

if (parts.length === 3 && parts[0] === "telegram") {
const parsed = parseInt(parts[2], 10);
return Number.isFinite(parsed) ? parsed : undefined;
}
return undefined;
}

// ── System Resources ─────────────────────────────────

export interface SystemResources {
Expand Down
18 changes: 18 additions & 0 deletions test/gateway-helpers.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,24 @@ describe("gateway/helpers", () => {
expect(resolveAgentThreadId(thread, message)).toBe("group:-456");
});

it("routes forum topic to separate session", () => {
const thread = { id: "telegram:-100123:42" };
const message = { chat: { type: "supergroup", id: -100123 } };
expect(resolveAgentThreadId(thread, message)).toBe("group:-100123:topic:42");
});

it("routes forum topic via message_thread_id", () => {
const thread = { id: "telegram:-100123" };
const message = { chat: { type: "supergroup", id: -100123 }, message_thread_id: 99 };
expect(resolveAgentThreadId(thread, message)).toBe("group:-100123:topic:99");
});

it("group without topic stays flat", () => {
const thread = { id: "telegram:-100123" };
const message = { chat: { type: "supergroup", id: -100123 } };
expect(resolveAgentThreadId(thread, message)).toBe("group:-100123");
});

it("routes telegram positive IDs to main", () => {
const thread = { id: "telegram:789" };
const message = {};
Expand Down
Loading