fix(typing): keep tool-start ttl mode-safe (#452, thanks @thesash)
This commit is contained in:
@@ -19,6 +19,7 @@
|
||||
- CLI: remove `update`, `gateway-daemon`, `gateway {install|uninstall|start|stop|restart|daemon status|wake|send|agent}`, and `telegram` commands; use `daemon` for service control, `send`/`agent`/`wake` for RPC, and `nodes canvas` for canvas ops.
|
||||
|
||||
### Fixes
|
||||
- Auto-reply: keep typing indicators alive during tool execution without changing typing-mode semantics. Thanks @thesash for PR #452.
|
||||
- macOS: harden Voice Wake tester/runtime (pause trigger, mic persistence, local-only tester) and keep transcript logs private. Thanks @xadenryan for PR #438.
|
||||
- macOS: preserve node bridge tunnel port override so remote nodes connect on the bridge port. Thanks @sircrumpet for PR #364.
|
||||
- Doctor/Daemon: surface gateway runtime state + port collision diagnostics; warn on legacy workspace dirs.
|
||||
|
||||
@@ -2,14 +2,18 @@ import { vi } from "vitest";
|
||||
|
||||
import type { TypingController } from "./typing.js";
|
||||
|
||||
export function createMockTypingController(): TypingController {
|
||||
export function createMockTypingController(
|
||||
overrides: Partial<TypingController> = {},
|
||||
): TypingController {
|
||||
return {
|
||||
onReplyStart: vi.fn(async () => {}),
|
||||
startTypingLoop: vi.fn(async () => {}),
|
||||
startTypingOnText: vi.fn(async () => {}),
|
||||
refreshTypingTtl: vi.fn(),
|
||||
isActive: vi.fn(() => false),
|
||||
markRunComplete: vi.fn(),
|
||||
markDispatchIdle: vi.fn(),
|
||||
cleanup: vi.fn(),
|
||||
...overrides,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { describe, expect, it, vi } from "vitest";
|
||||
|
||||
import { createMockTypingController } from "./test-helpers.js";
|
||||
import { createTypingSignaler, resolveTypingMode } from "./typing-mode.js";
|
||||
@@ -123,6 +123,36 @@ describe("createTypingSignaler", () => {
|
||||
expect(typing.startTypingOnText).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("does not start typing on tool start when inactive", async () => {
|
||||
const typing = createMockTypingController();
|
||||
const signaler = createTypingSignaler({
|
||||
typing,
|
||||
mode: "message",
|
||||
isHeartbeat: false,
|
||||
});
|
||||
|
||||
await signaler.signalToolStart();
|
||||
|
||||
expect(typing.refreshTypingTtl).not.toHaveBeenCalled();
|
||||
expect(typing.startTypingLoop).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("refreshes ttl on tool start when active", async () => {
|
||||
const typing = createMockTypingController({
|
||||
isActive: vi.fn(() => true),
|
||||
});
|
||||
const signaler = createTypingSignaler({
|
||||
typing,
|
||||
mode: "message",
|
||||
isHeartbeat: false,
|
||||
});
|
||||
|
||||
await signaler.signalToolStart();
|
||||
|
||||
expect(typing.refreshTypingTtl).toHaveBeenCalled();
|
||||
expect(typing.startTypingLoop).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("suppresses typing when disabled", async () => {
|
||||
const typing = createMockTypingController();
|
||||
const signaler = createTypingSignaler({
|
||||
|
||||
@@ -68,8 +68,9 @@ export function createTypingSignaler(params: {
|
||||
|
||||
const signalToolStart = async () => {
|
||||
if (disabled) return;
|
||||
// Keep typing indicator alive during tool execution
|
||||
await typing.startTypingLoop();
|
||||
if (!typing.isActive()) return;
|
||||
// Keep typing indicator alive during tool execution without changing mode semantics.
|
||||
typing.refreshTypingTtl();
|
||||
};
|
||||
|
||||
return {
|
||||
|
||||
@@ -3,6 +3,7 @@ export type TypingController = {
|
||||
startTypingLoop: () => Promise<void>;
|
||||
startTypingOnText: (text?: string) => Promise<void>;
|
||||
refreshTypingTtl: () => void;
|
||||
isActive: () => boolean;
|
||||
markRunComplete: () => void;
|
||||
markDispatchIdle: () => void;
|
||||
cleanup: () => void;
|
||||
@@ -76,6 +77,8 @@ export function createTypingController(params: {
|
||||
}, typingTtlMs);
|
||||
};
|
||||
|
||||
const isActive = () => active && !sealed;
|
||||
|
||||
const triggerTyping = async () => {
|
||||
if (sealed) return;
|
||||
await onReplyStart?.();
|
||||
@@ -138,6 +141,7 @@ export function createTypingController(params: {
|
||||
startTypingLoop,
|
||||
startTypingOnText,
|
||||
refreshTypingTtl,
|
||||
isActive,
|
||||
markRunComplete,
|
||||
markDispatchIdle,
|
||||
cleanup,
|
||||
|
||||
@@ -330,6 +330,7 @@ describe("typing controller idle", () => {
|
||||
startTypingLoop: vi.fn(async () => {}),
|
||||
startTypingOnText: vi.fn(async () => {}),
|
||||
refreshTypingTtl: vi.fn(),
|
||||
isActive: vi.fn(() => false),
|
||||
markRunComplete: vi.fn(),
|
||||
markDispatchIdle,
|
||||
cleanup: vi.fn(),
|
||||
|
||||
Reference in New Issue
Block a user