fix(telegram): preserve direct-topic self-history (#127050)

This commit is contained in:
Peter Steinberger
2026-08-20 23:18:05 -07:00
committed by GitHub
parent d15f570742
commit f1723c757b
8 changed files with 75 additions and 28 deletions
+6 -8
View File
@@ -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)
@@ -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,
@@ -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;
@@ -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 () => {
@@ -434,8 +434,6 @@ export const buildTelegramMessageContext = async ({
}),
};
const activationOverride = resolveGroupActivation({
chatId,
messageThreadId: resolvedThreadId,
sessionKey,
agentId: route.agentId,
cfg,
@@ -72,10 +72,8 @@ type ResolveTelegramGroupConfig = (
};
type ResolveGroupActivation = (params: {
chatId: string | number;
agentId?: string;
messageThreadId?: number;
sessionKey?: string;
sessionKey: string;
cfg: OpenClawConfig;
}) => boolean | undefined;
@@ -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<string, Array<{ sender: string; body: string; messageId: string }>>();
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 ?? "<media>",
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" },
@@ -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 } : {}),
});
}