fix: handle Telegram General topic thread params (#848) (thanks @azade-c)
This commit is contained in:
@@ -18,6 +18,7 @@
|
||||
- Browser: extension mode recovers when only one tab is attached (stale targetId fallback).
|
||||
- Browser: fix `tab not found` for extension relay snapshots/actions when Playwright blocks `newCDPSession` (use the single available Page).
|
||||
- Telegram: add bidirectional reaction support with configurable notifications and agent guidance. (#964) — thanks @bohdanpodvirnyi.
|
||||
- Telegram: skip `message_thread_id=1` for General topic sends while keeping typing indicators. (#848) — thanks @azade-c.
|
||||
- Discord: allow allowlisted guilds without channel lists to receive messages when `groupPolicy="allowlist"`. — thanks @thewilloftheshadow.
|
||||
|
||||
## 2026.1.14-1
|
||||
|
||||
@@ -188,6 +188,7 @@ Disable with:
|
||||
Telegram forum topics include a `message_thread_id` per message. Clawdbot:
|
||||
- Appends `:topic:<threadId>` to the Telegram group session key so each topic is isolated.
|
||||
- Sends typing indicators and replies with `message_thread_id` so responses stay in the topic.
|
||||
- General topic (thread id `1`) is special: message sends omit `message_thread_id` (Telegram rejects it), but typing indicators still include it.
|
||||
- Exposes `MessageThreadId` + `IsForum` in template context for routing/templating.
|
||||
- Topic-specific configuration is available under `channels.telegram.groups.<chatId>.topics.<threadId>` (skills, allowlists, auto-reply, system prompts, disable).
|
||||
|
||||
|
||||
@@ -17,7 +17,6 @@ import {
|
||||
buildSenderName,
|
||||
buildTelegramGroupFrom,
|
||||
buildTelegramGroupPeerId,
|
||||
buildTelegramThreadParams,
|
||||
buildTypingThreadParams,
|
||||
describeReplyTarget,
|
||||
extractTelegramLocation,
|
||||
|
||||
@@ -352,10 +352,8 @@ describe("createTelegramBot", () => {
|
||||
getFile: async () => ({ download: async () => new Uint8Array() }),
|
||||
});
|
||||
|
||||
expect(sendMessageSpy).toHaveBeenCalledWith(
|
||||
"-1001234567890",
|
||||
expect.any(String),
|
||||
expect.objectContaining({ message_thread_id: 1 }),
|
||||
);
|
||||
expect(sendMessageSpy).toHaveBeenCalledTimes(1);
|
||||
const sendParams = sendMessageSpy.mock.calls[0]?.[2] as { message_thread_id?: number };
|
||||
expect(sendParams?.message_thread_id).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1710,11 +1710,9 @@ describe("createTelegramBot", () => {
|
||||
getFile: async () => ({ download: async () => new Uint8Array() }),
|
||||
});
|
||||
|
||||
expect(sendMessageSpy).toHaveBeenCalledWith(
|
||||
"-1001234567890",
|
||||
expect.any(String),
|
||||
expect.objectContaining({ message_thread_id: 1 }),
|
||||
);
|
||||
expect(sendMessageSpy).toHaveBeenCalledTimes(1);
|
||||
const sendParams = sendMessageSpy.mock.calls[0]?.[2] as { message_thread_id?: number };
|
||||
expect(sendParams?.message_thread_id).toBeUndefined();
|
||||
});
|
||||
|
||||
it("applies topic skill filters and system prompts", async () => {
|
||||
|
||||
30
src/telegram/bot/helpers.test.ts
Normal file
30
src/telegram/bot/helpers.test.ts
Normal file
@@ -0,0 +1,30 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { buildTelegramThreadParams, buildTypingThreadParams } from "./helpers.js";
|
||||
|
||||
describe("buildTelegramThreadParams", () => {
|
||||
it("omits General topic thread id for message sends", () => {
|
||||
expect(buildTelegramThreadParams(1)).toBeUndefined();
|
||||
});
|
||||
|
||||
it("includes non-General topic thread ids", () => {
|
||||
expect(buildTelegramThreadParams(99)).toEqual({ message_thread_id: 99 });
|
||||
});
|
||||
|
||||
it("normalizes thread ids to integers", () => {
|
||||
expect(buildTelegramThreadParams(42.9)).toEqual({ message_thread_id: 42 });
|
||||
});
|
||||
});
|
||||
|
||||
describe("buildTypingThreadParams", () => {
|
||||
it("returns undefined when no thread id is provided", () => {
|
||||
expect(buildTypingThreadParams(undefined)).toBeUndefined();
|
||||
});
|
||||
|
||||
it("includes General topic thread id for typing indicators", () => {
|
||||
expect(buildTypingThreadParams(1)).toEqual({ message_thread_id: 1 });
|
||||
});
|
||||
|
||||
it("normalizes thread ids to integers", () => {
|
||||
expect(buildTypingThreadParams(42.9)).toEqual({ message_thread_id: 42 });
|
||||
});
|
||||
});
|
||||
@@ -21,25 +21,29 @@ export function resolveTelegramForumThreadId(params: {
|
||||
|
||||
/**
|
||||
* Build thread params for Telegram API calls (messages, media).
|
||||
* Excludes General topic (id=1) as Telegram rejects explicit message_thread_id=1
|
||||
* for sendMessage calls in forum supergroups ("message thread not found" error).
|
||||
* General forum topic (id=1) must be treated like a regular supergroup send:
|
||||
* Telegram rejects sendMessage/sendMedia with message_thread_id=1 ("thread not found").
|
||||
*/
|
||||
export function buildTelegramThreadParams(messageThreadId?: number) {
|
||||
if (messageThreadId == null || messageThreadId === TELEGRAM_GENERAL_TOPIC_ID) {
|
||||
if (messageThreadId == null) {
|
||||
return undefined;
|
||||
}
|
||||
return { message_thread_id: messageThreadId };
|
||||
const normalized = Math.trunc(messageThreadId);
|
||||
if (normalized === TELEGRAM_GENERAL_TOPIC_ID) {
|
||||
return undefined;
|
||||
}
|
||||
return { message_thread_id: normalized };
|
||||
}
|
||||
|
||||
/**
|
||||
* Build thread params for typing indicators (sendChatAction).
|
||||
* Unlike sendMessage, sendChatAction accepts message_thread_id=1 for General topic.
|
||||
* Empirically, General topic (id=1) needs message_thread_id for typing to appear.
|
||||
*/
|
||||
export function buildTypingThreadParams(messageThreadId?: number) {
|
||||
if (messageThreadId == null) {
|
||||
return undefined;
|
||||
}
|
||||
return { message_thread_id: messageThreadId };
|
||||
return { message_thread_id: Math.trunc(messageThreadId) };
|
||||
}
|
||||
|
||||
export function resolveTelegramStreamMode(
|
||||
|
||||
Reference in New Issue
Block a user