From 79176cc4e50ebe537bd7d7e9aa210fb5a00084e9 Mon Sep 17 00:00:00 2001 From: SidQin-cyber Date: Thu, 26 Feb 2026 21:04:31 +0800 Subject: [PATCH] fix(typing): force cleanup when dispatch idle is never received Add a grace timer after markRunComplete so the typing controller cleans up even when markDispatchIdle is never called, preventing indefinite typing keepalive loops in cron and announce flows. Made-with: Cursor (cherry picked from commit 684eaf2893542d648daa1ca0b0c1a32c264bb8bd) --- src/auto-reply/reply/typing.ts | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/src/auto-reply/reply/typing.ts b/src/auto-reply/reply/typing.ts index 82e3d762240..43174024606 100644 --- a/src/auto-reply/reply/typing.ts +++ b/src/auto-reply/reply/typing.ts @@ -61,6 +61,10 @@ export function createTypingController(params: { clearTimeout(typingTtlTimer); typingTtlTimer = undefined; } + if (dispatchIdleTimer) { + clearTimeout(dispatchIdleTimer); + dispatchIdleTimer = undefined; + } typingLoop.stop(); // Notify the channel to stop its typing indicator (e.g., on NO_REPLY). // This fires only once (sealed prevents re-entry). @@ -177,13 +181,28 @@ export function createTypingController(params: { await startTypingLoop(); }; + let dispatchIdleTimer: NodeJS.Timeout | undefined; + const DISPATCH_IDLE_GRACE_MS = 10_000; + const markRunComplete = () => { runComplete = true; maybeStopOnIdle(); + if (!sealed && !dispatchIdle) { + dispatchIdleTimer = setTimeout(() => { + if (!sealed && !dispatchIdle) { + log?.("typing: dispatch idle not received after run complete; forcing cleanup"); + cleanup(); + } + }, DISPATCH_IDLE_GRACE_MS); + } }; const markDispatchIdle = () => { dispatchIdle = true; + if (dispatchIdleTimer) { + clearTimeout(dispatchIdleTimer); + dispatchIdleTimer = undefined; + } maybeStopOnIdle(); };