diff --git a/src/auto-reply/reply/dispatch-from-config.test.ts b/src/auto-reply/reply/dispatch-from-config.test.ts index c95fd632f38..665cb2f14fd 100644 --- a/src/auto-reply/reply/dispatch-from-config.test.ts +++ b/src/auto-reply/reply/dispatch-from-config.test.ts @@ -4335,6 +4335,35 @@ describe("sendPolicy deny — suppress delivery, not processing (#53328)", () => expect(dispatcher.sendFinalReply).not.toHaveBeenCalled(); }); + it("keeps native command replies visible in group/channel turns", async () => { + setNoAbort(); + const dispatcher = createDispatcher(); + const replyResolver = vi.fn(async (_ctx: MsgContext, opts?: GetReplyOptions) => { + expect(opts?.sourceReplyDeliveryMode).toBe("automatic"); + expect(opts?.suppressTyping).toBe(false); + return { text: "status reply" } satisfies ReplyPayload; + }); + + const result = await dispatchReplyFromConfig({ + ctx: buildTestCtx({ + ChatType: "group", + CommandSource: "native", + CommandAuthorized: true, + WasMentioned: true, + SessionKey: "test:telegram:group:G1", + }), + cfg: emptyConfig, + dispatcher, + replyResolver, + }); + + expect(replyResolver).toHaveBeenCalledTimes(1); + expect(result.queuedFinal).toBe(true); + expect(dispatcher.sendFinalReply).toHaveBeenCalledWith( + expect.objectContaining({ text: "status reply" }), + ); + }); + it("allows config to keep group/channel source delivery automatic", async () => { setNoAbort(); const dispatcher = createDispatcher(); diff --git a/src/auto-reply/reply/source-reply-delivery-mode.test.ts b/src/auto-reply/reply/source-reply-delivery-mode.test.ts index b23b9c33bed..abcb1da2305 100644 --- a/src/auto-reply/reply/source-reply-delivery-mode.test.ts +++ b/src/auto-reply/reply/source-reply-delivery-mode.test.ts @@ -42,6 +42,15 @@ describe("resolveSourceReplyDeliveryMode", () => { }), ).toBe("automatic"); }); + + it("treats native commands as explicit replies in groups", () => { + expect( + resolveSourceReplyDeliveryMode({ + cfg: emptyConfig, + ctx: { ChatType: "group", CommandSource: "native" }, + }), + ).toBe("automatic"); + }); }); describe("resolveSourceReplyVisibilityPolicy", () => { @@ -83,6 +92,22 @@ describe("resolveSourceReplyVisibilityPolicy", () => { }); }); + it("keeps native command replies visible in groups", () => { + expect( + resolveSourceReplyVisibilityPolicy({ + cfg: emptyConfig, + ctx: { ChatType: "group", CommandSource: "native" }, + sendPolicy: "allow", + }), + ).toMatchObject({ + sourceReplyDeliveryMode: "automatic", + suppressAutomaticSourceDelivery: false, + suppressDelivery: false, + suppressHookReplyLifecycle: false, + suppressTyping: false, + }); + }); + it("keeps configured automatic group delivery visible", () => { expect( resolveSourceReplyVisibilityPolicy({ diff --git a/src/auto-reply/reply/source-reply-delivery-mode.ts b/src/auto-reply/reply/source-reply-delivery-mode.ts index d7b35c7d1b7..44c4c8b2e51 100644 --- a/src/auto-reply/reply/source-reply-delivery-mode.ts +++ b/src/auto-reply/reply/source-reply-delivery-mode.ts @@ -5,6 +5,7 @@ import type { SourceReplyDeliveryMode } from "../get-reply-options.types.js"; export type SourceReplyDeliveryModeContext = { ChatType?: string; + CommandSource?: "text" | "native"; }; export function resolveSourceReplyDeliveryMode(params: { @@ -15,6 +16,9 @@ export function resolveSourceReplyDeliveryMode(params: { if (params.requested) { return params.requested; } + if (params.ctx.CommandSource === "native") { + return "automatic"; + } const chatType = normalizeChatType(params.ctx.ChatType); if (chatType === "group" || chatType === "channel") { return params.cfg.messages?.groupChat?.visibleReplies === "automatic"