fix(telegram): restore thread_id=1 handling for DMs (regression from 19b8416a8) (openclaw#10942) thanks @garnetlyx

Verified:
- pnpm install --frozen-lockfile
- pnpm build
- pnpm test:macmini

Co-authored-by: garnetlyx <12513503+garnetlyx@users.noreply.github.com>
Co-authored-by: Tak Hoffman <781889+Takhoffman@users.noreply.github.com>
This commit is contained in:
Garnet Liu
2026-02-16 00:21:18 +08:00
committed by GitHub
parent 1843bcf1db
commit cc0bfa0f39
5 changed files with 42 additions and 11 deletions

View File

@@ -169,7 +169,7 @@ describe("deliverReplies", () => {
);
});
it("keeps message_thread_id=1 when allowed", async () => {
it("does not include message_thread_id for DMs (threads don't exist in private chats)", async () => {
const runtime = { error: vi.fn(), log: vi.fn() };
const sendMessage = vi.fn().mockResolvedValue({
message_id: 4,
@@ -191,7 +191,7 @@ describe("deliverReplies", () => {
expect(sendMessage).toHaveBeenCalledWith(
"123",
expect.any(String),
expect.objectContaining({
expect.not.objectContaining({
message_thread_id: 1,
}),
);

View File

@@ -43,9 +43,24 @@ describe("buildTelegramThreadParams", () => {
});
});
it("keeps thread id=1 for dm threads", () => {
expect(buildTelegramThreadParams({ id: 1, scope: "dm" })).toEqual({
message_thread_id: 1,
it("skips thread id for dm threads (DMs don't have threads)", () => {
expect(buildTelegramThreadParams({ id: 1, scope: "dm" })).toBeUndefined();
expect(buildTelegramThreadParams({ id: 2, scope: "dm" })).toBeUndefined();
});
it("normalizes and skips thread id for dm threads even with edge values", () => {
expect(buildTelegramThreadParams({ id: 0, scope: "dm" })).toBeUndefined();
expect(buildTelegramThreadParams({ id: -1, scope: "dm" })).toBeUndefined();
expect(buildTelegramThreadParams({ id: 1.9, scope: "dm" })).toBeUndefined();
});
it("handles thread id 0 for non-dm scopes", () => {
// id=0 should be included for forum and none scopes (not falsy)
expect(buildTelegramThreadParams({ id: 0, scope: "forum" })).toEqual({
message_thread_id: 0,
});
expect(buildTelegramThreadParams({ id: 0, scope: "none" })).toEqual({
message_thread_id: 0,
});
});

View File

@@ -56,17 +56,34 @@ export function resolveTelegramThreadSpec(params: {
/**
* Build thread params for Telegram API calls (messages, media).
*
* IMPORTANT: Thread IDs behave differently based on chat type:
* - DMs (private chats): Never send thread_id (threads don't exist)
* - Forum topics: Skip thread_id=1 (General topic), include others
* - Regular groups: Thread IDs are ignored by Telegram
*
* 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").
*
* @param thread - Thread specification with ID and scope
* @returns API params object or undefined if thread_id should be omitted
*/
export function buildTelegramThreadParams(thread?: TelegramThreadSpec | null) {
if (!thread?.id) {
if (thread?.id == null) {
return undefined;
}
const normalized = Math.trunc(thread.id);
if (normalized === TELEGRAM_GENERAL_TOPIC_ID && thread.scope === "forum") {
// Never send thread_id for DMs (threads don't exist in private chats)
if (thread.scope === "dm") {
return undefined;
}
// Telegram rejects message_thread_id=1 for General forum topic
if (normalized === TELEGRAM_GENERAL_TOPIC_ID) {
return undefined;
}
return { message_thread_id: normalized };
}

View File

@@ -94,7 +94,7 @@ describe("createTelegramDraftStream", () => {
await vi.waitFor(() => expect(api.sendMessage).toHaveBeenCalledWith(123, "Hello", undefined));
});
it("keeps message_thread_id for dm threads and clears preview on cleanup", async () => {
it("omits message_thread_id for dm threads and clears preview on cleanup", async () => {
const api = {
sendMessage: vi.fn().mockResolvedValue({ message_id: 17 }),
editMessageText: vi.fn().mockResolvedValue(true),
@@ -108,9 +108,7 @@ describe("createTelegramDraftStream", () => {
});
stream.update("Hello");
await vi.waitFor(() =>
expect(api.sendMessage).toHaveBeenCalledWith(123, "Hello", { message_thread_id: 1 }),
);
await vi.waitFor(() => expect(api.sendMessage).toHaveBeenCalledWith(123, "Hello", undefined));
await stream.clear();
expect(api.deleteMessage).toHaveBeenCalledWith(123, 17);