From dbccddbf343cff642c6afd9e0fffd27bbe5f6e7d Mon Sep 17 00:00:00 2001 From: Ayaan Zaidi Date: Wed, 1 Jul 2026 22:21:10 -0700 Subject: [PATCH] fix(auto-reply): suppress room-event fast progress dispatch --- .../reply/dispatch-from-config.test.ts | 38 +++++++++++++++++++ src/auto-reply/reply/dispatch-from-config.ts | 13 +++++-- 2 files changed, 48 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 0ba3482f39ee..d14cc4b29ad1 100644 --- a/src/auto-reply/reply/dispatch-from-config.test.ts +++ b/src/auto-reply/reply/dispatch-from-config.test.ts @@ -9904,6 +9904,44 @@ describe("sendPolicy deny — suppress delivery, not processing (#53328)", () => expect(dispatcher.sendFinalReply).not.toHaveBeenCalled(); }); + it("suppresses fast auto progress for room-event message-tool-only turns", async () => { + setNoAbort(); + sessionStoreMocks.currentEntry = { + sessionId: "s1", + updatedAt: 0, + sendPolicy: "allow", + }; + const dispatcher = createDispatcher(); + const replyResolver = vi.fn(async (_ctx: MsgContext, opts?: GetReplyOptions) => { + await opts?.onToolResult?.({ + text: "💨Fast: auto-off(75s>=60s)", + channelData: { openclawProgressKind: "fast-mode-auto" }, + }); + return { text: "NO_REPLY" } satisfies ReplyPayload; + }); + const ctx = buildTestCtx({ + SessionKey: "test:session", + ChatType: "channel", + InboundEventKind: "room_event", + }); + + const result = await dispatchReplyFromConfig({ + ctx, + cfg: emptyConfig, + dispatcher, + replyResolver, + replyOptions: { + sourceReplyDeliveryMode: "message_tool_only", + suppressDefaultToolProgressMessages: true, + }, + }); + + expect(result.queuedFinal).toBe(false); + expect(result.sourceReplyDeliveryMode).toBe("message_tool_only"); + expect(dispatcher.sendToolResult).not.toHaveBeenCalled(); + expect(dispatcher.sendFinalReply).not.toHaveBeenCalled(); + }); + it("suppresses fast auto progress when sendPolicy is deny", async () => { setNoAbort(); sessionStoreMocks.currentEntry = { diff --git a/src/auto-reply/reply/dispatch-from-config.ts b/src/auto-reply/reply/dispatch-from-config.ts index 96a2ee41d7ea..8ca482085060 100644 --- a/src/auto-reply/reply/dispatch-from-config.ts +++ b/src/auto-reply/reply/dispatch-from-config.ts @@ -2383,6 +2383,11 @@ export async function dispatchReplyFromConfig( ctx.InboundEventKind !== "room_event" && !sendPolicyDenied && params.replyOptions?.forceToolResultProgress === true; + const shouldDeliverFastModeAutoProgressDespiteSourceSuppression = () => + suppressAutomaticSourceDelivery && + sourceReplyDeliveryMode === "message_tool_only" && + ctx.InboundEventKind !== "room_event" && + !sendPolicyDenied; let finalReplyDeliveryStarted = false; const hasExecApprovalPayload = (payload: ReplyPayload) => { const execApproval = @@ -3074,8 +3079,7 @@ export async function dispatchReplyFromConfig( suppressTyping: typing.suppressTyping, onPartialReply: wrapProgressCallback(params.replyOptions?.onPartialReply), onReasoningStream: wrapProgressCallback(params.replyOptions?.onReasoningStream), - streamReasoningInNonStreamModes: - params.replyOptions?.streamReasoningInNonStreamModes, + streamReasoningInNonStreamModes: params.replyOptions?.streamReasoningInNonStreamModes, onReasoningEnd: wrapProgressCallback(params.replyOptions?.onReasoningEnd), onAssistantMessageStart: wrapProgressCallback( params.replyOptions?.onAssistantMessageStart, @@ -3140,6 +3144,9 @@ export async function dispatchReplyFromConfig( return; } const isFastModeAutoProgress = isFastModeAutoProgressPayload(payload); + const isFastModeAutoProgressDelivery = + isFastModeAutoProgress && + shouldDeliverFastModeAutoProgressDespiteSourceSuppression(); const isForcedToolProgress = shouldDeliverForcedToolProgressDespiteSourceSuppression(); const progressCallbackForwarded = shouldForwardToolResultProgressCallback( @@ -3164,7 +3171,7 @@ export async function dispatchReplyFromConfig( } if ( shouldSuppressProgressDelivery() && - !isFastModeAutoProgress && + !isFastModeAutoProgressDelivery && !isForcedToolProgress ) { return;