Skip to content

Commit 5dae2f5

Browse files
committed
fix(telegram): preserve bot-self reply target context under allowlist visibility (#82002)
Telegram supplemental-context filter computed senderAllowed only from the group allowlist, so a user reply to a bot-sent cron/system message would drop the quoted bot text whenever the bot account was not in effectiveGroupAllow under contextVisibility: "allowlist". Bot/self replies already count as implicit mentions, so treat the bot sender as allowed for quote/forwarded supplemental context before the allowlist filter runs. Adds extensions/telegram/src/bot.test.ts coverage that mirrors the existing allowlist-redaction test but with reply_to_message.from.id == primaryCtx.me.id; ReplyToBody is now preserved instead of being filtered out.
1 parent 6aa85df commit 5dae2f5

2 files changed

Lines changed: 59 additions & 7 deletions

File tree

extensions/telegram/src/bot-message-context.session.ts

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -262,6 +262,7 @@ export async function buildTelegramInboundContextPayload(params: {
262262
channel: "telegram",
263263
accountId: route.accountId,
264264
});
265+
const botSenderId = primaryCtx.me?.id != null ? String(primaryCtx.me.id) : undefined;
265266
const shouldIncludeGroupSupplementalContext = (paramsLocal: {
266267
kind: "quote" | "forwarded";
267268
senderId?: string;
@@ -270,13 +271,19 @@ export async function buildTelegramInboundContextPayload(params: {
270271
if (!isGroup) {
271272
return true;
272273
}
273-
const senderAllowed = effectiveGroupAllow?.hasEntries
274-
? isSenderAllowed({
275-
allow: effectiveGroupAllow,
276-
senderId: paramsLocal.senderId,
277-
senderUsername: paramsLocal.senderUsername,
278-
})
279-
: true;
274+
// Bot/self messages already count as implicit mentions in groups; treat
275+
// them as allowed supplemental context so a user replying to a bot-sent
276+
// notification keeps the quoted bot text under allowlist visibility.
277+
const isBotSelfSender = botSenderId != null && paramsLocal.senderId === botSenderId;
278+
const senderAllowed = isBotSelfSender
279+
? true
280+
: effectiveGroupAllow?.hasEntries
281+
? isSenderAllowed({
282+
allow: effectiveGroupAllow,
283+
senderId: paramsLocal.senderId,
284+
senderUsername: paramsLocal.senderUsername,
285+
})
286+
: true;
280287
return evaluateSupplementalContextVisibility({
281288
mode: contextVisibilityMode,
282289
kind: paramsLocal.kind,

extensions/telegram/src/bot.test.ts

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3081,6 +3081,51 @@ describe("createTelegramBot", () => {
30813081
expect(payload.Body).not.toContain("[Forwarded from Bob Smith (@bobsmith)");
30823082
});
30833083

3084+
it("preserves bot-self reply target context under allowlist visibility", async () => {
3085+
onSpy.mockReset();
3086+
sendMessageSpy.mockReset();
3087+
replySpy.mockReset();
3088+
loadConfig.mockReturnValue({
3089+
channels: {
3090+
telegram: {
3091+
groupPolicy: "allowlist",
3092+
contextVisibility: "allowlist",
3093+
groups: {
3094+
"-1008": {
3095+
requireMention: false,
3096+
allowFrom: ["1"],
3097+
},
3098+
},
3099+
},
3100+
},
3101+
});
3102+
3103+
createTelegramBot({ token: "tok" });
3104+
const handler = getOnHandler("message") as (ctx: Record<string, unknown>) => Promise<void>;
3105+
3106+
await handler({
3107+
message: {
3108+
message_id: 9100,
3109+
chat: { id: -1008, type: "group", title: "Ops" },
3110+
text: "what happened?",
3111+
date: 1736380800,
3112+
from: { id: 1, first_name: "Ada", username: "ada", is_bot: false },
3113+
reply_to_message: {
3114+
message_id: 9099,
3115+
text: "cron notification body",
3116+
from: { id: 999, first_name: "OpenClaw", is_bot: true },
3117+
},
3118+
},
3119+
me: { id: 999, username: "openclaw_bot" },
3120+
getFile: async () => ({ download: async () => new Uint8Array() }),
3121+
});
3122+
3123+
expect(replySpy).toHaveBeenCalledTimes(1);
3124+
const payload = mockMsgContextArg(replySpy as unknown as MockCallSource, 0, 0, "replySpy call");
3125+
expect(payload.ReplyToId).toBe("9099");
3126+
expect(payload.ReplyToBody).toBe("cron notification body");
3127+
});
3128+
30843129
it("accepts group replies to the bot without explicit mention when requireMention is enabled", async () => {
30853130
onSpy.mockClear();
30863131
replySpy.mockClear();

0 commit comments

Comments
 (0)