diff --git a/pkg/channels/telegram/telegram.go b/pkg/channels/telegram/telegram.go index cebebfed6..b490c1ad3 100644 --- a/pkg/channels/telegram/telegram.go +++ b/pkg/channels/telegram/telegram.go @@ -203,7 +203,7 @@ func (c *TelegramChannel) Send(ctx context.Context, msg bus.OutboundMessage) ([] if isToolFeedback { toolFeedbackContent = fitToolFeedbackForTelegram(msg.Content, useMarkdownV2, 4096) } - trackedChatID := telegramToolFeedbackChatKey(msg.ChatID, &msg.Context) + trackedChatID := telegramToolFeedbackTrackerKey(msg) if isToolFeedback { if msgID, handled, err := c.progress.Update(ctx, trackedChatID, toolFeedbackContent); handled { if err != nil { @@ -411,6 +411,7 @@ func (c *TelegramChannel) StartTyping(ctx context.Context, chatID string) (func( // EditMessage implements channels.MessageEditor. func (c *TelegramChannel) EditMessage(ctx context.Context, chatID string, messageID string, content string) error { useMarkdownV2 := c.tgCfg.UseMarkdownV2 + chatID = telegramToolFeedbackDeliveryChatID(chatID) cid, _, err := parseTelegramChatID(chatID) if err != nil { return err @@ -488,6 +489,23 @@ func outboundMessageIsToolFeedback(msg bus.OutboundMessage) bool { return strings.EqualFold(strings.TrimSpace(msg.Context.Raw["message_kind"]), "tool_feedback") } +func telegramToolFeedbackTrackerKey(msg bus.OutboundMessage) string { + key := telegramToolFeedbackChatKey(msg.ChatID, &msg.Context) + sessionKey := strings.TrimSpace(msg.SessionKey) + if key == "" || sessionKey == "" { + return key + } + return key + "#session:" + sessionKey +} + +func telegramToolFeedbackDeliveryChatID(chatID string) string { + chatID = strings.TrimSpace(chatID) + if idx := strings.Index(chatID, "#session:"); idx >= 0 { + return strings.TrimSpace(chatID[:idx]) + } + return chatID +} + func (c *TelegramChannel) currentToolFeedbackMessage(chatID string) (string, bool) { if c.progress == nil { return "", false @@ -529,7 +547,7 @@ func (c *TelegramChannel) dismissTrackedToolFeedbackMessage(ctx context.Context, return } c.ClearToolFeedbackMessage(chatID) - _ = c.DeleteMessage(ctx, chatID, messageID) + _ = c.DeleteMessage(ctx, telegramToolFeedbackDeliveryChatID(chatID), messageID) } func (c *TelegramChannel) finalizeTrackedToolFeedbackMessage( @@ -542,7 +560,7 @@ func (c *TelegramChannel) finalizeTrackedToolFeedbackMessage( if !ok || editFn == nil { return nil, false } - if err := editFn(ctx, chatID, msgID, content); err != nil { + if err := editFn(ctx, telegramToolFeedbackDeliveryChatID(chatID), msgID, content); err != nil { c.RecordToolFeedbackMessage(chatID, msgID, baseContent) return nil, false } diff --git a/pkg/channels/telegram/telegram_test.go b/pkg/channels/telegram/telegram_test.go index 92c714ced..949e3074c 100644 --- a/pkg/channels/telegram/telegram_test.go +++ b/pkg/channels/telegram/telegram_test.go @@ -374,6 +374,53 @@ func TestSend_TopicReplyDoesNotFinalizeDifferentTopicToolFeedback(t *testing.T) assert.True(t, ok, "tool feedback in the original topic should remain tracked") } +func TestSend_FinalReplyDoesNotFinalizeDifferentSessionToolFeedback(t *testing.T) { + nextMessageID := 0 + caller := &stubCaller{ + callFn: func(ctx context.Context, url string, data *ta.RequestData) (*ta.Response, error) { + nextMessageID++ + return successResponseWithMessageID(t, nextMessageID), nil + }, + } + ch := newTestChannel(t, caller) + + baseCtx := bus.InboundContext{ + Channel: "telegram", + ChatID: "-1001234567890", + TopicID: "42", + } + _, err := ch.Send(context.Background(), bus.OutboundMessage{ + ChatID: "-1001234567890", + SessionKey: "subturn-1", + Content: "Working...\n• tool: `read_file`", + Context: bus.InboundContext{ + Channel: "telegram", + ChatID: "-1001234567890", + TopicID: "42", + Raw: map[string]string{ + "message_kind": "tool_feedback", + }, + }, + }) + require.NoError(t, err) + + ids, err := ch.Send(context.Background(), bus.OutboundMessage{ + ChatID: "-1001234567890", + SessionKey: "main-session", + Content: "test", + Context: baseCtx, + }) + require.NoError(t, err) + require.Len(t, caller.calls, 2) + assert.Equal(t, []string{"2"}, ids) + assert.Contains(t, caller.calls[1].URL, "sendMessage") + assert.NotContains(t, caller.calls[1].URL, "editMessageText") + + msgID, ok := ch.currentToolFeedbackMessage("-1001234567890/42#session:subturn-1") + require.True(t, ok, "subturn tool feedback should remain tracked") + assert.Equal(t, "1", msgID) +} + func TestFinalizeTrackedToolFeedbackMessage_StopsTrackingBeforeEdit(t *testing.T) { ch := newTestChannel(t, &stubCaller{ callFn: func(context.Context, string, *ta.RequestData) (*ta.Response, error) {