Skip to content

Commit d68002a

Browse files
committed
fix(telegram): avoid duplicate dm chat window context
1 parent d6bea4c commit d68002a

3 files changed

Lines changed: 96 additions & 1 deletion

File tree

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
import { describe, expect, it } from "vitest";
2+
import { buildTelegramMessageContextForTest } from "./bot-message-context.test-harness.js";
3+
import type { TelegramPromptContextEntry } from "./bot-message-context.types.js";
4+
5+
const telegramChatWindowContext: TelegramPromptContextEntry = {
6+
label: "Conversation context",
7+
source: "telegram",
8+
type: "chat_window",
9+
payload: {
10+
order: "chronological",
11+
relation: "selected_for_current_message",
12+
messages: [
13+
{
14+
message_id: "10",
15+
sender: "Pat",
16+
timestamp_ms: 1_700_000_000_000,
17+
body: "Earlier DM turn already in the transcript",
18+
},
19+
],
20+
},
21+
};
22+
23+
describe("buildTelegramMessageContext prompt context", () => {
24+
it("omits Telegram chat-window context for existing unthreaded private DM sessions", async () => {
25+
const ctx = await buildTelegramMessageContextForTest({
26+
message: {
27+
chat: { id: 1234, type: "private", first_name: "Pat" },
28+
from: { id: 1234, first_name: "Pat" },
29+
text: "continue",
30+
},
31+
promptContext: [telegramChatWindowContext],
32+
sessionRuntime: {
33+
readSessionUpdatedAt: ({ sessionKey }) =>
34+
sessionKey === "agent:main:main" ? 1_700_000_000_000 : undefined,
35+
},
36+
});
37+
38+
expect(ctx?.ctxPayload.SessionKey).toBe("agent:main:main");
39+
expect(ctx?.ctxPayload.UntrustedStructuredContext).toBeUndefined();
40+
});
41+
42+
it("keeps Telegram chat-window context for fresh private DM sessions", async () => {
43+
const ctx = await buildTelegramMessageContextForTest({
44+
message: {
45+
chat: { id: 1234, type: "private", first_name: "Pat" },
46+
from: { id: 1234, first_name: "Pat" },
47+
text: "start",
48+
},
49+
promptContext: [telegramChatWindowContext],
50+
});
51+
52+
expect(ctx?.ctxPayload.UntrustedStructuredContext).toEqual([telegramChatWindowContext]);
53+
});
54+
55+
it("keeps Telegram chat-window context for existing private DM replies", async () => {
56+
const ctx = await buildTelegramMessageContextForTest({
57+
message: {
58+
chat: { id: 1234, type: "private", first_name: "Pat" },
59+
from: { id: 1234, first_name: "Pat" },
60+
text: "replying with context",
61+
reply_to_message: {
62+
chat: { id: 1234, type: "private", first_name: "Pat" },
63+
from: { id: 1234, first_name: "Pat" },
64+
text: "older referenced turn",
65+
date: 1_700_000_000,
66+
message_id: 10,
67+
},
68+
},
69+
promptContext: [telegramChatWindowContext],
70+
sessionRuntime: {
71+
readSessionUpdatedAt: ({ sessionKey }) =>
72+
sessionKey === "agent:main:main" ? 1_700_000_000_000 : undefined,
73+
},
74+
});
75+
76+
expect(ctx?.ctxPayload.UntrustedStructuredContext).toEqual([telegramChatWindowContext]);
77+
});
78+
});

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

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,10 @@ export async function resolveTelegramMessageContextStorePath(params: {
101101
});
102102
}
103103

104+
function isTelegramChatWindowPromptContext(entry: TelegramPromptContextEntry): boolean {
105+
return entry.source === "telegram" && entry.type === "chat_window";
106+
}
107+
104108
function replyTargetToChainEntry(replyTarget: TelegramReplyTarget): TelegramReplyChainEntry {
105109
return {
106110
...(replyTarget.id ? { messageId: replyTarget.id } : {}),
@@ -361,6 +365,17 @@ export async function buildTelegramInboundContextPayload(params: {
361365
storePath,
362366
sessionKey: route.sessionKey,
363367
});
368+
const shouldSuppressPersistedDmChatWindowContext =
369+
!isGroup &&
370+
previousTimestamp !== undefined &&
371+
dmThreadId == null &&
372+
visibleReplyChain.length === 0 &&
373+
!visibleReplyTarget;
374+
// Existing plain DMs already carry their history through the persistent
375+
// transcript. Keep chat windows for fresh DMs, topics, replies, and groups.
376+
const visiblePromptContext = shouldSuppressPersistedDmChatWindowContext
377+
? promptContext.filter((entry) => !isTelegramChatWindowPromptContext(entry))
378+
: promptContext;
364379
const body = formatInboundEnvelope({
365380
channel: "Telegram",
366381
from: conversationLabel,
@@ -530,7 +545,7 @@ export async function buildTelegramInboundContextPayload(params: {
530545
}
531546
: undefined,
532547
groupSystemPrompt: isGroup || (!isGroup && groupConfig) ? groupSystemPrompt : undefined,
533-
untrustedContext: promptContext.length > 0 ? promptContext : undefined,
548+
untrustedContext: visiblePromptContext.length > 0 ? visiblePromptContext : undefined,
534549
},
535550
contextVisibility: contextVisibilityMode,
536551
extra: {

extensions/telegram/src/bot-message-context.test-harness.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ type BuildTelegramMessageContextForTestParams = {
2222
message: Record<string, unknown>;
2323
me?: Record<string, unknown>;
2424
allMedia?: TelegramMediaRef[];
25+
promptContext?: BuildTelegramMessageContextParams["promptContext"];
2526
options?: BuildTelegramMessageContextParams["options"];
2627
cfg?: Record<string, unknown>;
2728
accountId?: string;
@@ -111,6 +112,7 @@ export async function buildTelegramMessageContextForTest(
111112
me: { id: 7, username: "bot", ...params.me },
112113
} as never,
113114
allMedia: params.allMedia ?? [],
115+
promptContext: params.promptContext ?? [],
114116
storeAllowFrom: [],
115117
options: params.options ?? {},
116118
bot: {

0 commit comments

Comments
 (0)