fix(telegram): clear draft after materializing DM stream

When using draft_transport in Telegram DMs, the preview draft sometimes
stays in the input area after the real message is delivered, showing a
brief duplicate reply.

Clear the stale draft by sending an empty sendMessageDraft as soon as
the final message is sent in Send(). The cleanup is asynchronous and
best-effort so it won't block message delivery.

Co-authored-by: Badgerbees <rapabelias@gmail.com>
This commit is contained in:
viettranxandBadgerbees committed 2026-03-24 07:51:13 +07:00
1 parent 0bce64032a
commit 6a2949de4a
3 files changed
+24

No files matched your search

+1
View File
@@ -46,6 +46,7 @@ type Channel struct {
pollDone chan struct{} // closed when polling goroutine exits
handlerWg sync.WaitGroup // tracks in-flight handler goroutines for graceful shutdown
handlerSem chan struct{} // bounded semaphore for concurrent handler goroutines
pendingDraftID sync.Map // localKey string → int (draftID)
}
type thinkingCancel struct {
+18
View File
@@ -161,6 +161,24 @@ func (c *Channel) Send(ctx context.Context, msg bus.OutboundMessage) error {
}
}
// Captured draft cleanup: if this message follows a DM stream that used the
// draft transport, we must clear the stale draft as soon as this bubble is sent.
if pID, ok := c.pendingDraftID.LoadAndDelete(localKey); ok {
draftID := pID.(int)
go func() {
params := &telego.SendMessageDraftParams{
ChatID: chatID,
DraftID: draftID,
Text: "",
MessageThreadID: resolveThreadIDForSend(threadID),
}
// Best-effort with Background ctx — caller ctx may already be cancelled.
if err := c.bot.SendMessageDraft(context.Background(), params); err != nil {
slog.Debug("telegram: draft clear failed (cosmetic)", "chat_id", chatID, "draft_id", draftID, "error", err)
}
}()
}
// Placeholder update (e.g. LLM retry notification): edit the placeholder
// but keep it alive for the final response. Don't stop typing or cleanup.
if msg.Metadata["placeholder_update"] == "true" {
+5
View File
@@ -331,6 +331,11 @@ func (c *Channel) FinalizeStream(ctx context.Context, chatID string, stream chan
slog.Warn("stream: initial send landed but ID unknown. Suppressing fallback message to avoid duplicate.", "chat_id", chatID)
}
// Capture draft ID for clearing after the final Send()
if ds, ok := stream.(*DraftStream); ok && ds.UsedDraftTransport() {
c.pendingDraftID.Store(chatID, ds.draftID)
}
// Stop thinking animation
if stop, ok := c.stopThinking.Load(chatID); ok {
if cf, ok := stop.(*thinkingCancel); ok {