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).
### 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.

View File

@ -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;

View File

@ -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