fix: gate tool summaries to direct chats (#2673) (thanks @Lukavyi)

This commit is contained in:
Ayaan Zaidi 2026-01-29 09:50:13 +05:30
parent 8179e9b499
commit e0e37af470
3 changed files with 55 additions and 27 deletions

View File

@ -73,6 +73,7 @@ Status: beta.
- **BREAKING:** Gateway auth mode "none" is removed; gateway now requires token/password (Tailscale Serve identity still allowed). - **BREAKING:** Gateway auth mode "none" is removed; gateway now requires token/password (Tailscale Serve identity still allowed).
### Fixes ### Fixes
- Telegram: restore verbose tool summaries for direct messages only. (#2673) Thanks @Lukavyi.
- Discord: restore username directory lookup in target resolution. (#3131) Thanks @bonald. - 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: 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. - Agents: prevent retries on oversized image errors and surface size limits. (#2871) Thanks @Suksham-sharma.

View File

@ -154,7 +154,7 @@ describe("dispatchReplyFromConfig", () => {
const replyResolver = async ( const replyResolver = async (
_ctx: MsgContext, _ctx: MsgContext,
opts: GetReplyOptions | undefined, opts: GetReplyOptions | undefined,
_cfg: ClawdbotConfig, _cfg: MoltbotConfig,
) => { ) => {
expect(opts?.onToolResult).toBeDefined(); expect(opts?.onToolResult).toBeDefined();
expect(typeof opts?.onToolResult).toBe("function"); expect(typeof opts?.onToolResult).toBe("function");
@ -170,7 +170,7 @@ describe("dispatchReplyFromConfig", () => {
handled: false, handled: false,
aborted: false, aborted: false,
}); });
const cfg = {} as ClawdbotConfig; const cfg = {} as MoltbotConfig;
const dispatcher = createDispatcher(); const dispatcher = createDispatcher();
const ctx = buildTestCtx({ const ctx = buildTestCtx({
Provider: "telegram", Provider: "telegram",
@ -190,12 +190,37 @@ describe("dispatchReplyFromConfig", () => {
expect(dispatcher.sendFinalReply).toHaveBeenCalledTimes(1); 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 () => { it("sends tool results via dispatcher in DM sessions", async () => {
mocks.tryFastAbortFromMessage.mockResolvedValue({ mocks.tryFastAbortFromMessage.mockResolvedValue({
handled: false, handled: false,
aborted: false, aborted: false,
}); });
const cfg = {} as ClawdbotConfig; const cfg = {} as MoltbotConfig;
const dispatcher = createDispatcher(); const dispatcher = createDispatcher();
const ctx = buildTestCtx({ const ctx = buildTestCtx({
Provider: "telegram", Provider: "telegram",
@ -205,7 +230,7 @@ describe("dispatchReplyFromConfig", () => {
const replyResolver = async ( const replyResolver = async (
_ctx: MsgContext, _ctx: MsgContext,
opts: GetReplyOptions | undefined, opts: GetReplyOptions | undefined,
_cfg: ClawdbotConfig, _cfg: MoltbotConfig,
) => { ) => {
// Simulate tool result emission // Simulate tool result emission
await opts?.onToolResult?.({ text: "🔧 exec: ls" }); await opts?.onToolResult?.({ text: "🔧 exec: ls" });
@ -224,7 +249,7 @@ describe("dispatchReplyFromConfig", () => {
handled: false, handled: false,
aborted: false, aborted: false,
}); });
const cfg = {} as ClawdbotConfig; const cfg = {} as MoltbotConfig;
const dispatcher = createDispatcher(); const dispatcher = createDispatcher();
const ctx = buildTestCtx({ const ctx = buildTestCtx({
Provider: "telegram", Provider: "telegram",
@ -235,7 +260,7 @@ describe("dispatchReplyFromConfig", () => {
const replyResolver = async ( const replyResolver = async (
_ctx: MsgContext, _ctx: MsgContext,
opts: GetReplyOptions | undefined, opts: GetReplyOptions | undefined,
_cfg: ClawdbotConfig, _cfg: MoltbotConfig,
) => { ) => {
expect(opts?.onToolResult).toBeUndefined(); expect(opts?.onToolResult).toBeUndefined();
return { text: "hi" } satisfies ReplyPayload; return { text: "hi" } satisfies ReplyPayload;

View File

@ -1,5 +1,6 @@
import type { MoltbotConfig } from "../../config/config.js"; import type { MoltbotConfig } from "../../config/config.js";
import { resolveSessionAgentId } from "../../agents/agent-scope.js"; import { resolveSessionAgentId } from "../../agents/agent-scope.js";
import { normalizeChatType } from "../../channels/chat-type.js";
import { loadSessionStore, resolveStorePath } from "../../config/sessions.js"; import { loadSessionStore, resolveStorePath } from "../../config/sessions.js";
import { logVerbose } from "../../globals.js"; import { logVerbose } from "../../globals.js";
import { isDiagnosticsEnabled } from "../../infra/diagnostic-events.js"; import { isDiagnosticsEnabled } from "../../infra/diagnostic-events.js";
@ -196,6 +197,8 @@ export async function dispatchReplyFromConfig(params: {
const shouldRouteToOriginating = const shouldRouteToOriginating =
isRoutableChannel(originatingChannel) && originatingTo && originatingChannel !== currentSurface; isRoutableChannel(originatingChannel) && originatingTo && originatingChannel !== currentSurface;
const ttsChannel = shouldRouteToOriginating ? 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). * Helper to send a payload via route-reply (async).
@ -276,27 +279,26 @@ export async function dispatchReplyFromConfig(params: {
ctx, ctx,
{ {
...params.replyOptions, ...params.replyOptions,
onToolResult: onToolResult: shouldEmitToolSummaries
ctx.ChatType !== "group" && ctx.CommandSource !== "native" ? (payload: ReplyPayload) => {
? (payload: ReplyPayload) => { const run = async () => {
const run = async () => { const ttsPayload = await maybeApplyTtsToPayload({
const ttsPayload = await maybeApplyTtsToPayload({ payload,
payload, cfg,
cfg, channel: ttsChannel,
channel: ttsChannel, kind: "tool",
kind: "tool", inboundAudio,
inboundAudio, ttsAuto: sessionTtsAuto,
ttsAuto: sessionTtsAuto, });
}); if (shouldRouteToOriginating) {
if (shouldRouteToOriginating) { await sendPayloadAsync(ttsPayload, undefined, false);
await sendPayloadAsync(ttsPayload, undefined, false); } else {
} else { dispatcher.sendToolResult(ttsPayload);
dispatcher.sendToolResult(ttsPayload); }
} };
}; return run();
return run(); }
} : undefined,
: undefined,
onBlockReply: (payload: ReplyPayload, context) => { onBlockReply: (payload: ReplyPayload, context) => {
const run = async () => { const run = async () => {
// Accumulate block text for TTS generation after streaming // Accumulate block text for TTS generation after streaming