diff --git a/CHANGELOG.md b/CHANGELOG.md index 740aeb67863..b7fbb0a1fca 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -56,6 +56,7 @@ Docs: https://docs.openclaw.ai - Hooks/doctor: warn when `hooks.transformsDir` points outside the canonical hooks transform directory, so invalid workspace skill paths get a direct recovery hint before the Gateway crash-loops. Fixes #75853. Thanks @midobk. - Proxy/audio: convert standard `FormData` bodies before proxy-backed undici fetches, so audio transcription and multipart uploads no longer send `[object FormData]` when `HTTP_PROXY` or `HTTPS_PROXY` is configured. Fixes #48554. Thanks @dco5. - Discord: allow explicitly configured ack reactions in tool-only guild channels while keeping automatic lifecycle/status reactions suppressed. Fixes #74922. Thanks @samvilian and @BlueBirdBack. +- Discord: keep typing indicators alive during long tool runs and auto-compaction while keepalive ticks continue, so active sessions do not appear stalled before the final reply. Thanks @Squirbie. - Discord: preserve multipart Content-Type headers for attachment uploads across REST fetch paths, so generated images and other media no longer fail delivery with `CONTENT_TYPE_INVALID`. Thanks @FunJim. - Discord: preserve attachment and sticker filenames when saving inbound media, so agents can see human-readable file names instead of only UUID-based paths. Fixes #59744. Thanks @xela92 and @rockcent. - Discord: preserve non-ASCII channel names in session display labels while keeping allowlist matching on the existing ASCII slug contract. Thanks @swjeong9. diff --git a/src/auto-reply/reply/typing-persistence.test.ts b/src/auto-reply/reply/typing-persistence.test.ts index 9380bf1900e..784bceb4e99 100644 --- a/src/auto-reply/reply/typing-persistence.test.ts +++ b/src/auto-reply/reply/typing-persistence.test.ts @@ -40,6 +40,29 @@ describe("typing persistence bug fix", () => { expect(onReplyStartSpy).not.toHaveBeenCalledTimes(2); }); + it("keeps typing alive while keepalive ticks continue during long runs", async () => { + const longRunCleanupSpy = vi.fn(); + const longRunController = createTypingController({ + onReplyStart: onReplyStartSpy, + onCleanup: longRunCleanupSpy, + typingIntervalSeconds: 6, + typingTtlMs: 10_000, + log: vi.fn(), + }); + + await longRunController.startTypingLoop(); + expect(onReplyStartSpy).toHaveBeenCalledTimes(1); + + await vi.advanceTimersByTimeAsync(6000); + expect(onReplyStartSpy).toHaveBeenCalledTimes(2); + + await vi.advanceTimersByTimeAsync(5000); + expect(longRunCleanupSpy).not.toHaveBeenCalled(); + + longRunController.cleanup(); + expect(longRunCleanupSpy).toHaveBeenCalledTimes(1); + }); + it("should stop typing when both runComplete and dispatchIdle are true", async () => { // Start typing await controller.startTypingLoop(); diff --git a/src/auto-reply/reply/typing.ts b/src/auto-reply/reply/typing.ts index f5f3aed13b6..8b135a0ed3e 100644 --- a/src/auto-reply/reply/typing.ts +++ b/src/auto-reply/reply/typing.ts @@ -128,6 +128,7 @@ export function createTypingController(params: { try { await startGuard.run(async () => { await onReplyStart?.(); + refreshTypingTtl(); }); } catch (err) { log?.(`typing start failed: ${String(err)}`);