Telegram Draft Streaming Fix
Problem
The current Telegram draft streaming implementation uses sendMessageDraft API, which returns TEXTDRAFT_PEER_INVALID error for regular bots in private chats.
{"ok":false,"error_code":400,"description":"Bad Request: TEXTDRAFT_PEER_INVALID"}
Additionally, the canStreamDraft condition in bot-message-dispatch.js requires:
typeof resolvedThreadId === "number" - but private chats return undefined
resolveBotTopicsEnabled(primaryCtx) - but most bots don't have topics enabled
This causes streaming to be disabled for most private chat scenarios.
Solution
1. Replace sendMessageDraft with sendMessage + editMessageText
Instead of using the unreliable sendMessageDraft API, use the standard approach:
- Send initial message with
sendMessage
- Update content progressively with
editMessageText
- Add a cursor indicator
▌ during streaming
- Remove cursor on final update
2. Simplify canStreamDraft condition
Change from:
const canStreamDraft = streamMode !== "off" &&
isPrivateChat &&
typeof resolvedThreadId === "number" &&
(await resolveBotTopicsEnabled(primaryCtx));
To:
const canStreamDraft = streamMode !== "off" &&
isPrivateChat;
3. Pass final text to stop()
Ensure the complete final text is passed to stop() to prevent truncation:
await draftStream?.stop(payload.text);
Files Changed
dist/telegram/draft-stream.js
const TELEGRAM_DRAFT_MAX_CHARS = 4096;
const DEFAULT_THROTTLE_MS = 400;
export function createTelegramDraftStream(params) {
const maxChars = Math.min(params.maxChars ?? TELEGRAM_DRAFT_MAX_CHARS, TELEGRAM_DRAFT_MAX_CHARS);
const throttleMs = Math.max(100, params.throttleMs ?? DEFAULT_THROTTLE_MS);
const chatId = params.chatId;
const threadParams = typeof params.messageThreadId === "number"
? { message_thread_id: Math.trunc(params.messageThreadId) }
: undefined;
let lastSentText = "";
let lastSentAt = 0;
let pendingText = "";
let inFlight = false;
let timer;
let stopped = false;
let messageId = null;
let initialSent = false;
let fullText = "";
const sendOrEdit = async (text, isFinal = false) => {
if (stopped && !isFinal)
return;
const trimmed = text.trimEnd();
if (!trimmed)
return;
const displayText = trimmed.length > maxChars
? trimmed.slice(0, maxChars - 3) + "..."
: trimmed;
const textWithCursor = isFinal ? displayText : displayText + " ▌";
if (textWithCursor === lastSentText && !isFinal)
return;
lastSentText = textWithCursor;
lastSentAt = Date.now();
try {
if (!initialSent) {
const result = await params.api.sendMessage(chatId, textWithCursor, {
...threadParams,
});
messageId = result.message_id;
initialSent = true;
} else if (messageId) {
await params.api.editMessageText(chatId, messageId, textWithCursor);
}
}
catch (err) {
const errMsg = err instanceof Error ? err.message : String(err);
if (errMsg.includes("message is not modified")) {
return;
}
params.warn?.(`telegram edit stream error: ${errMsg}`);
}
};
// ... flush, schedule, update functions remain similar ...
const stop = async (finalText) => {
stopped = true;
if (timer) {
clearTimeout(timer);
timer = undefined;
}
const textToFinalize = finalText || fullText || pendingText || lastSentText;
pendingText = "";
while (inFlight) {
await new Promise(resolve => setTimeout(resolve, 50));
}
if (messageId && textToFinalize) {
try {
const cleanText = textToFinalize.replace(/ ▌$/, "");
await sendOrEdit(cleanText, true);
} catch (err) {
params.warn?.(`telegram edit stream: failed to finalize: ${err}`);
}
}
};
const getMessageId = () => messageId;
return { update, flush, stop, getMessageId };
}
dist/telegram/bot-message-dispatch.js
- const canStreamDraft = streamMode !== "off" &&
- isPrivateChat &&
- typeof resolvedThreadId === "number" &&
- (await resolveBotTopicsEnabled(primaryCtx));
+ const canStreamDraft = streamMode !== "off" &&
+ isPrivateChat;
if (streamedMessageId) {
- await draftStream?.stop();
+ await draftStream?.stop(payload.text);
deliveryState.delivered = true;
return;
}
Testing
Tested with Telegram bot in private chat:
- ✅ Streaming displays progressively with cursor
- ✅ Final message contains complete content
- ✅ No duplicate messages sent
Environment
- OpenClaw version: 2026.1.29
- Node.js: v22.22.0
- Telegram Bot API
Telegram Draft Streaming Fix
Problem
The current Telegram draft streaming implementation uses
sendMessageDraftAPI, which returnsTEXTDRAFT_PEER_INVALIDerror for regular bots in private chats.Additionally, the
canStreamDraftcondition inbot-message-dispatch.jsrequires:typeof resolvedThreadId === "number"- but private chats returnundefinedresolveBotTopicsEnabled(primaryCtx)- but most bots don't have topics enabledThis causes streaming to be disabled for most private chat scenarios.
Solution
1. Replace
sendMessageDraftwithsendMessage+editMessageTextInstead of using the unreliable
sendMessageDraftAPI, use the standard approach:sendMessageeditMessageText▌during streaming2. Simplify
canStreamDraftconditionChange from:
To:
3. Pass final text to
stop()Ensure the complete final text is passed to
stop()to prevent truncation:Files Changed
dist/telegram/draft-stream.jsdist/telegram/bot-message-dispatch.jsif (streamedMessageId) { - await draftStream?.stop(); + await draftStream?.stop(payload.text); deliveryState.delivered = true; return; }Testing
Tested with Telegram bot in private chat:
Environment