fix: auto-inject Telegram forum topic threadId in message tool

When using Telegram DM topics (forum topics), messages sent via the
message tool (media, buttons, etc.) land in General Topic instead of
the user's current topic. This happens because Slack has
resolveSlackAutoThreadId for auto-threading but Telegram had no
equivalent.

Add resolveTelegramAutoThreadId that mirrors the Slack pattern:
- When channel is telegram and no explicit threadId is provided
- Check if toolContext.currentThreadTs (the topic ID) is set
- Verify the target matches the originating chat
- Inject the threadId into params so the Telegram plugin action
  handler picks it up for sendMessage/sendMedia

The subagent announce path already correctly passes threadId via
requesterOrigin (set from agentThreadId in sessions-spawn-tool),
so no changes needed there.
This commit is contained in:
Clawdbot
2026-02-02 16:46:19 +01:00
committed by Ayaan Zaidi
parent 9e0030b75f
commit eef247b7a4

View File

@@ -20,6 +20,7 @@ import { parseReplyDirectives } from "../../auto-reply/reply/reply-directives.js
import { dispatchChannelMessageAction } from "../../channels/plugins/message-actions.js";
import { extensionForMime } from "../../media/mime.js";
import { parseSlackTarget } from "../../slack/targets.js";
import { parseTelegramTarget } from "../../telegram/targets.js";
import {
isDeliverableMessageChannel,
normalizeMessageChannel,
@@ -244,6 +245,32 @@ function resolveSlackAutoThreadId(params: {
return context.currentThreadTs;
}
/**
* Auto-inject Telegram forum topic thread ID when the message tool targets
* the same chat the session originated from. Mirrors the Slack auto-threading
* pattern so media, buttons, and other tool-sent messages land in the correct
* topic instead of the General Topic.
*/
function resolveTelegramAutoThreadId(params: {
to: string;
toolContext?: ChannelThreadingToolContext;
}): string | undefined {
const context = params.toolContext;
if (!context?.currentThreadTs || !context.currentChannelId) {
return undefined;
}
// Parse both targets to extract base chat IDs, ignoring topic suffixes and
// internal prefixes (e.g. "telegram:group:123:topic:456" → "123").
// This mirrors Slack's parseSlackTarget approach — compare canonical chat IDs
// so auto-threading applies even when representations differ.
const parsedTo = parseTelegramTarget(params.to);
const parsedChannel = parseTelegramTarget(context.currentChannelId);
if (parsedTo.chatId.toLowerCase() !== parsedChannel.chatId.toLowerCase()) {
return undefined;
}
return context.currentThreadTs;
}
function resolveAttachmentMaxBytes(params: {
cfg: OpenClawConfig;
channel: ChannelId;
@@ -792,6 +819,16 @@ async function handleSendAction(ctx: ResolvedActionContext): Promise<MessageActi
channel === "slack" && !replyToId && !threadId
? resolveSlackAutoThreadId({ to, toolContext: input.toolContext })
: undefined;
// Telegram forum topic auto-threading: inject threadId so media/buttons land in the correct topic.
const telegramAutoThreadId =
channel === "telegram" && !threadId
? resolveTelegramAutoThreadId({ to, toolContext: input.toolContext })
: undefined;
const resolvedAutoThreadId = threadId ?? slackAutoThreadId ?? telegramAutoThreadId;
// Inject the resolved thread ID back into params so downstream dispatch (plugin/gateway) sees it.
if (resolvedAutoThreadId && !params.threadId) {
params.threadId = resolvedAutoThreadId;
}
const outboundRoute =
agentId && !dryRun
? await resolveOutboundSessionRoute({
@@ -802,7 +839,7 @@ async function handleSendAction(ctx: ResolvedActionContext): Promise<MessageActi
target: to,
resolvedTarget,
replyToId,
threadId: threadId ?? slackAutoThreadId,
threadId: resolvedAutoThreadId,
})
: null;
if (outboundRoute && agentId && !dryRun) {