diff --git a/extensions/telegram/src/bot-message-dispatch.test.ts b/extensions/telegram/src/bot-message-dispatch.test.ts index 6bc4ee58c441..302e5fbb21fd 100644 --- a/extensions/telegram/src/bot-message-dispatch.test.ts +++ b/extensions/telegram/src/bot-message-dispatch.test.ts @@ -1884,6 +1884,38 @@ describe("dispatchTelegramMessage draft streaming", () => { expect(deliverReplies).not.toHaveBeenCalled(); }); + it("marks directive-tagged durable finals with the transcript prompt-context timestamp", async () => { + const transcriptTimestamp = Date.now() + 1_000; + const context = createContext(); + context.ctxPayload.SessionKey = "agent:default:telegram:direct:123"; + mockDefaultSessionEntry(); + readLatestAssistantTextByIdentity.mockResolvedValue({ + text: "[[reply_to_current]]Final answer", + timestamp: transcriptTimestamp, + }); + deliverInboundReplyWithMessageSendContext.mockResolvedValue({ + status: "handled_visible", + delivery: { + messageIds: ["2001"], + visibleReplySent: true, + }, + }); + dispatchReplyWithBufferedBlockDispatcher.mockImplementation(async ({ dispatcherOptions }) => { + await dispatcherOptions.deliver({ text: "Final answer" }, { kind: "final" }); + return { queuedFinal: true }; + }); + + await dispatchWithContext({ context, streamMode: "off" }); + + const outbound = expectRecordFields(mockCallArg(deliverInboundReplyWithMessageSendContext), { + payload: expect.objectContaining({ text: "Final answer" }), + }); + expectRecordFields(expectRecordFields(outbound.payload, {}).channelData, { + telegram: { promptContextTimestampMs: transcriptTimestamp }, + }); + expect(deliverReplies).not.toHaveBeenCalled(); + }); + it("keeps the Telegram edit cap for non-block previews regardless of chunk config", async () => { const draftStream = createDraftStream(); createTelegramDraftStream.mockReturnValue(draftStream); diff --git a/extensions/telegram/src/bot-message-dispatch.ts b/extensions/telegram/src/bot-message-dispatch.ts index e707d7229ac2..d34159ef1cec 100644 --- a/extensions/telegram/src/bot-message-dispatch.ts +++ b/extensions/telegram/src/bot-message-dispatch.ts @@ -54,6 +54,7 @@ import { appendAssistantMirrorMessageByIdentity, readLatestAssistantTextByIdentity, } from "openclaw/plugin-sdk/session-transcript-runtime"; +import { stripInlineDirectiveTagsForDelivery } from "openclaw/plugin-sdk/text-chunking"; import { resolveTelegramConfigReasoningDefault } from "./agent-config.js"; import { withTelegramApiErrorLogging } from "./api-logging.js"; import type { TelegramBotDeps } from "./bot-deps.js"; @@ -1580,9 +1581,14 @@ export const dispatchTelegramMessage = async ({ }; const resolveCurrentTurnTranscriptFinalText = async (): Promise => (await resolveCurrentTurnTranscriptFinal())?.text; + const normalizePromptContextTimestampText = (text: string): string => + stripInlineDirectiveTagsForDelivery(text).text.trim(); const resolvePromptContextTimestampMs = async (text: string): Promise => { const final = await resolveCurrentTurnTranscriptFinal(); - if (final?.text.trim() !== text.trim()) { + if ( + !final || + normalizePromptContextTimestampText(final.text) !== normalizePromptContextTimestampText(text) + ) { return undefined; } return final.timestamp; diff --git a/extensions/telegram/src/bot.test.ts b/extensions/telegram/src/bot.test.ts index bb333734ea8c..5a21821c237e 100644 --- a/extensions/telegram/src/bot.test.ts +++ b/extensions/telegram/src/bot.test.ts @@ -2461,6 +2461,7 @@ describe("createTelegramBot", () => { const senderId = 202; const visibleReply = "Yep - I'm here now."; const replyTimestampMs = 1_778_474_700_000; + const telegramReplyDate = Math.floor((replyTimestampMs + 5_000) / 1000); await writeDirectTelegramTranscriptContext({ cfg: config, @@ -2482,9 +2483,10 @@ describe("createTelegramBot", () => { from: { id: senderId, is_bot: false, first_name: "Kesava" }, reply_to_message: { chat: { id: chatId, type: "private" }, - date: Math.floor(replyTimestampMs / 1000), + date: telegramReplyDate, from: { id: 999, is_bot: true, first_name: "OpenClaw" }, message_id: 736, + openclaw_prompt_context_timestamp_ms: replyTimestampMs, text: visibleReply, }, },