diff --git a/extensions/telegram/src/bot-core.ts b/extensions/telegram/src/bot-core.ts index f8d2330b4e45..b0137d141322 100644 --- a/extensions/telegram/src/bot-core.ts +++ b/extensions/telegram/src/bot-core.ts @@ -300,7 +300,7 @@ export function createTelegramBotCore( } recordTelegramGroupHistoryEntry({ historyMap: groupHistories, - historyKey: buildTelegramGroupPeerId(record.chatId, record.messageThreadId), + historyKey: buildTelegramGroupPeerId(record.chatId, record.threadSpec), limit: historyLimit, entry: { sender: botHistorySender, @@ -331,23 +331,21 @@ export function createTelegramBotCore( groupId: String(chatId), }); const resolveGroupActivation = (params: { - chatId: string | number; agentId?: string; - messageThreadId?: number; - sessionKey?: string; + sessionKey: string; cfg: OpenClawConfig; }) => { const agentId = params.agentId ?? ownerAgentId; - const sessionKey = - params.sessionKey ?? - `agent:${agentId}:telegram:group:${buildTelegramGroupPeerId(params.chatId, params.messageThreadId)}`; const storePath = telegramDeps.resolveStorePath(params.cfg.session?.store, { agentId }); try { const getSessionEntry = telegramDeps.getSessionEntry; if (!getSessionEntry) { return undefined; } - const storedActivation = getSessionEntry({ storePath, sessionKey })?.groupActivation; + const storedActivation = getSessionEntry({ + storePath, + sessionKey: params.sessionKey, + })?.groupActivation; const activation = storedActivation === "mention" || storedActivation === "always" ? normalizeGroupActivation(storedActivation) diff --git a/extensions/telegram/src/bot-handlers.inbound-media.ts b/extensions/telegram/src/bot-handlers.inbound-media.ts index cbed53ca68ba..fe8ba78ba0f2 100644 --- a/extensions/telegram/src/bot-handlers.inbound-media.ts +++ b/extensions/telegram/src/bot-handlers.inbound-media.ts @@ -159,8 +159,6 @@ export function createTelegramInboundMedia({ runtimeCfg: authorization.authorizationCfg, }); const activationOverride = resolveGroupActivation({ - chatId, - messageThreadId: resolvedThreadId, sessionKey: sessionState.sessionKey, agentId: sessionState.agentId, cfg: authorization.authorizationCfg, diff --git a/extensions/telegram/src/bot-handlers.types.ts b/extensions/telegram/src/bot-handlers.types.ts index a54a44a0c048..2555c28fed1b 100644 --- a/extensions/telegram/src/bot-handlers.types.ts +++ b/extensions/telegram/src/bot-handlers.types.ts @@ -83,10 +83,8 @@ export type RegisterTelegramHandlerParams = { telegramDeps: TelegramBotDeps; resolveGroupPolicy: (chatId: string | number, cfg: OpenClawConfig) => ChannelGroupPolicy; resolveGroupActivation: (params: { - chatId: string | number; agentId?: string; - messageThreadId?: number; - sessionKey?: string; + sessionKey: string; cfg: OpenClawConfig; }) => boolean | undefined; resolveGroupRequireMention: (chatId: string | number, cfg: OpenClawConfig) => boolean; diff --git a/extensions/telegram/src/bot-message-context.require-mention.test.ts b/extensions/telegram/src/bot-message-context.require-mention.test.ts index d2b9ab578e2e..6cfb95d8bcc6 100644 --- a/extensions/telegram/src/bot-message-context.require-mention.test.ts +++ b/extensions/telegram/src/bot-message-context.require-mention.test.ts @@ -380,13 +380,11 @@ describe("buildTelegramMessageContext requireMention precedence", () => { if (!ctx?.ctxPayload) { throw new Error("expected Telegram context payload when topic disables requireMention"); } - const activationCalls = resolveGroupActivation.mock.calls as unknown as Array< - [{ chatId: number; messageThreadId?: number; sessionKey: string }] - >; - const [activationOptions] = activationCalls[0] ?? []; - expect(activationOptions?.chatId).toBe(-1001234567890); - expect(activationOptions?.messageThreadId).toBe(99); - expect(activationOptions?.sessionKey).toBe("agent:main:telegram:group:-1001234567890:topic:99"); + expect(resolveGroupActivation).toHaveBeenCalledWith( + expect.objectContaining({ + sessionKey: "agent:main:telegram:group:-1001234567890:topic:99", + }), + ); }); it("lets explicit topic requireMention=true override always activation", async () => { diff --git a/extensions/telegram/src/bot-message-context.ts b/extensions/telegram/src/bot-message-context.ts index e6276e137d92..716ef930b41e 100644 --- a/extensions/telegram/src/bot-message-context.ts +++ b/extensions/telegram/src/bot-message-context.ts @@ -434,8 +434,6 @@ export const buildTelegramMessageContext = async ({ }), }; const activationOverride = resolveGroupActivation({ - chatId, - messageThreadId: resolvedThreadId, sessionKey, agentId: route.agentId, cfg, diff --git a/extensions/telegram/src/bot-message-context.types.ts b/extensions/telegram/src/bot-message-context.types.ts index bb7b0971f94c..6bcf12709074 100644 --- a/extensions/telegram/src/bot-message-context.types.ts +++ b/extensions/telegram/src/bot-message-context.types.ts @@ -72,10 +72,8 @@ type ResolveTelegramGroupConfig = ( }; type ResolveGroupActivation = (params: { - chatId: string | number; agentId?: string; - messageThreadId?: number; - sessionKey?: string; + sessionKey: string; cfg: OpenClawConfig; }) => boolean | undefined; diff --git a/extensions/telegram/src/outbound-message-context.test.ts b/extensions/telegram/src/outbound-message-context.test.ts index 853c74439ed2..b5ceb3ec2248 100644 --- a/extensions/telegram/src/outbound-message-context.test.ts +++ b/extensions/telegram/src/outbound-message-context.test.ts @@ -6,12 +6,17 @@ import { } from "openclaw/plugin-sdk/plugin-state-test-runtime"; import { resolveStorePath } from "openclaw/plugin-sdk/session-store-runtime"; import { afterEach, beforeEach, describe, expect, it } from "vitest"; +import { buildTelegramGroupPeerId } from "./bot/helpers.js"; +import { recordTelegramGroupHistoryEntry } from "./group-history-window.js"; import { resolveTelegramMessageCacheScope } from "./message-cache-persistence.js"; import { createTelegramMessageCache, hasProviderObservedTelegramThreadBinding, } from "./message-cache.js"; -import { recordOutboundMessageForPromptContext } from "./outbound-message-context.js"; +import { + recordOutboundMessageForPromptContext, + registerTelegramOutboundGroupHistoryRecorder, +} from "./outbound-message-context.js"; import { setTelegramRuntime } from "./runtime.js"; import { clearTelegramRuntimeForTest as clearTelegramRuntime, @@ -172,6 +177,59 @@ describe("recordOutboundMessageForPromptContext", () => { expect(cached?.threadBinding?.threadSpec).toEqual({ scope: "direct-messages", id: 77 }); }); + it("records forum and channel Direct Messages replies with the same topic ID in separate histories", async () => { + const chatId = -1001; + const history = new Map>(); + const unregister = registerTelegramOutboundGroupHistoryRecorder({ + accountId: "default", + recorder: (record) => + recordTelegramGroupHistoryEntry({ + historyMap: history, + historyKey: buildTelegramGroupPeerId(record.chatId, record.threadSpec), + limit: 10, + entry: { + sender: "Configured Agent (you)", + body: record.text ?? "", + messageId: String(record.messageId), + }, + }), + }); + + try { + for (const { scope, messageId, body } of [ + { scope: "forum", messageId: 710, body: "Forum reply" }, + { scope: "direct-messages", messageId: 711, body: "Direct-topic reply" }, + ] as const) { + await recordOutboundMessageForPromptContext({ + cfg, + account: { accountId: "default", name: "Configured Agent" }, + chatId, + messageId, + messageThreadId: 77, + successfulSendThread: { scope, id: 77 }, + message: { + chat: { id: chatId, type: "supergroup" }, + date: 1_736_380_700, + message_id: messageId, + ...(scope === "forum" + ? { message_thread_id: 77 } + : { direct_messages_topic: { topic_id: 77 } }), + text: body, + }, + }); + } + + expect(history.get("-1001:direct-topic:77")).toEqual([ + expect.objectContaining({ body: "Direct-topic reply", messageId: "711" }), + ]); + expect(history.get("-1001:topic:77")).toEqual([ + expect.objectContaining({ body: "Forum reply", messageId: "710" }), + ]); + } finally { + unregister(); + } + }); + it("binds a successful General-topic response from trusted send context", async () => { const cached = await recordAndRead({ account: { accountId: "default", name: "Configured Agent" }, diff --git a/extensions/telegram/src/outbound-message-context.ts b/extensions/telegram/src/outbound-message-context.ts index f3f7a65e07cc..4dddd4746012 100644 --- a/extensions/telegram/src/outbound-message-context.ts +++ b/extensions/telegram/src/outbound-message-context.ts @@ -43,7 +43,7 @@ type TelegramOutboundGroupHistoryRecord = { chatId: string | number; messageId: number; text?: string; - messageThreadId?: number; + threadSpec?: TelegramThreadSpec; timestamp?: number; }; @@ -179,11 +179,12 @@ export async function recordOutboundMessageForPromptContext(params: { }); if (params.recordGroupHistory !== false) { const timestamp = resolveOutboundCacheMessageTimestamp(cacheMessage); + const threadSpec = providerObservedThread ?? params.successfulSendThread; outboundGroupHistoryRecorders.get(params.account.accountId)?.({ chatId: params.chatId, messageId: params.messageId, text: params.text ?? cacheMessage.text ?? cacheMessage.caption, - ...(messageThreadId !== undefined ? { messageThreadId } : {}), + ...(threadSpec ? { threadSpec } : {}), ...(timestamp !== undefined ? { timestamp } : {}), }); }