diff --git a/src/plugins/runtime/runtime-discord-typing.test.ts b/src/plugins/runtime/runtime-discord-typing.test.ts index a323ce284bc..1eb5b6fd315 100644 --- a/src/plugins/runtime/runtime-discord-typing.test.ts +++ b/src/plugins/runtime/runtime-discord-typing.test.ts @@ -35,4 +35,23 @@ describe("createDiscordTypingLease", () => { leaseB.stop(); }); + + it("swallows background pulse failures", async () => { + vi.useFakeTimers(); + const pulse = vi + .fn<(params: { channelId: string; accountId?: string; cfg?: unknown }) => Promise>() + .mockResolvedValueOnce(undefined) + .mockRejectedValueOnce(new Error("boom")); + + const lease = await createDiscordTypingLease({ + channelId: "123", + intervalMs: 2_000, + pulse, + }); + + await expect(vi.advanceTimersByTimeAsync(2_000)).resolves.toBe(vi); + expect(pulse).toHaveBeenCalledTimes(2); + + lease.stop(); + }); }); diff --git a/src/plugins/runtime/runtime-discord-typing.ts b/src/plugins/runtime/runtime-discord-typing.ts index 17b782ff2b1..e5bed40e987 100644 --- a/src/plugins/runtime/runtime-discord-typing.ts +++ b/src/plugins/runtime/runtime-discord-typing.ts @@ -1,3 +1,5 @@ +import { logWarn } from "../../logger.js"; + export type CreateDiscordTypingLeaseParams = { channelId: string; accountId?: string; @@ -38,7 +40,10 @@ export async function createDiscordTypingLease(params: CreateDiscordTypingLeaseP await pulse(); timer = setInterval(() => { - void pulse(); + // Background lease refreshes must never escape as unhandled rejections. + void pulse().catch((err) => { + logWarn(`plugins: discord typing pulse failed: ${String(err)}`); + }); }, intervalMs); timer.unref?.(); diff --git a/src/plugins/runtime/runtime-telegram-typing.test.ts b/src/plugins/runtime/runtime-telegram-typing.test.ts index a472a47096b..aa9f9fe2667 100644 --- a/src/plugins/runtime/runtime-telegram-typing.test.ts +++ b/src/plugins/runtime/runtime-telegram-typing.test.ts @@ -35,4 +35,30 @@ describe("createTelegramTypingLease", () => { leaseB.stop(); }); + + it("swallows background pulse failures", async () => { + vi.useFakeTimers(); + const pulse = vi + .fn< + (params: { + to: string; + accountId?: string; + cfg?: unknown; + messageThreadId?: number; + }) => Promise + >() + .mockResolvedValueOnce(undefined) + .mockRejectedValueOnce(new Error("boom")); + + const lease = await createTelegramTypingLease({ + to: "telegram:123", + intervalMs: 2_000, + pulse, + }); + + await expect(vi.advanceTimersByTimeAsync(2_000)).resolves.toBe(vi); + expect(pulse).toHaveBeenCalledTimes(2); + + lease.stop(); + }); }); diff --git a/src/plugins/runtime/runtime-telegram-typing.ts b/src/plugins/runtime/runtime-telegram-typing.ts index c9434316840..e03c2f33fe5 100644 --- a/src/plugins/runtime/runtime-telegram-typing.ts +++ b/src/plugins/runtime/runtime-telegram-typing.ts @@ -1,4 +1,5 @@ import type { OpenClawConfig } from "../../config/config.js"; +import { logWarn } from "../../logger.js"; export type CreateTelegramTypingLeaseParams = { to: string; @@ -36,8 +37,12 @@ export async function createTelegramTypingLease(params: CreateTelegramTypingLeas await refresh(); const timer = setInterval(() => { - void refresh(); + // Background lease refreshes must never escape as unhandled rejections. + void refresh().catch((err) => { + logWarn(`plugins: telegram typing pulse failed: ${String(err)}`); + }); }, intervalMs); + timer.unref?.(); return { refresh,