mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-10 04:52:43 +00:00
fix(telegram): prevent duplicate messages in DM draft streaming mode (#32118)
* fix(telegram): prevent duplicate messages in DM draft streaming mode When using sendMessageDraft for DM streaming (streaming: 'partial'), the draft bubble auto-converts to the final message. The code was incorrectly falling through to sendPayload() after the draft was finalized, causing a duplicate message. This fix checks if we're in draft preview mode with hasStreamedMessage and skips the sendPayload call, returning "preview-finalized" directly. Key changes: - Use hasStreamedMessage flag instead of previewRevision comparison - Avoids double stopDraftLane calls by returning early - Prevents duplicate messages when final text equals last streamed text Root cause: In lane-delivery.ts, the final message handling logic did not properly handle the DM draft flow where sendMessageDraft creates a transient bubble that doesn't need a separate final send. * fix(telegram): harden DM draft finalization path * fix(telegram): require emitted draft preview for unchanged finals * fix(telegram): require final draft text emission before finalize * fix: update changelog for telegram draft finalization (#32118) (thanks @OpenCils) --------- Co-authored-by: Ayaan Zaidi <zaidi@uplause.io>
This commit is contained in:
@@ -8,6 +8,7 @@ export type DraftLaneState = {
|
||||
stream: TelegramDraftStream | undefined;
|
||||
lastPartialText: string;
|
||||
hasStreamedMessage: boolean;
|
||||
previewRevisionBaseline: number;
|
||||
};
|
||||
|
||||
export type ArchivedPreview = {
|
||||
@@ -328,6 +329,43 @@ export function createLaneTextDeliverer(params: CreateLaneTextDelivererParams) {
|
||||
!hasMedia && text.length > 0 && text.length <= params.draftMaxChars && !payload.isError;
|
||||
|
||||
if (infoKind === "final") {
|
||||
const hasPreviewButtons = Boolean(previewButtons?.some((row) => row.length > 0));
|
||||
const canFinalizeDraftPreviewDirectly =
|
||||
isDraftPreviewLane(lane) &&
|
||||
lane.hasStreamedMessage &&
|
||||
canEditViaPreview &&
|
||||
!hasPreviewButtons;
|
||||
let draftPreviewStopped = false;
|
||||
if (canFinalizeDraftPreviewDirectly) {
|
||||
const previewRevisionBeforeFlush = lane.stream?.previewRevision?.() ?? 0;
|
||||
const finalTextSnapshot = text.trimEnd();
|
||||
const hasEmittedPreviewInCurrentLane =
|
||||
previewRevisionBeforeFlush > lane.previewRevisionBaseline;
|
||||
const deliveredPreviewTextBeforeFinal = lane.stream?.lastDeliveredText?.() ?? "";
|
||||
const finalTextAlreadyDelivered =
|
||||
deliveredPreviewTextBeforeFinal === finalTextSnapshot && hasEmittedPreviewInCurrentLane;
|
||||
const unchangedFinalText = text === lane.lastPartialText;
|
||||
lane.stream?.update(text);
|
||||
await params.flushDraftLane(lane);
|
||||
await params.stopDraftLane(lane);
|
||||
draftPreviewStopped = true;
|
||||
const previewUpdated = (lane.stream?.previewRevision?.() ?? 0) > previewRevisionBeforeFlush;
|
||||
const deliveredPreviewTextAfterFinal =
|
||||
lane.stream?.lastDeliveredText?.() ?? deliveredPreviewTextBeforeFinal;
|
||||
if (
|
||||
(previewUpdated && deliveredPreviewTextAfterFinal === finalTextSnapshot) ||
|
||||
(unchangedFinalText && finalTextAlreadyDelivered)
|
||||
) {
|
||||
lane.lastPartialText = text;
|
||||
params.finalizedPreviewByLane[laneName] = true;
|
||||
params.markDelivered();
|
||||
return "preview-finalized";
|
||||
}
|
||||
params.log(
|
||||
`telegram: ${laneName} draft final text not emitted; falling back to standard send`,
|
||||
);
|
||||
}
|
||||
|
||||
if (laneName === "answer") {
|
||||
const archivedResult = await consumeArchivedAnswerPreviewForFinal({
|
||||
lane,
|
||||
@@ -340,7 +378,7 @@ export function createLaneTextDeliverer(params: CreateLaneTextDelivererParams) {
|
||||
return archivedResult;
|
||||
}
|
||||
}
|
||||
if (canEditViaPreview && !params.finalizedPreviewByLane[laneName]) {
|
||||
if (canEditViaPreview && !params.finalizedPreviewByLane[laneName] && !draftPreviewStopped) {
|
||||
await params.flushDraftLane(lane);
|
||||
if (laneName === "answer") {
|
||||
const archivedResultAfterFlush = await consumeArchivedAnswerPreviewForFinal({
|
||||
@@ -372,7 +410,9 @@ export function createLaneTextDeliverer(params: CreateLaneTextDelivererParams) {
|
||||
`telegram: preview final too long for edit (${text.length} > ${params.draftMaxChars}); falling back to standard send`,
|
||||
);
|
||||
}
|
||||
await params.stopDraftLane(lane);
|
||||
if (!draftPreviewStopped) {
|
||||
await params.stopDraftLane(lane);
|
||||
}
|
||||
const delivered = await params.sendPayload(params.applyTextToPayload(payload, text));
|
||||
return delivered ? "sent" : "skipped";
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user