From bb7e8624abd8a45908cd96614cb85a0a1ec088a7 Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Tue, 28 Apr 2026 08:27:06 +0100 Subject: [PATCH] fix: keep typing for group message-tool replies --- .../reply/dispatch-from-config.test.ts | 16 ++++++++++++++-- src/auto-reply/reply/dispatch-from-config.ts | 2 +- 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/src/auto-reply/reply/dispatch-from-config.test.ts b/src/auto-reply/reply/dispatch-from-config.test.ts index b50f032444a..ae108905ca3 100644 --- a/src/auto-reply/reply/dispatch-from-config.test.ts +++ b/src/auto-reply/reply/dispatch-from-config.test.ts @@ -3891,7 +3891,10 @@ describe("sendPolicy deny — suppress delivery, not processing (#53328)", () => sendPolicy: "deny", }; const dispatcher = createDispatcher(); - const replyResolver = vi.fn(async () => ({ text: "agent reply" }) satisfies ReplyPayload); + const replyResolver = vi.fn(async (_ctx: MsgContext, opts?: GetReplyOptions) => { + expect(opts?.suppressTyping).toBe(true); + return { text: "agent reply" } satisfies ReplyPayload; + }); const ctx = buildTestCtx({ SessionKey: "test:session" }); await dispatchReplyFromConfig({ @@ -4220,8 +4223,11 @@ describe("sendPolicy deny — suppress delivery, not processing (#53328)", () => itemEvent: vi.fn(), planUpdate: vi.fn(), toolResult: vi.fn(), + typingStart: vi.fn(async () => {}), }; const replyResolver = vi.fn(async (_ctx: MsgContext, opts?: GetReplyOptions) => { + expect(opts?.suppressTyping).toBe(false); + await opts?.onReplyStart?.(); await opts?.onPartialReply?.({ text: "draft leak" }); await opts?.onReasoningStream?.({ text: "reasoning leak" }); await opts?.onAssistantMessageStart?.(); @@ -4244,6 +4250,7 @@ describe("sendPolicy deny — suppress delivery, not processing (#53328)", () => onPartialReply: callbacks.partial, onReasoningStream: callbacks.reasoning, onAssistantMessageStart: callbacks.assistantStart, + onReplyStart: callbacks.typingStart, onBlockReplyQueued: callbacks.blockQueued, onToolStart: callbacks.toolStart, onItemEvent: callbacks.itemEvent, @@ -4257,7 +4264,11 @@ describe("sendPolicy deny — suppress delivery, not processing (#53328)", () => expect(dispatcher.sendFinalReply).not.toHaveBeenCalled(); expect(dispatcher.sendBlockReply).not.toHaveBeenCalled(); expect(dispatcher.sendToolResult).not.toHaveBeenCalled(); - for (const callback of Object.values(callbacks)) { + expect(callbacks.typingStart).toHaveBeenCalledTimes(1); + for (const [name, callback] of Object.entries(callbacks)) { + if (name === "typingStart") { + continue; + } expect(callback).not.toHaveBeenCalled(); } expect(hookMocks.runner.runReplyDispatch).toHaveBeenCalledWith( @@ -4275,6 +4286,7 @@ describe("sendPolicy deny — suppress delivery, not processing (#53328)", () => const dispatcher = createDispatcher(); const replyResolver = vi.fn(async (_ctx: MsgContext, opts?: GetReplyOptions) => { expect(opts?.sourceReplyDeliveryMode).toBe("message_tool_only"); + expect(opts?.suppressTyping).toBe(false); return { text: "final reply" } satisfies ReplyPayload; }); diff --git a/src/auto-reply/reply/dispatch-from-config.ts b/src/auto-reply/reply/dispatch-from-config.ts index fc2117970f1..674b7b9b57a 100644 --- a/src/auto-reply/reply/dispatch-from-config.ts +++ b/src/auto-reply/reply/dispatch-from-config.ts @@ -1054,7 +1054,7 @@ export async function dispatchReplyFromConfig( const typing = resolveRunTypingPolicy({ requestedPolicy: params.replyOptions?.typingPolicy, suppressTyping: - suppressDelivery || params.replyOptions?.suppressTyping === true || shouldSuppressTyping, + sendPolicyDenied || params.replyOptions?.suppressTyping === true || shouldSuppressTyping, originatingChannel: routeReplyChannel, systemEvent: shouldRouteToOriginating, });