From da95b58a2a277e39ac0014a0978f165c161d8cd9 Mon Sep 17 00:00:00 2001 From: Shadow Date: Mon, 12 Jan 2026 22:20:29 -0600 Subject: [PATCH] Typing: keep indicators active during tool runs Closes #450 Closes #447 --- CHANGELOG.md | 1 + src/auto-reply/reply/agent-runner.ts | 2 +- src/auto-reply/reply/typing-mode.test.ts | 6 +++--- src/auto-reply/reply/typing-mode.ts | 6 +++++- 4 files changed, 10 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a31fd6193..fcde2b446 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ - Memory: allow custom OpenAI-compatible embedding endpoints for memory search (remote baseUrl/apiKey/headers). (#819 — thanks @mukhtharcm) ### Fixes +- Typing: keep typing indicators alive during tool execution. (#450, #447 — thanks @thewilloftheshadow) - Cron: coerce enabled patches so disabling jobs persists correctly. (#205 — thanks @thewilloftheshadow) - Control UI: keep chat scroll position unless user is near the bottom. (#217 — thanks @thewilloftheshadow) - Fallback: treat credential validation failures ("no credentials found", "no API key found") as auth errors that trigger model fallback. (#822 — thanks @sebslight) diff --git a/src/auto-reply/reply/agent-runner.ts b/src/auto-reply/reply/agent-runner.ts index c06c754b9..ac6f9790e 100644 --- a/src/auto-reply/reply/agent-runner.ts +++ b/src/auto-reply/reply/agent-runner.ts @@ -704,7 +704,7 @@ export async function runReplyAgent(params: { if (evt.stream === "tool") { const phase = typeof evt.data.phase === "string" ? evt.data.phase : ""; - if (phase === "start") { + if (phase === "start" || phase === "update") { void typingSignals.signalToolStart(); } } diff --git a/src/auto-reply/reply/typing-mode.test.ts b/src/auto-reply/reply/typing-mode.test.ts index 697b97af5..3b18c47b5 100644 --- a/src/auto-reply/reply/typing-mode.test.ts +++ b/src/auto-reply/reply/typing-mode.test.ts @@ -123,7 +123,7 @@ describe("createTypingSignaler", () => { expect(typing.startTypingOnText).not.toHaveBeenCalled(); }); - it("does not start typing on tool start when inactive", async () => { + it("starts typing on tool start when inactive", async () => { const typing = createMockTypingController(); const signaler = createTypingSignaler({ typing, @@ -133,8 +133,8 @@ describe("createTypingSignaler", () => { await signaler.signalToolStart(); - expect(typing.refreshTypingTtl).not.toHaveBeenCalled(); - expect(typing.startTypingLoop).not.toHaveBeenCalled(); + expect(typing.startTypingLoop).toHaveBeenCalled(); + expect(typing.refreshTypingTtl).toHaveBeenCalled(); }); it("refreshes ttl on tool start when active", async () => { diff --git a/src/auto-reply/reply/typing-mode.ts b/src/auto-reply/reply/typing-mode.ts index a445b6e2e..9e2a6b36b 100644 --- a/src/auto-reply/reply/typing-mode.ts +++ b/src/auto-reply/reply/typing-mode.ts @@ -68,7 +68,11 @@ export function createTypingSignaler(params: { const signalToolStart = async () => { if (disabled) return; - if (!typing.isActive()) return; + if (!typing.isActive()) { + await typing.startTypingLoop(); + typing.refreshTypingTtl(); + return; + } // Keep typing indicator alive during tool execution without changing mode semantics. typing.refreshTypingTtl(); };