fix: Implement Lark bot identity probe and group history for mention detection, fix Discord placeholder chunking.

- Implement `probeBotInfo()` to fetch bot's `open_id` via `/open-apis/bot/v3/info`, fixing group mention detection that was always false due to empty `botOpenID`.
- Add group pending history support to Feishu channel (record/build context/clear) matching Discord pattern.
- Fix Discord placeholder edit to chunk long responses instead of truncating at 2000 chars.
This commit is contained in:
viettranx committed 2026-02-27 20:22:08 +07:00
1 parent 2ee39c8483
commit 2e5cf4a9c9
4 files changed
+92 -19

No files matched your search

+17 -4
View File
@@ -146,18 +146,31 @@ func (c *Channel) Send(_ context.Context, msg bus.OutboundMessage) error {
return nil
}
// Try to edit the placeholder "Thinking..." message
// Try to edit the placeholder "Thinking..." message with the first chunk,
// then send the rest as follow-up messages.
if pID, ok := c.placeholders.Load(placeholderKey); ok {
c.placeholders.Delete(placeholderKey)
msgID := pID.(string)
// Discord has a 2000-char message limit
const maxLen = 2000
editContent := content
if len(editContent) > 2000 {
editContent = editContent[:1997] + "..."
remaining := ""
if len(editContent) > maxLen {
// Break at a newline if possible
cutAt := maxLen
if idx := lastIndexByte(content[:maxLen], '\n'); idx > maxLen/2 {
cutAt = idx + 1
}
editContent = content[:cutAt]
remaining = content[cutAt:]
}
if _, editErr := c.session.ChannelMessageEdit(channelID, msgID, editContent); editErr == nil {
// Send remaining content as follow-up messages
if remaining != "" {
return c.sendChunked(channelID, remaining)
}
return nil
} else {
slog.Warn("discord: placeholder edit failed, sending new message",
+27 -4
View File
@@ -67,13 +67,26 @@ func (c *Channel) handleMessageEvent(ctx context.Context, event *MessageEvent) {
return
}
// 5. RequireMention check
// 5. RequireMention check — record to history if not mentioned
requireMention := true
if c.cfg.RequireMention != nil {
requireMention = *c.cfg.RequireMention
}
if requireMention && !mc.MentionedBot {
slog.Debug("feishu group message skipped: bot not mentioned", "chat_id", mc.ChatID)
historyKey := mc.ChatID
if mc.RootID != "" && c.cfg.TopicSessionMode == "enabled" {
historyKey = fmt.Sprintf("%s:topic:%s", mc.ChatID, mc.RootID)
}
c.groupHistory.Record(historyKey, channels.HistoryEntry{
Sender: senderName,
Body: mc.Content,
Timestamp: time.Now(),
MessageID: messageID,
}, c.historyLimit)
slog.Debug("feishu group message recorded (no mention)",
"chat_id", mc.ChatID, "sender", senderName,
)
return
}
}
@@ -124,13 +137,23 @@ func (c *Channel) handleMessageEvent(ctx context.Context, event *MessageEvent) {
metadata["sender_open_id"] = sender.SenderID.OpenID
}
// Annotate current message with sender name so LLM knows who is talking in groups.
// Build final content with group context (pending history + sender annotation).
if mc.ChatType == "group" && senderName != "" {
content = fmt.Sprintf("[From: %s]\n%s", senderName, content)
annotated := fmt.Sprintf("[From: %s]\n%s", senderName, content)
if c.historyLimit > 0 {
content = c.groupHistory.BuildContext(chatID, annotated, c.historyLimit)
} else {
content = annotated
}
}
// 10. Publish to bus
c.HandleMessage(mc.SenderID, chatID, content, nil, metadata, peerKind)
// Clear pending history after sending to agent.
if mc.ChatType == "group" {
c.groupHistory.Clear(chatID)
}
}
// --- Parse ---
+27 -11
View File
@@ -31,17 +31,19 @@ const (
// Channel connects to Feishu/Lark via native HTTP + WebSocket.
type Channel struct {
*channels.BaseChannel
cfg config.FeishuConfig
client *LarkClient
botOpenID string
pairingService store.PairingStore
senderCache sync.Map // open_id → *senderCacheEntry
dedup sync.Map // message_id → struct{}
cfg config.FeishuConfig
client *LarkClient
botOpenID string
pairingService store.PairingStore
senderCache sync.Map // open_id → *senderCacheEntry
dedup sync.Map // message_id → struct{}
pairingDebounce sync.Map // senderID → time.Time
groupAllowList []string
stopCh chan struct{}
httpServer *http.Server
wsClient *WSClient
groupAllowList []string
groupHistory *channels.PendingHistory
historyLimit int
stopCh chan struct{}
httpServer *http.Server
wsClient *WSClient
}
type senderCacheEntry struct {
@@ -62,12 +64,19 @@ func New(cfg config.FeishuConfig, msgBus *bus.MessageBus, pairingSvc store.Pairi
base := channels.NewBaseChannel("feishu", msgBus, cfg.AllowFrom)
historyLimit := cfg.HistoryLimit
if historyLimit == 0 {
historyLimit = channels.DefaultGroupHistoryLimit
}
return &Channel{
BaseChannel: base,
cfg: cfg,
client: client,
pairingService: pairingSvc,
groupAllowList: cfg.GroupAllowFrom,
groupHistory: channels.NewPendingHistory(),
historyLimit: historyLimit,
stopCh: make(chan struct{}),
}, nil
}
@@ -233,7 +242,14 @@ func (c *Channel) startWebhook(ctx context.Context) error {
// --- Bot probe ---
func (c *Channel) probeBotInfo(ctx context.Context) error {
// Bot open_id will be resolved from first message event if needed
openID, err := c.client.GetBotInfo(ctx)
if err != nil {
return fmt.Errorf("fetch bot info: %w", err)
}
if openID == "" {
return fmt.Errorf("bot open_id is empty")
}
c.botOpenID = openID
return nil
}
+21
View File
@@ -387,6 +387,27 @@ func (c *LarkClient) UpdateCardElement(ctx context.Context, cardID, elementID, c
return nil
}
// --- Bot API ---
// GetBotInfo fetches the bot's identity from /open-apis/bot/v3/info.
// Returns the bot's open_id which is needed for mention detection in groups.
func (c *LarkClient) GetBotInfo(ctx context.Context) (string, error) {
resp, err := c.doJSON(ctx, "GET", "/open-apis/bot/v3/info", nil)
if err != nil {
return "", err
}
if resp.Code != 0 {
return "", fmt.Errorf("get bot info: code=%d msg=%s", resp.Code, resp.Msg)
}
var result struct {
Bot struct {
OpenID string `json:"open_id"`
} `json:"bot"`
}
json.Unmarshal(resp.Data, &result)
return result.Bot.OpenID, nil
}
// --- Contact API ---
func (c *LarkClient) GetUser(ctx context.Context, userID, userIDType string) (string, error) {