fix: Add IsForum check so only forum topic threads get session isolation, not regular group reply threads
This commit is contained in:
parent
3f1e89da7f
commit
320fcd1f02
2 changed files with 55 additions and 14 deletions
|
|
@ -520,14 +520,13 @@ func (c *TelegramChannel) handleMessage(ctx context.Context, message *telego.Mes
|
||||||
content = cleaned
|
content = cleaned
|
||||||
}
|
}
|
||||||
|
|
||||||
// Build composite chatID. For forum topics, embed the thread ID in the
|
// For forum topics, embed the thread ID as "chatID/threadID" so replies
|
||||||
// chatID as "chatID/threadID" (same pattern as Slack's "channelID/threadTS")
|
// route to the correct topic and each topic gets its own session.
|
||||||
// so all outbound methods route replies to the correct topic.
|
// Only forum groups (IsForum) are handled; regular group reply threads
|
||||||
// Note: Telegram's "General" topic uses thread ID 1; it is treated like any
|
// must share one session per group.
|
||||||
// other topic for session isolation and agent binding.
|
|
||||||
compositeChatID := fmt.Sprintf("%d", chatID)
|
compositeChatID := fmt.Sprintf("%d", chatID)
|
||||||
threadID := message.MessageThreadID
|
threadID := message.MessageThreadID
|
||||||
if threadID != 0 {
|
if message.Chat.IsForum && threadID != 0 {
|
||||||
compositeChatID = fmt.Sprintf("%d/%d", chatID, threadID)
|
compositeChatID = fmt.Sprintf("%d/%d", chatID, threadID)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -555,9 +554,8 @@ func (c *TelegramChannel) handleMessage(ctx context.Context, message *telego.Mes
|
||||||
"is_group": fmt.Sprintf("%t", message.Chat.Type != "private"),
|
"is_group": fmt.Sprintf("%t", message.Chat.Type != "private"),
|
||||||
}
|
}
|
||||||
|
|
||||||
// For forum topic messages, set parent_peer metadata so the routing system
|
// Set parent_peer metadata for per-topic agent binding.
|
||||||
// can isolate sessions per topic via the existing 7-level priority cascade.
|
if message.Chat.IsForum && threadID != 0 {
|
||||||
if threadID != 0 {
|
|
||||||
metadata["parent_peer_kind"] = "topic"
|
metadata["parent_peer_kind"] = "topic"
|
||||||
metadata["parent_peer_id"] = fmt.Sprintf("%d", threadID)
|
metadata["parent_peer_id"] = fmt.Sprintf("%d", threadID)
|
||||||
}
|
}
|
||||||
|
|
@ -614,11 +612,8 @@ func (c *TelegramChannel) downloadFile(ctx context.Context, fileID, ext string)
|
||||||
return c.downloadFileWithInfo(file, ext)
|
return c.downloadFileWithInfo(file, ext)
|
||||||
}
|
}
|
||||||
|
|
||||||
// parseTelegramChatID splits a composite chat ID "chatID/threadID" into its
|
// parseTelegramChatID splits "chatID/threadID" into its components.
|
||||||
// components. For non-forum messages the threadID is 0. If the chatID string
|
// Returns threadID=0 when no "/" is present (non-forum messages).
|
||||||
// contains a "/" segment, the second part must be a valid integer thread ID;
|
|
||||||
// otherwise an error is returned. This mirrors the Slack adapter (channelID/threadTS).
|
|
||||||
// Implemented with strings.Index to avoid allocating a slice in the hot path.
|
|
||||||
func parseTelegramChatID(chatID string) (int64, int, error) {
|
func parseTelegramChatID(chatID string) (int64, int, error) {
|
||||||
idx := strings.Index(chatID, "/")
|
idx := strings.Index(chatID, "/")
|
||||||
if idx == -1 {
|
if idx == -1 {
|
||||||
|
|
|
||||||
|
|
@ -414,3 +414,49 @@ func TestHandleMessage_NoForum_NoThreadMetadata(t *testing.T) {
|
||||||
assert.Empty(t, inbound.Metadata["parent_peer_kind"])
|
assert.Empty(t, inbound.Metadata["parent_peer_kind"])
|
||||||
assert.Empty(t, inbound.Metadata["parent_peer_id"])
|
assert.Empty(t, inbound.Metadata["parent_peer_id"])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestHandleMessage_ReplyThread_NonForum_NoIsolation(t *testing.T) {
|
||||||
|
messageBus := bus.NewMessageBus()
|
||||||
|
ch := &TelegramChannel{
|
||||||
|
BaseChannel: channels.NewBaseChannel("telegram", nil, messageBus, nil),
|
||||||
|
chatIDs: make(map[string]int64),
|
||||||
|
ctx: context.Background(),
|
||||||
|
}
|
||||||
|
|
||||||
|
// In regular groups, reply threads set MessageThreadID to the original
|
||||||
|
// message ID. This should NOT trigger per-thread session isolation.
|
||||||
|
msg := &telego.Message{
|
||||||
|
Text: "reply in thread",
|
||||||
|
MessageID: 20,
|
||||||
|
MessageThreadID: 15,
|
||||||
|
Chat: telego.Chat{
|
||||||
|
ID: -100999,
|
||||||
|
Type: "supergroup",
|
||||||
|
IsForum: false,
|
||||||
|
},
|
||||||
|
From: &telego.User{
|
||||||
|
ID: 9,
|
||||||
|
FirstName: "Carol",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
err := ch.handleMessage(context.Background(), msg)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), time.Second)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
inbound, ok := messageBus.ConsumeInbound(ctx)
|
||||||
|
require.True(t, ok)
|
||||||
|
|
||||||
|
// chatID should NOT include thread suffix for non-forum groups
|
||||||
|
assert.Equal(t, "-100999", inbound.ChatID)
|
||||||
|
|
||||||
|
// Peer ID should be raw chat ID (shared session for whole group)
|
||||||
|
assert.Equal(t, "group", inbound.Peer.Kind)
|
||||||
|
assert.Equal(t, "-100999", inbound.Peer.ID)
|
||||||
|
|
||||||
|
// No parent peer metadata
|
||||||
|
assert.Empty(t, inbound.Metadata["parent_peer_kind"])
|
||||||
|
assert.Empty(t, inbound.Metadata["parent_peer_id"])
|
||||||
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue