mirror of
https://github.com/openclaw/openclaw.git
synced 2026-02-08 21:09:23 +08:00
fix: telegram topic auto-threading — use parseTelegramTarget, add tests (#7235) (thanks @Lukavyi)
This commit is contained in:
@@ -6,6 +6,7 @@ Docs: https://docs.openclaw.ai
|
||||
|
||||
### Changes
|
||||
|
||||
- Telegram: auto-inject forum topic `threadId` in message tool and subagent announce so media, buttons, and subagent results land in the correct topic instead of General. (#7235) Thanks @Lukavyi.
|
||||
- CLI: sort `openclaw --help` commands (and options) alphabetically. (#8068) Thanks @deepsoumya617.
|
||||
- Telegram: remove last `@ts-nocheck` from `bot-handlers.ts`, use Grammy types directly, deduplicate `StickerMetadata`. Zero `@ts-nocheck` remaining in `src/telegram/`. (#9206)
|
||||
- Telegram: remove `@ts-nocheck` from `bot-message.ts`, type deps via `Omit<BuildTelegramMessageContextParams>`, widen `allMedia` to `TelegramMediaRef[]`. (#9180)
|
||||
|
||||
@@ -152,7 +152,63 @@ describe("runMessageAction threading auto-injection", () => {
|
||||
agentId: "main",
|
||||
});
|
||||
|
||||
const call = mocks.executeSendAction.mock.calls[0]?.[0] as { ctx?: { params?: any } };
|
||||
const call = mocks.executeSendAction.mock.calls[0]?.[0] as {
|
||||
ctx?: { params?: Record<string, unknown> };
|
||||
};
|
||||
expect(call?.ctx?.params?.threadId).toBe("42");
|
||||
});
|
||||
|
||||
it("skips telegram auto-threading when target chat differs", async () => {
|
||||
mocks.executeSendAction.mockResolvedValue({
|
||||
handledBy: "plugin",
|
||||
payload: {},
|
||||
});
|
||||
|
||||
await runMessageAction({
|
||||
cfg: telegramConfig,
|
||||
action: "send",
|
||||
params: {
|
||||
channel: "telegram",
|
||||
target: "telegram:999",
|
||||
message: "hi",
|
||||
},
|
||||
toolContext: {
|
||||
currentChannelId: "telegram:123",
|
||||
currentThreadTs: "42",
|
||||
},
|
||||
agentId: "main",
|
||||
});
|
||||
|
||||
const call = mocks.executeSendAction.mock.calls[0]?.[0] as {
|
||||
ctx?: { params?: Record<string, unknown> };
|
||||
};
|
||||
expect(call?.ctx?.params?.threadId).toBeUndefined();
|
||||
});
|
||||
|
||||
it("matches telegram target with internal prefix variations", async () => {
|
||||
mocks.executeSendAction.mockResolvedValue({
|
||||
handledBy: "plugin",
|
||||
payload: {},
|
||||
});
|
||||
|
||||
await runMessageAction({
|
||||
cfg: telegramConfig,
|
||||
action: "send",
|
||||
params: {
|
||||
channel: "telegram",
|
||||
target: "telegram:group:123",
|
||||
message: "hi",
|
||||
},
|
||||
toolContext: {
|
||||
currentChannelId: "telegram:123",
|
||||
currentThreadTs: "42",
|
||||
},
|
||||
agentId: "main",
|
||||
});
|
||||
|
||||
const call = mocks.executeSendAction.mock.calls[0]?.[0] as {
|
||||
ctx?: { params?: Record<string, unknown> };
|
||||
};
|
||||
expect(call?.ctx?.params?.threadId).toBe("42");
|
||||
});
|
||||
|
||||
@@ -178,7 +234,9 @@ describe("runMessageAction threading auto-injection", () => {
|
||||
agentId: "main",
|
||||
});
|
||||
|
||||
const call = mocks.executeSendAction.mock.calls[0]?.[0] as { ctx?: { params?: any } };
|
||||
const call = mocks.executeSendAction.mock.calls[0]?.[0] as {
|
||||
ctx?: { params?: Record<string, unknown> };
|
||||
};
|
||||
expect(call?.ctx?.params?.threadId).toBe("999");
|
||||
});
|
||||
});
|
||||
|
||||
@@ -20,7 +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";
|
||||
// parseTelegramTarget no longer used (telegram auto-threading uses string matching)
|
||||
import { parseTelegramTarget } from "../../telegram/targets.js";
|
||||
import {
|
||||
isDeliverableMessageChannel,
|
||||
normalizeMessageChannel,
|
||||
@@ -250,6 +250,10 @@ function resolveSlackAutoThreadId(params: {
|
||||
* 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.
|
||||
*
|
||||
* Unlike Slack, we do not gate on `replyToMode` here: Telegram forum topics
|
||||
* are persistent sub-channels (not ephemeral reply threads), so auto-injection
|
||||
* should always apply when the target chat matches.
|
||||
*/
|
||||
function resolveTelegramAutoThreadId(params: {
|
||||
to: string;
|
||||
@@ -259,12 +263,12 @@ function resolveTelegramAutoThreadId(params: {
|
||||
if (!context?.currentThreadTs || !context.currentChannelId) {
|
||||
return undefined;
|
||||
}
|
||||
// Only apply when the target matches the originating chat.
|
||||
// Note: Telegram topic routing is carried via threadId/message_thread_id;
|
||||
// `currentChannelId` (and most agent targets) are typically the base chat id.
|
||||
const normalizedTo = params.to.trim().toLowerCase();
|
||||
const normalizedChannel = context.currentChannelId.trim().toLowerCase();
|
||||
if (normalizedTo !== normalizedChannel) {
|
||||
// Use parseTelegramTarget to extract canonical chatId from both sides,
|
||||
// mirroring how Slack uses parseSlackTarget. This handles format variations
|
||||
// like `telegram:group:123:topic:456` vs `telegram:123`.
|
||||
const parsedTo = parseTelegramTarget(params.to);
|
||||
const parsedChannel = parseTelegramTarget(context.currentChannelId);
|
||||
if (parsedTo.chatId.toLowerCase() !== parsedChannel.chatId.toLowerCase()) {
|
||||
return undefined;
|
||||
}
|
||||
return context.currentThreadTs;
|
||||
@@ -823,10 +827,11 @@ async function handleSendAction(ctx: ResolvedActionContext): Promise<MessageActi
|
||||
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 resolvedThreadId = threadId ?? slackAutoThreadId ?? telegramAutoThreadId;
|
||||
// Write auto-resolved threadId back into params so downstream dispatch
|
||||
// (plugin `readStringParam(params, "threadId")`) picks it up.
|
||||
if (resolvedThreadId && !params.threadId) {
|
||||
params.threadId = resolvedThreadId;
|
||||
}
|
||||
const outboundRoute =
|
||||
agentId && !dryRun
|
||||
@@ -838,7 +843,7 @@ async function handleSendAction(ctx: ResolvedActionContext): Promise<MessageActi
|
||||
target: to,
|
||||
resolvedTarget,
|
||||
replyToId,
|
||||
threadId: resolvedAutoThreadId,
|
||||
threadId: resolvedThreadId,
|
||||
})
|
||||
: null;
|
||||
if (outboundRoute && agentId && !dryRun) {
|
||||
|
||||
Reference in New Issue
Block a user