fix(telegram): dedupe visible assistant prompt context

This commit is contained in:
momothemage
2026-07-06 11:06:53 +08:00
parent 2b48e98148
commit 8a3b4c2549
2 changed files with 106 additions and 4 deletions
@@ -48,6 +48,7 @@ import {
resolveAmbientTranscriptWatermarkKey,
} from "openclaw/plugin-sdk/session-store-runtime";
import { normalizeStringEntries } from "openclaw/plugin-sdk/string-coerce-runtime";
import { stripInlineDirectiveTagsForDelivery } from "openclaw/plugin-sdk/text-chunking";
import { expandTelegramAllowFromWithAccessGroups } from "./access-groups.js";
import { resolveTelegramAccount, resolveTelegramMediaRuntimeOptions } from "./accounts.js";
import { withTelegramApiErrorLogging } from "./api-logging.js";
@@ -187,13 +188,17 @@ type TelegramPromptContextMessageForDedupe = {
function resolvePromptContextTextDedupeKey(
message: TelegramPromptContextMessageForDedupe,
): string | undefined {
if (typeof message.body !== "string" || !message.body.trim()) {
if (typeof message.body !== "string") {
return undefined;
}
const visibleBody = stripInlineDirectiveTagsForDelivery(message.body).text.trim();
if (!visibleBody) {
return undefined;
}
if (typeof message.timestamp_ms !== "number" || !Number.isFinite(message.timestamp_ms)) {
return undefined;
}
return `${message.timestamp_ms}:${message.body.trim()}`;
return `${message.timestamp_ms}:${visibleBody}`;
}
export const registerTelegramHandlers = ({
+99 -2
View File
@@ -153,6 +153,7 @@ async function writeDirectTelegramTranscriptContext(params: {
cfg: OpenClawConfig;
storePath: string;
chatId: number;
role?: "assistant" | "user";
senderId: number;
sessionId: string;
text: string;
@@ -188,10 +189,10 @@ async function writeDirectTelegramTranscriptContext(params: {
[
JSON.stringify({ type: "session", id: params.sessionId }),
JSON.stringify({
id: "transcript-user-1",
id: params.role === "assistant" ? "transcript-assistant-1" : "transcript-user-1",
type: "message",
message: {
role: "user",
role: params.role ?? "user",
content: params.text,
timestamp: params.timestamp,
},
@@ -2429,6 +2430,102 @@ describe("createTelegramBot", () => {
}
});
it("dedupes direct assistant transcript context against cached Telegram replies after stripping directives", async () => {
onSpy.mockClear();
replySpy.mockClear();
const storePath = `/tmp/openclaw-telegram-dm-visible-dedupe-${process.pid}-${Date.now()}.json`;
const config = {
channels: {
telegram: {
dmPolicy: "open",
allowFrom: ["*"],
},
},
session: {
store: storePath,
},
} satisfies NonNullable<Parameters<typeof createTelegramBot>[0]["config"]>;
await rm(storePath, { force: true });
await rm(`${storePath}.telegram-messages.json`, { force: true });
try {
loadConfig.mockReturnValue(config);
createTelegramBot({ token: "tok", config });
const handler = getOnHandler("message") as (ctx: Record<string, unknown>) => Promise<void>;
const baseCtx = {
me: { id: 999, username: "openclaw_bot" },
getFile: async () => ({ download: async () => new Uint8Array() }),
};
const chatId = 7773;
const senderId = 202;
const visibleReply = "Yep - I'm here now.";
const replyTimestampMs = 1_778_474_700_000;
await writeDirectTelegramTranscriptContext({
cfg: config,
storePath,
chatId,
role: "assistant",
senderId,
sessionId: "telegram-dm-assistant-visible-dedupe-session",
text: `[[reply_to_current]]${visibleReply}`,
timestamp: replyTimestampMs,
});
await handler({
...baseCtx,
message: {
chat: { id: chatId, type: "private" },
text: "still there?",
date: 1_778_474_850,
message_id: 741,
from: { id: senderId, is_bot: false, first_name: "Kesava" },
reply_to_message: {
chat: { id: chatId, type: "private" },
date: Math.floor(replyTimestampMs / 1000),
from: { id: 999, is_bot: true, first_name: "OpenClaw" },
message_id: 736,
text: visibleReply,
},
},
});
expect(replySpy).toHaveBeenCalledTimes(1);
const payload = mockMsgContextArg(
replySpy as unknown as MockCallSource,
0,
0,
"replySpy call",
);
const [conversationContext] = requireArray(
payload.UntrustedStructuredContext,
"structured context",
);
const contextRecord = requireRecord(conversationContext, "conversation context");
const contextPayload = requireRecord(contextRecord.payload, "conversation context payload");
const messages = requireArray(contextPayload.messages, "conversation context messages").map(
(message, index) => requireRecord(message, `conversation context message ${index + 1}`),
);
expect(messages).toEqual([
expect.objectContaining({
body: visibleReply,
is_reply_target: true,
message_id: "736",
sender: "OpenClaw",
}),
]);
expect(messages.filter((message) => message.body === visibleReply)).toHaveLength(1);
expect(JSON.stringify(messages)).not.toContain("[[reply_to_current]]");
expect(messages.some((message) => String(message.message_id).startsWith("session:"))).toBe(
false,
);
} finally {
await rm(storePath, { force: true });
await rm(`${storePath}.telegram-messages.json`, { force: true });
}
});
it("skips direct transcript context for hard reset messages", async () => {
onSpy.mockClear();
replySpy.mockClear();