refactor(channels): emit inbound context in primary adapters
This commit is contained in:
parent
9cfa3c3ba6
commit
cf11ff70c3
4 changed files with 137 additions and 33 deletions
|
|
@ -251,12 +251,39 @@ func (c *BaseChannel) HandleMessage(
|
||||||
media []string,
|
media []string,
|
||||||
metadata map[string]string,
|
metadata map[string]string,
|
||||||
senderOpts ...bus.SenderInfo,
|
senderOpts ...bus.SenderInfo,
|
||||||
|
) {
|
||||||
|
var sender bus.SenderInfo
|
||||||
|
if len(senderOpts) > 0 {
|
||||||
|
sender = senderOpts[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
inboundCtx := bus.ContextFromLegacyInbound(bus.InboundMessage{
|
||||||
|
Channel: c.name,
|
||||||
|
SenderID: senderID,
|
||||||
|
Sender: sender,
|
||||||
|
ChatID: chatID,
|
||||||
|
Peer: peer,
|
||||||
|
MessageID: messageID,
|
||||||
|
Metadata: metadata,
|
||||||
|
})
|
||||||
|
|
||||||
|
c.HandleMessageWithContext(ctx, peer, chatID, content, media, inboundCtx, senderOpts...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *BaseChannel) HandleMessageWithContext(
|
||||||
|
ctx context.Context,
|
||||||
|
peer bus.Peer,
|
||||||
|
deliveryChatID, content string,
|
||||||
|
media []string,
|
||||||
|
inboundCtx bus.InboundContext,
|
||||||
|
senderOpts ...bus.SenderInfo,
|
||||||
) {
|
) {
|
||||||
// Use SenderInfo-based allow check when available, else fall back to string
|
// Use SenderInfo-based allow check when available, else fall back to string
|
||||||
var sender bus.SenderInfo
|
var sender bus.SenderInfo
|
||||||
if len(senderOpts) > 0 {
|
if len(senderOpts) > 0 {
|
||||||
sender = senderOpts[0]
|
sender = senderOpts[0]
|
||||||
}
|
}
|
||||||
|
senderID := strings.TrimSpace(inboundCtx.SenderID)
|
||||||
if sender.CanonicalID != "" || sender.PlatformID != "" {
|
if sender.CanonicalID != "" || sender.PlatformID != "" {
|
||||||
if !c.IsAllowedSender(sender) {
|
if !c.IsAllowedSender(sender) {
|
||||||
return
|
return
|
||||||
|
|
@ -273,21 +300,33 @@ func (c *BaseChannel) HandleMessage(
|
||||||
resolvedSenderID = sender.CanonicalID
|
resolvedSenderID = sender.CanonicalID
|
||||||
}
|
}
|
||||||
|
|
||||||
scope := BuildMediaScope(c.name, chatID, messageID)
|
if resolvedSenderID == "" {
|
||||||
|
resolvedSenderID = senderID
|
||||||
|
}
|
||||||
|
|
||||||
|
inboundCtx.Channel = c.name
|
||||||
|
if inboundCtx.ChatID == "" {
|
||||||
|
inboundCtx.ChatID = deliveryChatID
|
||||||
|
}
|
||||||
|
if inboundCtx.SenderID == "" {
|
||||||
|
inboundCtx.SenderID = resolvedSenderID
|
||||||
|
}
|
||||||
|
|
||||||
|
scope := BuildMediaScope(c.name, deliveryChatID, inboundCtx.MessageID)
|
||||||
|
|
||||||
msg := bus.InboundMessage{
|
msg := bus.InboundMessage{
|
||||||
Channel: c.name,
|
Channel: c.name,
|
||||||
SenderID: resolvedSenderID,
|
SenderID: resolvedSenderID,
|
||||||
Sender: sender,
|
Sender: sender,
|
||||||
ChatID: chatID,
|
ChatID: deliveryChatID,
|
||||||
|
Context: inboundCtx,
|
||||||
Content: content,
|
Content: content,
|
||||||
Media: media,
|
Media: media,
|
||||||
Peer: peer,
|
Peer: peer,
|
||||||
MessageID: messageID,
|
MessageID: inboundCtx.MessageID,
|
||||||
MediaScope: scope,
|
MediaScope: scope,
|
||||||
Metadata: metadata,
|
|
||||||
}
|
}
|
||||||
msg.Context = bus.ContextFromLegacyInbound(msg)
|
msg = bus.NormalizeInboundMessage(msg)
|
||||||
|
|
||||||
// Auto-trigger typing indicator, message reaction, and placeholder before publishing.
|
// Auto-trigger typing indicator, message reaction, and placeholder before publishing.
|
||||||
// Each capability is independent — all three may fire for the same message.
|
// Each capability is independent — all three may fire for the same message.
|
||||||
|
|
@ -298,14 +337,14 @@ func (c *BaseChannel) HandleMessage(
|
||||||
if c.owner != nil && c.placeholderRecorder != nil {
|
if c.owner != nil && c.placeholderRecorder != nil {
|
||||||
// Typing
|
// Typing
|
||||||
if tc, ok := c.owner.(TypingCapable); ok {
|
if tc, ok := c.owner.(TypingCapable); ok {
|
||||||
if stop, err := tc.StartTyping(ctx, chatID); err == nil {
|
if stop, err := tc.StartTyping(ctx, deliveryChatID); err == nil {
|
||||||
c.placeholderRecorder.RecordTypingStop(c.name, chatID, stop)
|
c.placeholderRecorder.RecordTypingStop(c.name, deliveryChatID, stop)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Reaction
|
// Reaction
|
||||||
if rc, ok := c.owner.(ReactionCapable); ok && messageID != "" {
|
if rc, ok := c.owner.(ReactionCapable); ok && msg.MessageID != "" {
|
||||||
if undo, err := rc.ReactToMessage(ctx, chatID, messageID); err == nil {
|
if undo, err := rc.ReactToMessage(ctx, deliveryChatID, msg.MessageID); err == nil {
|
||||||
c.placeholderRecorder.RecordReactionUndo(c.name, chatID, undo)
|
c.placeholderRecorder.RecordReactionUndo(c.name, deliveryChatID, undo)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Placeholder — independent pipeline.
|
// Placeholder — independent pipeline.
|
||||||
|
|
@ -314,8 +353,8 @@ func (c *BaseChannel) HandleMessage(
|
||||||
// "Thinking…" only once the voice has been processed.
|
// "Thinking…" only once the voice has been processed.
|
||||||
if !audioAnnotationRe.MatchString(content) {
|
if !audioAnnotationRe.MatchString(content) {
|
||||||
if pc, ok := c.owner.(PlaceholderCapable); ok {
|
if pc, ok := c.owner.(PlaceholderCapable); ok {
|
||||||
if phID, err := pc.SendPlaceholder(ctx, chatID); err == nil && phID != "" {
|
if phID, err := pc.SendPlaceholder(ctx, deliveryChatID); err == nil && phID != "" {
|
||||||
c.placeholderRecorder.RecordPlaceholder(c.name, chatID, phID)
|
c.placeholderRecorder.RecordPlaceholder(c.name, deliveryChatID, phID)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -324,7 +363,7 @@ func (c *BaseChannel) HandleMessage(
|
||||||
if err := c.bus.PublishInbound(ctx, msg); err != nil {
|
if err := c.bus.PublishInbound(ctx, msg); err != nil {
|
||||||
logger.ErrorCF("channels", "Failed to publish inbound message", map[string]any{
|
logger.ErrorCF("channels", "Failed to publish inbound message", map[string]any{
|
||||||
"channel": c.name,
|
"channel": c.name,
|
||||||
"chat_id": chatID,
|
"chat_id": deliveryChatID,
|
||||||
"error": err.Error(),
|
"error": err.Error(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -363,8 +363,8 @@ func (c *DiscordChannel) handleMessage(s *discordgo.Session, m *discordgo.Messag
|
||||||
|
|
||||||
// In guild (group) channels, apply unified group trigger filtering
|
// In guild (group) channels, apply unified group trigger filtering
|
||||||
// DMs (GuildID is empty) always get a response
|
// DMs (GuildID is empty) always get a response
|
||||||
if m.GuildID != "" {
|
|
||||||
isMentioned := false
|
isMentioned := false
|
||||||
|
if m.GuildID != "" {
|
||||||
for _, mention := range m.Mentions {
|
for _, mention := range m.Mentions {
|
||||||
if mention.ID == c.botUserID {
|
if mention.ID == c.botUserID {
|
||||||
isMentioned = true
|
isMentioned = true
|
||||||
|
|
@ -477,8 +477,24 @@ func (c *DiscordChannel) handleMessage(s *discordgo.Session, m *discordgo.Messag
|
||||||
"channel_id": m.ChannelID,
|
"channel_id": m.ChannelID,
|
||||||
"is_dm": fmt.Sprintf("%t", m.GuildID == ""),
|
"is_dm": fmt.Sprintf("%t", m.GuildID == ""),
|
||||||
}
|
}
|
||||||
|
inboundCtx := bus.InboundContext{
|
||||||
|
Channel: c.Name(),
|
||||||
|
ChatID: m.ChannelID,
|
||||||
|
ChatType: peerKind,
|
||||||
|
SenderID: senderID,
|
||||||
|
MessageID: m.ID,
|
||||||
|
Mentioned: isMentioned,
|
||||||
|
Raw: metadata,
|
||||||
|
}
|
||||||
|
if m.GuildID != "" {
|
||||||
|
inboundCtx.SpaceID = m.GuildID
|
||||||
|
inboundCtx.SpaceType = "guild"
|
||||||
|
}
|
||||||
|
if m.MessageReference != nil {
|
||||||
|
inboundCtx.ReplyToMessageID = m.MessageReference.MessageID
|
||||||
|
}
|
||||||
|
|
||||||
c.HandleMessage(c.ctx, peer, m.ID, senderID, m.ChannelID, content, mediaPaths, metadata, sender)
|
c.HandleMessageWithContext(c.ctx, peer, m.ChannelID, content, mediaPaths, inboundCtx, sender)
|
||||||
}
|
}
|
||||||
|
|
||||||
// startTyping starts a continuous typing indicator loop for the given chatID.
|
// startTyping starts a continuous typing indicator loop for the given chatID.
|
||||||
|
|
|
||||||
|
|
@ -379,7 +379,22 @@ func (c *SlackChannel) handleMessageEvent(ev *slackevents.MessageEvent) {
|
||||||
"has_thread": threadTS != "",
|
"has_thread": threadTS != "",
|
||||||
})
|
})
|
||||||
|
|
||||||
c.HandleMessage(c.ctx, peer, messageTS, senderID, chatID, content, mediaPaths, metadata, sender)
|
inboundCtx := bus.InboundContext{
|
||||||
|
Channel: c.Name(),
|
||||||
|
Account: c.teamID,
|
||||||
|
ChatID: channelID,
|
||||||
|
ChatType: peerKind,
|
||||||
|
SenderID: senderID,
|
||||||
|
MessageID: messageTS,
|
||||||
|
SpaceID: c.teamID,
|
||||||
|
SpaceType: "workspace",
|
||||||
|
Raw: metadata,
|
||||||
|
}
|
||||||
|
if threadTS != "" {
|
||||||
|
inboundCtx.TopicID = threadTS
|
||||||
|
}
|
||||||
|
|
||||||
|
c.HandleMessageWithContext(c.ctx, peer, chatID, content, mediaPaths, inboundCtx, sender)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *SlackChannel) handleAppMention(ev *slackevents.AppMentionEvent) {
|
func (c *SlackChannel) handleAppMention(ev *slackevents.AppMentionEvent) {
|
||||||
|
|
@ -443,8 +458,21 @@ func (c *SlackChannel) handleAppMention(ev *slackevents.AppMentionEvent) {
|
||||||
"is_mention": "true",
|
"is_mention": "true",
|
||||||
"team_id": c.teamID,
|
"team_id": c.teamID,
|
||||||
}
|
}
|
||||||
|
inboundCtx := bus.InboundContext{
|
||||||
|
Channel: c.Name(),
|
||||||
|
Account: c.teamID,
|
||||||
|
ChatID: channelID,
|
||||||
|
ChatType: mentionPeerKind,
|
||||||
|
TopicID: threadTS,
|
||||||
|
SenderID: senderID,
|
||||||
|
MessageID: messageTS,
|
||||||
|
SpaceID: c.teamID,
|
||||||
|
SpaceType: "workspace",
|
||||||
|
Mentioned: true,
|
||||||
|
Raw: metadata,
|
||||||
|
}
|
||||||
|
|
||||||
c.HandleMessage(c.ctx, mentionPeer, messageTS, senderID, chatID, content, nil, metadata, mentionSender)
|
c.HandleMessageWithContext(c.ctx, mentionPeer, chatID, content, nil, inboundCtx, mentionSender)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *SlackChannel) handleSlashCommand(event socketmode.Event) {
|
func (c *SlackChannel) handleSlashCommand(event socketmode.Event) {
|
||||||
|
|
@ -491,16 +519,30 @@ func (c *SlackChannel) handleSlashCommand(event socketmode.Event) {
|
||||||
"command": cmd.Command,
|
"command": cmd.Command,
|
||||||
"text": utils.Truncate(content, 50),
|
"text": utils.Truncate(content, 50),
|
||||||
})
|
})
|
||||||
|
peerKind := "channel"
|
||||||
|
peerID := channelID
|
||||||
|
if strings.HasPrefix(channelID, "D") {
|
||||||
|
peerKind = "direct"
|
||||||
|
peerID = senderID
|
||||||
|
}
|
||||||
|
inboundCtx := bus.InboundContext{
|
||||||
|
Channel: c.Name(),
|
||||||
|
Account: c.teamID,
|
||||||
|
ChatID: channelID,
|
||||||
|
ChatType: peerKind,
|
||||||
|
SenderID: senderID,
|
||||||
|
SpaceID: c.teamID,
|
||||||
|
SpaceType: "workspace",
|
||||||
|
Raw: metadata,
|
||||||
|
}
|
||||||
|
|
||||||
c.HandleMessage(
|
c.HandleMessageWithContext(
|
||||||
c.ctx,
|
c.ctx,
|
||||||
bus.Peer{Kind: "channel", ID: channelID},
|
bus.Peer{Kind: peerKind, ID: peerID},
|
||||||
"",
|
|
||||||
senderID,
|
|
||||||
chatID,
|
chatID,
|
||||||
content,
|
content,
|
||||||
nil,
|
nil,
|
||||||
metadata,
|
inboundCtx,
|
||||||
cmdSender,
|
cmdSender,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -660,8 +660,9 @@ func (c *TelegramChannel) handleMessage(ctx context.Context, message *telego.Mes
|
||||||
}
|
}
|
||||||
|
|
||||||
// In group chats, apply unified group trigger filtering
|
// In group chats, apply unified group trigger filtering
|
||||||
|
isMentioned := false
|
||||||
if message.Chat.Type != "private" {
|
if message.Chat.Type != "private" {
|
||||||
isMentioned := c.isBotMentioned(message)
|
isMentioned = c.isBotMentioned(message)
|
||||||
if isMentioned {
|
if isMentioned {
|
||||||
content = c.stripBotMention(content)
|
content = c.stripBotMention(content)
|
||||||
}
|
}
|
||||||
|
|
@ -722,24 +723,30 @@ func (c *TelegramChannel) handleMessage(ctx context.Context, message *telego.Mes
|
||||||
"first_name": user.FirstName,
|
"first_name": user.FirstName,
|
||||||
"is_group": fmt.Sprintf("%t", message.Chat.Type != "private"),
|
"is_group": fmt.Sprintf("%t", message.Chat.Type != "private"),
|
||||||
}
|
}
|
||||||
if message.ReplyToMessage != nil {
|
|
||||||
metadata["reply_to_message_id"] = fmt.Sprintf("%d", message.ReplyToMessage.MessageID)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set parent_peer metadata for per-topic agent binding.
|
inboundCtx := bus.InboundContext{
|
||||||
|
Channel: c.Name(),
|
||||||
|
ChatID: fmt.Sprintf("%d", chatID),
|
||||||
|
ChatType: peerKind,
|
||||||
|
SenderID: platformID,
|
||||||
|
MessageID: messageID,
|
||||||
|
Mentioned: isMentioned,
|
||||||
|
Raw: metadata,
|
||||||
|
}
|
||||||
if message.Chat.IsForum && threadID != 0 {
|
if message.Chat.IsForum && threadID != 0 {
|
||||||
metadata["parent_peer_kind"] = "topic"
|
inboundCtx.TopicID = fmt.Sprintf("%d", threadID)
|
||||||
metadata["parent_peer_id"] = fmt.Sprintf("%d", threadID)
|
}
|
||||||
|
if message.ReplyToMessage != nil {
|
||||||
|
inboundCtx.ReplyToMessageID = fmt.Sprintf("%d", message.ReplyToMessage.MessageID)
|
||||||
}
|
}
|
||||||
|
|
||||||
c.HandleMessage(c.ctx,
|
c.HandleMessageWithContext(
|
||||||
|
c.ctx,
|
||||||
peer,
|
peer,
|
||||||
messageID,
|
|
||||||
platformID,
|
|
||||||
compositeChatID,
|
compositeChatID,
|
||||||
content,
|
content,
|
||||||
mediaPaths,
|
mediaPaths,
|
||||||
metadata,
|
inboundCtx,
|
||||||
sender,
|
sender,
|
||||||
)
|
)
|
||||||
return nil
|
return nil
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue