fix(telegram): combine voice payload with preceding streamed text#81131
fix(telegram): combine voice payload with preceding streamed text#81131sdimantsd wants to merge 3 commits into
Conversation
When a final voice payload arrives after partial/block streaming already sent the text, delete the streamed text message and attach the spoken text as caption so voice+text arrive as one combined message. This restores pre-bca16d0f00 behavior where audioAsVoice payloads implicitly carried the spoken text and the streamed text was suppressed. Note: audioAsVoice payloads don't set mediaUrls (reply.hasMedia=false) and in partial streaming mode the payload carries text (reply.trimmedText non-empty), so we key only on audioAsVoice + hasStreamedMessage.
|
Thanks for the context here. I swept through the related work, and this is now duplicate or superseded. Close this PR as superseded: the remaining Telegram final-mode TTS behavior is now tracked by the canonical issue and broader replacement PR, while this one-file Telegram transport patch is the riskier delete-and-resend path. Canonical path: Close this branch and continue the captioned final-mode TTS decision and implementation review in #83511 and #83988. So I’m closing this here and keeping the remaining discussion on #83511 and #83988. Review detailsBest possible solution: Close this branch and continue the captioned final-mode TTS decision and implementation review in #83511 and #83988. Do we have a high-confidence way to reproduce the issue? Yes. Source and tests on current main show late media after streamed Telegram text is delivered as separate media while the streamed text remains, and the PR screenshot shows the proposed combined voice-caption result; I did not run a fresh live Telegram session in this read-only review. Is this the best way to solve the issue? No. This is not the best fix because it deletes an already-delivered Telegram message in the transport layer; the safer canonical path is the upstream capability-gated final-TTS deferral under review. Security review: Security review cleared: No concrete security or supply-chain concern was found; the diff only changes Telegram runtime delivery behavior. AGENTS.md: found and applied where relevant. What I checked:
Likely related people:
Codex review notes: model internal, reasoning high; reviewed against fef839407995. |
|
This pull request has been automatically marked as stale due to inactivity. |
|
ClawSweeper PR egg: 🔥 warming; proof passed, review follow-up or readiness checks remain. Hatch with Rules and detailsHatchability:
About:
|
|
Thanks @sdimantsd for the focused Telegram proof and patch in #81131. I am closing this as superseded by #83988 because the remaining final-mode TTS churn is now being handled in the broader canonical path for #83511. Clownfish is keeping the canonical PR open there so implementation review, validation, and follow-up stay in one place. The source PR context and attribution are preserved in the cluster record, and your Telegram repro/proof remains useful for the canonical review. If this branch covers a different reproduction path or the issue still reproduces after #83988 lands, please reply and we can reopen or split it back out. |
Summary
When a final voice payload arrives in Telegram after partial/block streaming has already sent the text, two separate Telegram messages appear: first the streamed text, then the voice note. This restores the pre-bca16d0f00 behavior where audioAsVoice payloads implicitly carried the spoken text as the voice's caption, so voice+text arrive as one combined message.
Behavior change
Before: User receives 1) streamed text message 2) separate voice note (two messages).
After: User receives a single voice note with the spoken text as caption.
Real behavior proof
After-fix screenshot (Telegram chat, agent reply with TTS):
The single message in the screenshot is the voice note with the spoken text bound to it as caption.
Running on openclaw:local built from this branch on top of 2026.5.12-beta.1. Patch identifiable at runtime in /app/dist/bot-DiUeqsFG.js (grep -c voice-combine returns 2).
Mechanism
When info.kind === final, effectivePayload.audioAsVoice === true, and answerLane.hasStreamedMessage:
The keying on audioAsVoice + hasStreamedMessage is necessary because audioAsVoice payloads don't set mediaUrls (so reply.hasMedia=false), and in partial-streaming mode the payload carries text (so reply.trimmedText is non-empty).
Test plan