diff --git a/src/config/sessions.test.ts b/src/config/sessions.test.ts index 0f867e93077..c6f92246e02 100644 --- a/src/config/sessions.test.ts +++ b/src/config/sessions.test.ts @@ -176,7 +176,7 @@ describe("sessions", () => { }); }); - it("updateLastRoute clears threadId when deliveryContext explicitly omits it", async () => { + it("updateLastRoute clears threadId when explicit route omits threadId", async () => { const mainSessionKey = "agent:main:main"; const dir = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-sessions-")); const storePath = path.join(dir, "sessions.json"); @@ -209,7 +209,6 @@ describe("sessions", () => { deliveryContext: { channel: "telegram", to: "222", - threadId: undefined, }, }); diff --git a/src/config/sessions/store.ts b/src/config/sessions/store.ts index 8f9ebf1d03a..a7fe48e1444 100644 --- a/src/config/sessions/store.ts +++ b/src/config/sessions/store.ts @@ -440,10 +440,21 @@ export async function updateLastRoute(params: { }); const mergedInput = mergeDeliveryContext(explicitContext, inlineContext); const explicitDeliveryContext = params.deliveryContext; - const clearThreadFromFallback = + const explicitThreadFromDeliveryContext = explicitDeliveryContext != null && - Object.prototype.hasOwnProperty.call(explicitDeliveryContext, "threadId") && - explicitDeliveryContext.threadId == null; + Object.prototype.hasOwnProperty.call(explicitDeliveryContext, "threadId") + ? explicitDeliveryContext.threadId + : undefined; + const explicitThreadValue = + explicitThreadFromDeliveryContext ?? + (threadId != null && threadId !== "" ? threadId : undefined); + const explicitRouteProvided = Boolean( + explicitContext?.channel || + explicitContext?.to || + inlineContext?.channel || + inlineContext?.to, + ); + const clearThreadFromFallback = explicitRouteProvided && explicitThreadValue == null; const fallbackContext = clearThreadFromFallback ? removeThreadFromDeliveryContext(deliveryContextFromSession(existing)) : deliveryContextFromSession(existing); diff --git a/src/telegram/send.returns-undefined-empty-input.test.ts b/src/telegram/send.returns-undefined-empty-input.test.ts index d6c71c44d59..a93a1e41b66 100644 --- a/src/telegram/send.returns-undefined-empty-input.test.ts +++ b/src/telegram/send.returns-undefined-empty-input.test.ts @@ -508,6 +508,23 @@ describe("sendMessageTelegram", () => { expect(res.messageId).toBe("58"); }); + it("does not retry thread-not-found when no message_thread_id was provided", async () => { + const chatId = "123"; + const threadErr = new Error("400: Bad Request: message thread not found"); + const sendMessage = vi.fn().mockRejectedValueOnce(threadErr); + const api = { sendMessage } as unknown as { + sendMessage: typeof sendMessage; + }; + + await expect( + sendMessageTelegram(chatId, "hello forum", { + token: "tok", + api, + }), + ).rejects.toThrow("message thread not found"); + expect(sendMessage).toHaveBeenCalledTimes(1); + }); + it("sets disable_notification when silent is true", async () => { const chatId = "123"; const sendMessage = vi.fn().mockResolvedValue({ diff --git a/src/telegram/send.ts b/src/telegram/send.ts index 570f68aa73b..3d1d32bb82a 100644 --- a/src/telegram/send.ts +++ b/src/telegram/send.ts @@ -179,7 +179,17 @@ function isTelegramThreadNotFoundError(err: unknown): boolean { } function hasMessageThreadIdParam(params?: Record): boolean { - return Boolean(params && Object.hasOwn(params, "message_thread_id")); + if (!params) { + return false; + } + const value = params.message_thread_id; + if (typeof value === "number") { + return Number.isFinite(value); + } + if (typeof value === "string") { + return value.trim().length > 0; + } + return false; } function removeMessageThreadIdParam(