Typing: keep indicators active during tool runs

Closes #450
Closes #447
This commit is contained in:
Shadow
2026-01-12 22:20:29 -06:00
parent e15d5d0533
commit da95b58a2a
4 changed files with 10 additions and 5 deletions

View File

@@ -7,6 +7,7 @@
- Memory: allow custom OpenAI-compatible embedding endpoints for memory search (remote baseUrl/apiKey/headers). (#819 — thanks @mukhtharcm) - Memory: allow custom OpenAI-compatible embedding endpoints for memory search (remote baseUrl/apiKey/headers). (#819 — thanks @mukhtharcm)
### Fixes ### 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) - 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) - 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) - Fallback: treat credential validation failures ("no credentials found", "no API key found") as auth errors that trigger model fallback. (#822 — thanks @sebslight)

View File

@@ -704,7 +704,7 @@ export async function runReplyAgent(params: {
if (evt.stream === "tool") { if (evt.stream === "tool") {
const phase = const phase =
typeof evt.data.phase === "string" ? evt.data.phase : ""; typeof evt.data.phase === "string" ? evt.data.phase : "";
if (phase === "start") { if (phase === "start" || phase === "update") {
void typingSignals.signalToolStart(); void typingSignals.signalToolStart();
} }
} }

View File

@@ -123,7 +123,7 @@ describe("createTypingSignaler", () => {
expect(typing.startTypingOnText).not.toHaveBeenCalled(); 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 typing = createMockTypingController();
const signaler = createTypingSignaler({ const signaler = createTypingSignaler({
typing, typing,
@@ -133,8 +133,8 @@ describe("createTypingSignaler", () => {
await signaler.signalToolStart(); await signaler.signalToolStart();
expect(typing.refreshTypingTtl).not.toHaveBeenCalled(); expect(typing.startTypingLoop).toHaveBeenCalled();
expect(typing.startTypingLoop).not.toHaveBeenCalled(); expect(typing.refreshTypingTtl).toHaveBeenCalled();
}); });
it("refreshes ttl on tool start when active", async () => { it("refreshes ttl on tool start when active", async () => {

View File

@@ -68,7 +68,11 @@ export function createTypingSignaler(params: {
const signalToolStart = async () => { const signalToolStart = async () => {
if (disabled) return; 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. // Keep typing indicator alive during tool execution without changing mode semantics.
typing.refreshTypingTtl(); typing.refreshTypingTtl();
}; };