From e0e37af470d7ac92baed6fbf0ec73cb1a5e9c5b9 Mon Sep 17 00:00:00 2001 From: Ayaan Zaidi Date: Thu, 29 Jan 2026 09:50:13 +0530 Subject: [PATCH] fix: gate tool summaries to direct chats (#2673) (thanks @Lukavyi) --- CHANGELOG.md | 1 + .../reply/dispatch-from-config.test.ts | 37 +++++++++++++--- src/auto-reply/reply/dispatch-from-config.ts | 44 ++++++++++--------- 3 files changed, 55 insertions(+), 27 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index bb52cc8e6..f16e0d679 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -73,6 +73,7 @@ Status: beta. - **BREAKING:** Gateway auth mode "none" is removed; gateway now requires token/password (Tailscale Serve identity still allowed). ### Fixes +- Telegram: restore verbose tool summaries for direct messages only. (#2673) Thanks @Lukavyi. - Discord: restore username directory lookup in target resolution. (#3131) Thanks @bonald. - Agents: align MiniMax base URL test expectation with default provider config. (#3131) Thanks @bonald. - Agents: prevent retries on oversized image errors and surface size limits. (#2871) Thanks @Suksham-sharma. diff --git a/src/auto-reply/reply/dispatch-from-config.test.ts b/src/auto-reply/reply/dispatch-from-config.test.ts index 2604038ec..336b7d7b2 100644 --- a/src/auto-reply/reply/dispatch-from-config.test.ts +++ b/src/auto-reply/reply/dispatch-from-config.test.ts @@ -154,7 +154,7 @@ describe("dispatchReplyFromConfig", () => { const replyResolver = async ( _ctx: MsgContext, opts: GetReplyOptions | undefined, - _cfg: ClawdbotConfig, + _cfg: MoltbotConfig, ) => { expect(opts?.onToolResult).toBeDefined(); expect(typeof opts?.onToolResult).toBe("function"); @@ -170,7 +170,7 @@ describe("dispatchReplyFromConfig", () => { handled: false, aborted: false, }); - const cfg = {} as ClawdbotConfig; + const cfg = {} as MoltbotConfig; const dispatcher = createDispatcher(); const ctx = buildTestCtx({ Provider: "telegram", @@ -190,12 +190,37 @@ describe("dispatchReplyFromConfig", () => { expect(dispatcher.sendFinalReply).toHaveBeenCalledTimes(1); }); + it("does not provide onToolResult in channel sessions", async () => { + mocks.tryFastAbortFromMessage.mockResolvedValue({ + handled: false, + aborted: false, + }); + const cfg = {} as MoltbotConfig; + const dispatcher = createDispatcher(); + const ctx = buildTestCtx({ + Provider: "slack", + ChatType: "channel", + }); + + const replyResolver = async ( + _ctx: MsgContext, + opts: GetReplyOptions | undefined, + _cfg: MoltbotConfig, + ) => { + expect(opts?.onToolResult).toBeUndefined(); + return { text: "hi" } satisfies ReplyPayload; + }; + + await dispatchReplyFromConfig({ ctx, cfg, dispatcher, replyResolver }); + expect(dispatcher.sendFinalReply).toHaveBeenCalledTimes(1); + }); + it("sends tool results via dispatcher in DM sessions", async () => { mocks.tryFastAbortFromMessage.mockResolvedValue({ handled: false, aborted: false, }); - const cfg = {} as ClawdbotConfig; + const cfg = {} as MoltbotConfig; const dispatcher = createDispatcher(); const ctx = buildTestCtx({ Provider: "telegram", @@ -205,7 +230,7 @@ describe("dispatchReplyFromConfig", () => { const replyResolver = async ( _ctx: MsgContext, opts: GetReplyOptions | undefined, - _cfg: ClawdbotConfig, + _cfg: MoltbotConfig, ) => { // Simulate tool result emission await opts?.onToolResult?.({ text: "🔧 exec: ls" }); @@ -224,7 +249,7 @@ describe("dispatchReplyFromConfig", () => { handled: false, aborted: false, }); - const cfg = {} as ClawdbotConfig; + const cfg = {} as MoltbotConfig; const dispatcher = createDispatcher(); const ctx = buildTestCtx({ Provider: "telegram", @@ -235,7 +260,7 @@ describe("dispatchReplyFromConfig", () => { const replyResolver = async ( _ctx: MsgContext, opts: GetReplyOptions | undefined, - _cfg: ClawdbotConfig, + _cfg: MoltbotConfig, ) => { expect(opts?.onToolResult).toBeUndefined(); return { text: "hi" } satisfies ReplyPayload; diff --git a/src/auto-reply/reply/dispatch-from-config.ts b/src/auto-reply/reply/dispatch-from-config.ts index c85e654de..ee4ccf07a 100644 --- a/src/auto-reply/reply/dispatch-from-config.ts +++ b/src/auto-reply/reply/dispatch-from-config.ts @@ -1,5 +1,6 @@ import type { MoltbotConfig } from "../../config/config.js"; import { resolveSessionAgentId } from "../../agents/agent-scope.js"; +import { normalizeChatType } from "../../channels/chat-type.js"; import { loadSessionStore, resolveStorePath } from "../../config/sessions.js"; import { logVerbose } from "../../globals.js"; import { isDiagnosticsEnabled } from "../../infra/diagnostic-events.js"; @@ -196,6 +197,8 @@ export async function dispatchReplyFromConfig(params: { const shouldRouteToOriginating = isRoutableChannel(originatingChannel) && originatingTo && originatingChannel !== currentSurface; const ttsChannel = shouldRouteToOriginating ? originatingChannel : currentSurface; + const normalizedChatType = normalizeChatType(ctx.ChatType); + const shouldEmitToolSummaries = normalizedChatType === "direct" && ctx.CommandSource !== "native"; /** * Helper to send a payload via route-reply (async). @@ -276,27 +279,26 @@ export async function dispatchReplyFromConfig(params: { ctx, { ...params.replyOptions, - onToolResult: - ctx.ChatType !== "group" && ctx.CommandSource !== "native" - ? (payload: ReplyPayload) => { - const run = async () => { - const ttsPayload = await maybeApplyTtsToPayload({ - payload, - cfg, - channel: ttsChannel, - kind: "tool", - inboundAudio, - ttsAuto: sessionTtsAuto, - }); - if (shouldRouteToOriginating) { - await sendPayloadAsync(ttsPayload, undefined, false); - } else { - dispatcher.sendToolResult(ttsPayload); - } - }; - return run(); - } - : undefined, + onToolResult: shouldEmitToolSummaries + ? (payload: ReplyPayload) => { + const run = async () => { + const ttsPayload = await maybeApplyTtsToPayload({ + payload, + cfg, + channel: ttsChannel, + kind: "tool", + inboundAudio, + ttsAuto: sessionTtsAuto, + }); + if (shouldRouteToOriginating) { + await sendPayloadAsync(ttsPayload, undefined, false); + } else { + dispatcher.sendToolResult(ttsPayload); + } + }; + return run(); + } + : undefined, onBlockReply: (payload: ReplyPayload, context) => { const run = async () => { // Accumulate block text for TTS generation after streaming