diff --git a/src/auto-reply/reply/dispatch-acp.test.ts b/src/auto-reply/reply/dispatch-acp.test.ts index 57d18cb8693..708d6a894f5 100644 --- a/src/auto-reply/reply/dispatch-acp.test.ts +++ b/src/auto-reply/reply/dispatch-acp.test.ts @@ -471,6 +471,31 @@ describe("tryDispatchAcpReply", () => { expect(dispatcher.sendBlockReply).not.toHaveBeenCalled(); }); + it("keeps same-provider tool-only ACP final replies private when an origin route exists", async () => { + setReadyAcpResolution(); + mockVisibleTextTurn("hidden final"); + const onReplyStart = vi.fn(); + const { dispatcher } = createDispatcher(); + + const result = await runDispatch({ + bodyForAgent: "reply via message tool if needed", + dispatcher, + onReplyStart, + suppressUserDelivery: true, + suppressReplyLifecycle: false, + sourceReplyDeliveryMode: "message_tool_only", + shouldRouteToOriginating: true, + originatingChannel: "discord", + originatingTo: "channel:C1", + }); + + expect(result?.queuedFinal).toBe(false); + expect(onReplyStart).toHaveBeenCalledTimes(1); + expect(routeMocks.routeReply).not.toHaveBeenCalled(); + expect(dispatcher.sendFinalReply).not.toHaveBeenCalled(); + expect(dispatcher.sendBlockReply).not.toHaveBeenCalled(); + }); + it("edits ACP tool lifecycle updates in place when supported", async () => { setReadyAcpResolution(); mockToolLifecycleTurn("call-1"); diff --git a/src/auto-reply/reply/dispatch-from-config.test.ts b/src/auto-reply/reply/dispatch-from-config.test.ts index d47d22a170e..55a97bd7955 100644 --- a/src/auto-reply/reply/dispatch-from-config.test.ts +++ b/src/auto-reply/reply/dispatch-from-config.test.ts @@ -4502,6 +4502,35 @@ describe("sendPolicy deny — suppress delivery, not processing (#53328)", () => expect(dispatcher.sendFinalReply).not.toHaveBeenCalled(); }); + it("does not auto-route same-provider group/channel final replies in message-tool-only mode", async () => { + setNoAbort(); + mocks.routeReply.mockClear(); + const dispatcher = createDispatcher(); + const replyResolver = vi.fn(async (_ctx: MsgContext, opts?: GetReplyOptions) => { + expect(opts?.sourceReplyDeliveryMode).toBe("message_tool_only"); + return { text: "final reply" } satisfies ReplyPayload; + }); + + const result = await dispatchReplyFromConfig({ + ctx: buildTestCtx({ + ChatType: "channel", + Provider: "discord", + Surface: "discord", + OriginatingChannel: "discord", + OriginatingTo: "channel:C1", + SessionKey: "test:discord:channel:C1", + }), + cfg: emptyConfig, + dispatcher, + replyResolver, + }); + + expect(replyResolver).toHaveBeenCalledTimes(1); + expect(result.queuedFinal).toBe(false); + expect(dispatcher.sendFinalReply).not.toHaveBeenCalled(); + expect(mocks.routeReply).not.toHaveBeenCalled(); + }); + it("uses harness defaults for direct source delivery when config is unset", async () => { setNoAbort(); registerAgentHarness({