fix(telegram): inject toolContext threadId into message tool params

When using the message tool in a Telegram DM with topics enabled,
media and text were sent to the General topic instead of the current
session topic. This happened because toolContext.currentThreadTs was
not mapped to params.threadId before the Telegram plugin action
handler read it.

Now runMessageAction injects toolContext.currentThreadTs into
params.threadId when not explicitly provided, ensuring messages
land in the correct topic.

Fixes #2777
This commit is contained in:
Clawdbot 2026-01-27 14:55:50 +01:00
parent 3f83afe4a6
commit c5267396a4
2 changed files with 116 additions and 0 deletions

View File

@ -4,6 +4,7 @@ import type { MoltbotConfig } from "../../config/config.js";
import { setActivePluginRegistry } from "../../plugins/runtime.js";
import { createTestRegistry } from "../../test-utils/channel-plugins.js";
import { slackPlugin } from "../../../extensions/slack/src/channel.js";
import { telegramPlugin } from "../../../extensions/telegram/src/channel.js";
const mocks = vi.hoisted(() => ({
executeSendAction: vi.fn(),
@ -116,3 +117,111 @@ describe("runMessageAction Slack threading", () => {
expect(call?.ctx?.mirror?.sessionKey).toBe("agent:main:slack:channel:c123:thread:333.444");
});
});
const telegramConfig = {
channels: {
telegram: {
enabled: true,
botToken: "test:token",
},
},
} as MoltbotConfig;
describe("runMessageAction thread id injection from toolContext", () => {
beforeEach(async () => {
const { createPluginRuntime } = await import("../../plugins/runtime/index.js");
const { setTelegramRuntime } = await import("../../../extensions/telegram/src/runtime.js");
const runtime = createPluginRuntime();
setTelegramRuntime(runtime);
setActivePluginRegistry(
createTestRegistry([
{
pluginId: "telegram",
source: "test",
plugin: telegramPlugin,
},
]),
);
});
afterEach(() => {
setActivePluginRegistry(createTestRegistry([]));
mocks.executeSendAction.mockReset();
mocks.recordSessionMetaFromInbound.mockReset();
});
it("injects toolContext.currentThreadTs into params.threadId when not explicitly set", async () => {
mocks.executeSendAction.mockResolvedValue({
handledBy: "plugin",
payload: { ok: true, messageId: "123", chatId: "63448508" },
});
await runMessageAction({
cfg: telegramConfig,
action: "send",
params: {
channel: "telegram",
target: "63448508",
message: "hello from topic",
},
toolContext: {
currentChannelId: "63448508",
currentChannelProvider: "telegram",
currentThreadTs: "994409",
},
});
const call = mocks.executeSendAction.mock.calls[0]?.[0];
expect(call?.ctx?.params?.threadId).toBe("994409");
});
it("does not override explicit threadId with toolContext", async () => {
mocks.executeSendAction.mockResolvedValue({
handledBy: "plugin",
payload: { ok: true, messageId: "124", chatId: "63448508" },
});
await runMessageAction({
cfg: telegramConfig,
action: "send",
params: {
channel: "telegram",
target: "63448508",
message: "explicit thread",
threadId: "12345",
},
toolContext: {
currentChannelId: "63448508",
currentChannelProvider: "telegram",
currentThreadTs: "994409",
},
});
const call = mocks.executeSendAction.mock.calls[0]?.[0];
expect(call?.ctx?.params?.threadId).toBe("12345");
});
it("does not inject threadId when toolContext has no currentThreadTs", async () => {
mocks.executeSendAction.mockResolvedValue({
handledBy: "plugin",
payload: { ok: true, messageId: "125", chatId: "63448508" },
});
await runMessageAction({
cfg: telegramConfig,
action: "send",
params: {
channel: "telegram",
target: "63448508",
message: "no thread context",
},
toolContext: {
currentChannelId: "63448508",
currentChannelProvider: "telegram",
},
});
const call = mocks.executeSendAction.mock.calls[0]?.[0];
expect(call?.ctx?.params?.threadId).toBeUndefined();
});
});

View File

@ -900,6 +900,13 @@ export async function runMessageAction(
}
}
// Inject thread id from session context when not explicitly provided.
// This ensures the message tool sends to the correct Telegram DM topic
// (or forum thread) instead of the General/root chat.
if (!params.threadId && input.toolContext?.currentThreadTs) {
params.threadId = input.toolContext.currentThreadTs;
}
applyTargetToParams({ action, args: params });
if (actionRequiresTarget(action)) {
if (!actionHasTarget(action, params)) {