diff --git a/src/auto-reply/reply/dispatch-from-config.test.ts b/src/auto-reply/reply/dispatch-from-config.test.ts index a63125e3270..4a3eee88d85 100644 --- a/src/auto-reply/reply/dispatch-from-config.test.ts +++ b/src/auto-reply/reply/dispatch-from-config.test.ts @@ -3099,7 +3099,7 @@ describe("dispatchReplyFromConfig", () => { }); }); - it("keeps diagnostic progress when source progress callbacks are suppressed", async () => { + it("forwards non-answer progress callbacks when source replies are suppressed", async () => { setNoAbort(); const cfg = { diagnostics: { enabled: true } } as OpenClawConfig; const dispatcher = createDispatcher(); @@ -3138,9 +3138,9 @@ describe("dispatchReplyFromConfig", () => { replyResolver, }); - expect(callbacks.toolStart).not.toHaveBeenCalled(); - expect(callbacks.itemEvent).not.toHaveBeenCalled(); - expect(callbacks.commandOutput).not.toHaveBeenCalled(); + expect(callbacks.toolStart).toHaveBeenCalledTimes(1); + expect(callbacks.itemEvent).toHaveBeenCalledTimes(1); + expect(callbacks.commandOutput).toHaveBeenCalledTimes(1); expect(diagnosticMocks.markDiagnosticSessionProgress).toHaveBeenCalledTimes(3); expect(diagnosticMocks.markDiagnosticSessionProgress).toHaveBeenCalledWith({ sessionKey: "agent:main:discord:channel:C1", @@ -4567,7 +4567,11 @@ describe("sendPolicy deny — suppress delivery, not processing (#53328)", () => if (name === "typingStart") { continue; } - expect(callback).not.toHaveBeenCalled(); + if (name === "toolStart" || name === "itemEvent" || name === "planUpdate") { + expect(callback).toHaveBeenCalledTimes(1); + } else { + expect(callback).not.toHaveBeenCalled(); + } } expect(hookMocks.runner.runReplyDispatch).toHaveBeenCalledWith( expect.objectContaining({ diff --git a/src/auto-reply/reply/dispatch-from-config.ts b/src/auto-reply/reply/dispatch-from-config.ts index 45ac7e35d47..398dd8e0aed 100644 --- a/src/auto-reply/reply/dispatch-from-config.ts +++ b/src/auto-reply/reply/dispatch-from-config.ts @@ -1243,15 +1243,19 @@ export async function dispatchReplyFromConfig( const onPlanUpdateFromReplyOptions = params.replyOptions?.onPlanUpdate; const onApprovalEventFromReplyOptions = params.replyOptions?.onApprovalEvent; const onPatchSummaryFromReplyOptions = params.replyOptions?.onPatchSummary; + const shouldForwardProgressCallback = (options?: { + forwardWhenSourceDeliverySuppressed?: boolean; + }) => !suppressAutomaticSourceDelivery || options?.forwardWhenSourceDeliverySuppressed === true; const wrapProgressCallback = ( callback: ((...args: Args) => Promise | void) | undefined, + options?: { forwardWhenSourceDeliverySuppressed?: boolean }, ): ((...args: Args) => Promise) | undefined => { if (!callback && (!suppressAutomaticSourceDelivery || !canTrackSession)) { return undefined; } return async (...args: Args) => { markProgress(); - if (!suppressAutomaticSourceDelivery) { + if (shouldForwardProgressCallback(options)) { await callback?.(...args); } }; @@ -1274,11 +1278,21 @@ export async function dispatchReplyFromConfig( onReasoningEnd: wrapProgressCallback(params.replyOptions?.onReasoningEnd), onAssistantMessageStart: wrapProgressCallback(params.replyOptions?.onAssistantMessageStart), onBlockReplyQueued: wrapProgressCallback(params.replyOptions?.onBlockReplyQueued), - onToolStart: wrapProgressCallback(params.replyOptions?.onToolStart), - onItemEvent: wrapProgressCallback(params.replyOptions?.onItemEvent), - onCommandOutput: wrapProgressCallback(params.replyOptions?.onCommandOutput), - onCompactionStart: wrapProgressCallback(params.replyOptions?.onCompactionStart), - onCompactionEnd: wrapProgressCallback(params.replyOptions?.onCompactionEnd), + onToolStart: wrapProgressCallback(params.replyOptions?.onToolStart, { + forwardWhenSourceDeliverySuppressed: true, + }), + onItemEvent: wrapProgressCallback(params.replyOptions?.onItemEvent, { + forwardWhenSourceDeliverySuppressed: true, + }), + onCommandOutput: wrapProgressCallback(params.replyOptions?.onCommandOutput, { + forwardWhenSourceDeliverySuppressed: true, + }), + onCompactionStart: wrapProgressCallback(params.replyOptions?.onCompactionStart, { + forwardWhenSourceDeliverySuppressed: true, + }), + onCompactionEnd: wrapProgressCallback(params.replyOptions?.onCompactionEnd, { + forwardWhenSourceDeliverySuppressed: true, + }), onToolResult: (payload: ReplyPayload) => { markProgress(); const run = async () => { @@ -1330,7 +1344,7 @@ export async function dispatchReplyFromConfig( onPlanUpdate: async (payload) => { markProgress(); markInboundDedupeReplayUnsafe(); - if (!suppressAutomaticSourceDelivery) { + if (shouldForwardProgressCallback({ forwardWhenSourceDeliverySuppressed: true })) { await onPlanUpdateFromReplyOptions?.(payload); } if (payload.phase !== "update" || shouldSuppressDefaultToolProgressMessages()) { @@ -1341,7 +1355,7 @@ export async function dispatchReplyFromConfig( onApprovalEvent: async (payload) => { markProgress(); markInboundDedupeReplayUnsafe(); - if (!suppressAutomaticSourceDelivery) { + if (shouldForwardProgressCallback({ forwardWhenSourceDeliverySuppressed: true })) { await onApprovalEventFromReplyOptions?.(payload); } if (payload.phase !== "requested" || shouldSuppressDefaultToolProgressMessages()) { @@ -1360,7 +1374,7 @@ export async function dispatchReplyFromConfig( onPatchSummary: async (payload) => { markProgress(); markInboundDedupeReplayUnsafe(); - if (!suppressAutomaticSourceDelivery) { + if (shouldForwardProgressCallback({ forwardWhenSourceDeliverySuppressed: true })) { await onPatchSummaryFromReplyOptions?.(payload); } if (payload.phase !== "end" || shouldSuppressDefaultToolProgressMessages()) {