diff --git a/CHANGELOG.md b/CHANGELOG.md index 0f1966a5e82..222f10196c0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ Docs: https://docs.openclaw.ai - Voice Call/realtime: add opt-in OpenClaw agent voice context capsules and consult-cadence guidance so Gemini/OpenAI realtime calls can sound like the configured agent without consulting the full agent on every ordinary turn. Thanks @scoootscooob. - Docker/Gateway: harden the gateway container by dropping `NET_RAW` and `NET_ADMIN` capabilities and enabling `no-new-privileges` in the bundled `docker-compose.yml`. Thanks @VintageAyu. - Telegram: accept plugin-owned numeric forum-topic targets in the agent message tool and keep reply-dispatch provider chunks behind a real stable runtime alias during in-place package updates. Fixes #77137. Thanks @richardmqq. +- Telegram/streaming: keep draft preview rotation from reusing a pre-tool assistant preview after visible tool or media output lands between compaction replay and the next assistant message. Thanks @vincentkoc. - Channels/WhatsApp: support explicit WhatsApp Channel/Newsletter `@newsletter` outbound message targets with channel session metadata instead of DM routing. Fixes #13417; carries forward the narrow outbound target idea from #13424. Thanks @vincentkoc and @agentz-manfred. - TTS/telephony: honor provider voice/model overrides in telephony synthesis providers so Google Meet agent speech logs match the backend that actually produced the audio. Thanks @vincentkoc. - Voice Call/realtime: bound the paced Twilio audio queue and close overloaded realtime streams before provider audio can pile up behind the websocket backpressure guard. Thanks @vincentkoc. diff --git a/extensions/telegram/src/bot-message-dispatch.ts b/extensions/telegram/src/bot-message-dispatch.ts index 77384cba52d..3bdc762aee8 100644 --- a/extensions/telegram/src/bot-message-dispatch.ts +++ b/extensions/telegram/src/bot-message-dispatch.ts @@ -555,6 +555,7 @@ export const dispatchTelegramMessage = async ({ let splitReasoningOnNextStream = false; let skipNextAnswerMessageStartRotation = false; let pendingCompactionReplayBoundary = false; + let discardAnswerPreviewOnNextRotation = false; let draftLaneEventQueue = Promise.resolve(); const reasoningStepState = createTelegramReasoningStepState(); const enqueueDraftLaneEvent = (task: () => Promise): Promise => { @@ -600,6 +601,7 @@ export const dispatchTelegramMessage = async ({ const materializedId = await answerLane.stream?.materialize?.(); const previewMessageId = materializedId ?? answerLane.stream?.messageId(); if ( + !discardAnswerPreviewOnNextRotation && typeof previewMessageId === "number" && activePreviewLifecycleByLane.answer === "transient" ) { @@ -613,6 +615,7 @@ export const dispatchTelegramMessage = async ({ answerLane.stream?.forceNewMessage(); didForceNewMessage = true; } + discardAnswerPreviewOnNextRotation = false; resetDraftLaneState(answerLane); answerLaneHasAssistantContent = false; if (didForceNewMessage) { @@ -967,11 +970,12 @@ export const dispatchTelegramMessage = async ({ if (isDispatchSuperseded()) { return; } - const clearPendingCompactionReplayBoundaryOnVisibleBoundary = ( - didDeliver: boolean, - ) => { + const markVisibleNonPreviewBoundary = (didDeliver: boolean) => { if (didDeliver && info.kind !== "final") { pendingCompactionReplayBoundary = false; + if (answerLane.hasStreamedMessage) { + discardAnswerPreviewOnNextRotation = true; + } } }; if (payload.isError === true) { @@ -1047,6 +1051,8 @@ export const dispatchTelegramMessage = async ({ }); if (info.kind === "final") { emitPreviewFinalizedHook(result); + } else if (segment.lane === "answer" && result.kind === "sent") { + markVisibleNonPreviewBoundary(true); } if (segment.lane === "reasoning") { if (result.kind !== "skipped") { @@ -1069,7 +1075,7 @@ export const dispatchTelegramMessage = async ({ if (reply.hasMedia) { const payloadWithoutSuppressedReasoning = typeof payload.text === "string" ? { ...payload, text: "" } : payload; - clearPendingCompactionReplayBoundaryOnVisibleBoundary( + markVisibleNonPreviewBoundary( await sendPayload(payloadWithoutSuppressedReasoning), ); } @@ -1093,9 +1099,7 @@ export const dispatchTelegramMessage = async ({ } return; } - clearPendingCompactionReplayBoundaryOnVisibleBoundary( - await sendPayload(payload), - ); + markVisibleNonPreviewBoundary(await sendPayload(payload)); if (info.kind === "final") { await flushBufferedFinalAnswer(); pendingCompactionReplayBoundary = false;