mirror of
https://github.com/openclaw/openclaw.git
synced 2026-06-03 14:34:07 +00:00
refactor(telegram): keep topic thread mapping plugin-local
* refactor(telegram): keep topic thread mapping plugin-local * fix(telegram): preserve native topic ids for username targets
This commit is contained in:
committed by
GitHub
parent
9e2bd8b2f7
commit
70c8abdca1
@@ -553,28 +553,60 @@ function resolveTelegramOutboundSessionRoute(params: {
|
||||
if (isGroup) {
|
||||
return baseRoute;
|
||||
}
|
||||
const outboundSessionThreadId =
|
||||
resolvedThreadId !== undefined ? `${chatId}:${resolvedThreadId}` : undefined;
|
||||
const canonicalThreadId =
|
||||
resolvedThreadId !== undefined
|
||||
? buildTelegramCanonicalTopicThreadId({ chatId, topicId: resolvedThreadId })
|
||||
: undefined;
|
||||
const route = buildThreadAwareOutboundSessionRoute({
|
||||
route: baseRoute,
|
||||
threadId: outboundSessionThreadId,
|
||||
threadId: canonicalThreadId,
|
||||
currentSessionKey: params.currentSessionKey,
|
||||
precedence: ["threadId", "currentSession"],
|
||||
canRecoverCurrentThread: ({ route }) =>
|
||||
route.chatType !== "direct" || (params.cfg.session?.dmScope ?? "main") !== "main",
|
||||
});
|
||||
const deliveryThreadId =
|
||||
resolvedThreadId ?? parseTelegramThreadId(route.threadId) ?? route.threadId;
|
||||
const routeThreadId = resolveTelegramNativeTopicThreadId(route.threadId, resolvedThreadId);
|
||||
return {
|
||||
...route,
|
||||
...(deliveryThreadId !== undefined ? { threadId: deliveryThreadId } : {}),
|
||||
...(routeThreadId !== undefined ? { threadId: routeThreadId } : {}),
|
||||
from:
|
||||
deliveryThreadId !== undefined
|
||||
? `telegram:${chatId}:topic:${deliveryThreadId}`
|
||||
routeThreadId !== undefined
|
||||
? `telegram:${chatId}:topic:${routeThreadId}`
|
||||
: `telegram:${chatId}`,
|
||||
};
|
||||
}
|
||||
|
||||
function buildTelegramCanonicalTopicThreadId(params: { chatId: string; topicId: number }): string {
|
||||
// Core session routing sees one canonical thread id. Telegram topic ids are
|
||||
// chat-scoped, so direct-topic sessions include the chat id to avoid collisions.
|
||||
return `${params.chatId}:${params.topicId}`;
|
||||
}
|
||||
|
||||
function resolveTelegramNativeTopicThreadId(
|
||||
threadId?: string | number,
|
||||
nativeTopicId?: number,
|
||||
): string | number | undefined {
|
||||
if (nativeTopicId !== undefined) {
|
||||
return nativeTopicId;
|
||||
}
|
||||
// Keep the chat-scoped canonical id inside OpenClaw state; translate it back
|
||||
// only when returning Telegram route metadata used by send/typing paths.
|
||||
if (threadId === undefined) {
|
||||
return undefined;
|
||||
}
|
||||
const parsedThreadId = parseTelegramThreadId(threadId);
|
||||
if (parsedThreadId !== undefined) {
|
||||
return parsedThreadId;
|
||||
}
|
||||
if (typeof threadId === "string") {
|
||||
const canonicalMatch = /:(\d+)$/.exec(threadId.trim());
|
||||
if (canonicalMatch?.[1]) {
|
||||
return Number(canonicalMatch[1]);
|
||||
}
|
||||
}
|
||||
return threadId;
|
||||
}
|
||||
|
||||
async function resolveTelegramTargets(params: {
|
||||
cfg: OpenClawConfig;
|
||||
accountId?: string | null;
|
||||
|
||||
@@ -14,6 +14,37 @@ describe("telegram session route", () => {
|
||||
expect(route?.threadId).toBe(99);
|
||||
});
|
||||
|
||||
it("keeps same direct topic ids distinct across chats", async () => {
|
||||
const first = await telegramPlugin.messaging?.resolveOutboundSessionRoute?.({
|
||||
cfg: {},
|
||||
agentId: "main",
|
||||
target: "12345:topic:99",
|
||||
});
|
||||
const second = await telegramPlugin.messaging?.resolveOutboundSessionRoute?.({
|
||||
cfg: {},
|
||||
agentId: "main",
|
||||
target: "67890:topic:99",
|
||||
});
|
||||
|
||||
expect(first?.sessionKey).toBe("agent:main:main:thread:12345:99");
|
||||
expect(second?.sessionKey).toBe("agent:main:main:thread:67890:99");
|
||||
expect(first?.threadId).toBe(99);
|
||||
expect(second?.threadId).toBe(99);
|
||||
});
|
||||
|
||||
it("returns native topic ids for username direct topic targets", async () => {
|
||||
const route = await telegramPlugin.messaging?.resolveOutboundSessionRoute?.({
|
||||
cfg: {},
|
||||
agentId: "main",
|
||||
target: "@alice:topic:99",
|
||||
});
|
||||
|
||||
expect(route?.sessionKey).toBe("agent:main:main:thread:@alice:99");
|
||||
expect(route?.baseSessionKey).toBe("agent:main:main");
|
||||
expect(route?.threadId).toBe(99);
|
||||
expect(route?.from).toBe("telegram:@alice:topic:99");
|
||||
});
|
||||
|
||||
it("aligns isolated direct topic sessions with inbound reply routing", async () => {
|
||||
const route = await telegramPlugin.messaging?.resolveOutboundSessionRoute?.({
|
||||
cfg: { session: { dmScope: "per-account-channel-peer" } },
|
||||
@@ -44,6 +75,20 @@ describe("telegram session route", () => {
|
||||
expect(route?.from).toBe("telegram:12345:topic:99");
|
||||
});
|
||||
|
||||
it("recovers username direct topic thread routes from currentSessionKey", async () => {
|
||||
const route = await telegramPlugin.messaging?.resolveOutboundSessionRoute?.({
|
||||
cfg: { session: { dmScope: "per-channel-peer" } },
|
||||
agentId: "main",
|
||||
target: "@alice",
|
||||
currentSessionKey: "agent:main:telegram:direct:@alice:thread:@alice:99",
|
||||
});
|
||||
|
||||
expect(route?.sessionKey).toBe("agent:main:telegram:direct:@alice:thread:@alice:99");
|
||||
expect(route?.baseSessionKey).toBe("agent:main:telegram:direct:@alice");
|
||||
expect(route?.threadId).toBe(99);
|
||||
expect(route?.from).toBe("telegram:@alice:topic:99");
|
||||
});
|
||||
|
||||
it('does not recover currentSessionKey threads for shared dmScope "main" DMs', async () => {
|
||||
const route = await telegramPlugin.messaging?.resolveOutboundSessionRoute?.({
|
||||
cfg: {},
|
||||
|
||||
Reference in New Issue
Block a user