fix: 死锁修复、管理员权限集中管控、群聊频率控制、表情映射修复
thinker.go: 移除所有 defer t.muUnlock() 持锁跨阻塞调用的模式,消除8处死锁点 - performThink: defer→立即解锁,LLM调用不再持锁 - lightThinkLoop: defer在for循环内→第2次迭代自死锁 - resetSilenceTimer: defer持锁调performThink - UpdatePresence: defer持锁调time.Sleep+performThink - storeThought: defer+panic→锁泄露; 移除extractProactiveMessage嵌套锁 is_admin三层防御: - synthesizer: 系统提示词注入管理员/非管理员身份标签 - iot_provider: 非管理员直接拒绝IoT操作 - plugin-manager: ToolDefinition.AdminOnly自动拦截,集中管控 群聊优化: - group_ambient: 强化审查指令,【不发送】自审查标签 - 群聊间隔4s→3s,最多2条/轮 - 工具失败也推跟进消息,避免沉默 平台桥接: - 日志文件名使用适配器唯一标识符(ConfigName) - QQ表情映射替换为官方116条目数据 - CQ表情保留名称/ID
This commit is contained in:
@@ -363,7 +363,6 @@ func (t *Thinker) SetEmotionTracker(et *persona.EmotionTracker) {
|
||||
// Called by the ai-core presence endpoint when gateway detects connect/disconnect.
|
||||
func (t *Thinker) UpdatePresence(online bool, sessionID string) {
|
||||
t.muLock()
|
||||
defer t.muUnlock()
|
||||
wasOffline := !t.isUserOnline()
|
||||
t.setUserOnline(online)
|
||||
t.lastOnlineChange = time.Now()
|
||||
@@ -371,7 +370,8 @@ func (t *Thinker) UpdatePresence(online bool, sessionID string) {
|
||||
t.userSessionID = sessionID
|
||||
t.activeSessionID = sessionID
|
||||
}
|
||||
|
||||
emotionTracker := t.emotionTracker
|
||||
t.muUnlock()
|
||||
|
||||
if online && wasOffline {
|
||||
log.Printf("[后台思考] 用户上线 (session=%s),触发重连思考", sessionID)
|
||||
@@ -379,8 +379,8 @@ func (t *Thinker) UpdatePresence(online bool, sessionID string) {
|
||||
time.Sleep(2 * time.Second)
|
||||
t.performThink("user_returned")
|
||||
// Also update emotion tracker
|
||||
if t.emotionTracker != nil {
|
||||
t.emotionTracker.UpdateMood("user_returned")
|
||||
if emotionTracker != nil {
|
||||
emotionTracker.UpdateMood("user_returned")
|
||||
}
|
||||
} else if !online {
|
||||
log.Printf("[后台思考] 用户离线")
|
||||
@@ -391,7 +391,6 @@ func (t *Thinker) UpdatePresence(online bool, sessionID string) {
|
||||
// Called by the internal reminder-trigger endpoint when a reminder fires.
|
||||
func (t *Thinker) TriggerReminderMessage(userID, sessionID, message string) {
|
||||
t.muLock()
|
||||
defer t.muUnlock()
|
||||
pusher := t.messagePusher
|
||||
pushSessionID := sessionID
|
||||
if pushSessionID == "" {
|
||||
@@ -400,7 +399,7 @@ func (t *Thinker) TriggerReminderMessage(userID, sessionID, message string) {
|
||||
if pushSessionID == "" {
|
||||
pushSessionID = t.adminSessionID
|
||||
}
|
||||
|
||||
t.muUnlock()
|
||||
|
||||
if pusher != nil && message != "" {
|
||||
log.Printf("[提醒推送] 推送LLM提醒: user=%s session=%s msg=%s", userID, pushSessionID, message)
|
||||
@@ -411,8 +410,8 @@ func (t *Thinker) TriggerReminderMessage(userID, sessionID, message string) {
|
||||
// PushPlatformMessage pushes a message to a platform channel via the platform pusher.
|
||||
func (t *Thinker) PushPlatformMessage(target ProactiveTarget, message string) {
|
||||
t.muLock()
|
||||
defer t.muUnlock()
|
||||
pusher := t.platformMessagePusher
|
||||
t.muUnlock()
|
||||
|
||||
if pusher != nil {
|
||||
log.Printf("[平台推送] target=%s/%s group=%s msg=%s", target.Platform, target.ChatType, target.GroupID, message)
|
||||
@@ -743,10 +742,9 @@ func (t *Thinker) resetSilenceTimer() {
|
||||
case <-t.silenceTimer.C:
|
||||
// 再次检查:用户是否真的沉默了足够久
|
||||
t.muLock()
|
||||
defer t.muUnlock()
|
||||
silenceDuration := time.Since(t.lastUserTime())
|
||||
canThink := time.Since(t.lastThinkTimeAtomic()) >= t.minThinkGap
|
||||
|
||||
t.muUnlock()
|
||||
|
||||
if silenceDuration < t.silenceTimeout {
|
||||
log.Printf("[后台思考] 静默检测触发但用户已活动,跳过 (实际静默=%v)", silenceDuration)
|
||||
@@ -894,10 +892,9 @@ func (t *Thinker) lightThinkLoop() {
|
||||
return
|
||||
case <-time.After(t.lightThinkInterval):
|
||||
t.muLock()
|
||||
defer t.muUnlock()
|
||||
sinceLastUser := time.Since(t.lastUserTime())
|
||||
sinceLastThink := time.Since(t.lastThinkTimeAtomic())
|
||||
|
||||
t.muUnlock()
|
||||
|
||||
// Skip if user was active recently (last 30s).
|
||||
if sinceLastUser < 30*time.Second {
|
||||
@@ -955,16 +952,16 @@ func (t *Thinker) performLightThink() {
|
||||
// Check if deep think should be woken.
|
||||
if strings.Contains(content, "【需要深思】") {
|
||||
log.Println("[轻量思考] 检测到【需要深思】,唤醒深度思考...")
|
||||
t.muLock()
|
||||
defer t.muUnlock()
|
||||
canDeep := time.Since(t.lastThinkTimeAtomic()) >= t.minThinkGap
|
||||
t.muLock()
|
||||
canDeep := time.Since(t.lastThinkTimeAtomic()) >= t.minThinkGap
|
||||
t.muUnlock()
|
||||
|
||||
if canDeep {
|
||||
t.performThink("light_wake")
|
||||
} else {
|
||||
log.Println("[轻量思考] 距上次深度思考太近,跳过唤醒")
|
||||
}
|
||||
}
|
||||
if canDeep {
|
||||
t.performThink("light_wake")
|
||||
} else {
|
||||
log.Println("[轻量思考] 距上次深度思考太近,跳过唤醒")
|
||||
}
|
||||
}
|
||||
|
||||
// Check if a topic should be initiated (proactive conversation starter).
|
||||
if idx := strings.Index(content, "【话题发起】"); idx >= 0 {
|
||||
@@ -972,7 +969,6 @@ func (t *Thinker) performLightThink() {
|
||||
if topic != "" {
|
||||
log.Printf("[轻量思考] 话题发起: %s", topic)
|
||||
t.muLock()
|
||||
defer t.muUnlock()
|
||||
pusher := t.messagePusher
|
||||
sessionID := t.activeSessionID
|
||||
if sessionID == "" {
|
||||
@@ -980,6 +976,7 @@ func (t *Thinker) performLightThink() {
|
||||
}
|
||||
canPush := time.Since(t.lastProactiveTime()) >= t.proactiveMsgMinGap
|
||||
|
||||
t.muUnlock()
|
||||
if pusher != nil && canPush {
|
||||
go pusher(t.adminUserID, sessionID, topic)
|
||||
if t.convStore != nil && sessionID != "" {
|
||||
@@ -1140,11 +1137,11 @@ func (t *Thinker) performThink(triggerReason string) {
|
||||
var convHistory []model.LLMMessage
|
||||
if t.convStore != nil {
|
||||
t.muLock()
|
||||
defer t.muUnlock()
|
||||
sessionID := t.activeSessionID
|
||||
if sessionID == "" {
|
||||
sessionID = t.adminSessionID
|
||||
}
|
||||
t.muUnlock()
|
||||
|
||||
if sessionID != "" {
|
||||
convHistory = t.convStore.GetHistory(sessionID, 30)
|
||||
@@ -1258,13 +1255,13 @@ func (t *Thinker) performThink(triggerReason string) {
|
||||
var platformObservation string
|
||||
if triggerReason == "periodic" || triggerReason == "post_chat" {
|
||||
t.muLock()
|
||||
defer t.muUnlock()
|
||||
for i := len(t.pendingThoughts) - 1; i >= 0; i-- {
|
||||
if strings.HasPrefix(t.pendingThoughts[i].Content, "[平台观察") {
|
||||
platformObservation = t.pendingThoughts[i].Content
|
||||
break
|
||||
}
|
||||
}
|
||||
t.muUnlock()
|
||||
|
||||
}
|
||||
|
||||
@@ -1409,7 +1406,6 @@ func (t *Thinker) performThink(triggerReason string) {
|
||||
adapterName, groupID := parts[0], parts[1]
|
||||
// Find the channel to get the correct platform type.
|
||||
t.muLock()
|
||||
defer t.muUnlock()
|
||||
var platform string
|
||||
for _, ch := range t.platformChannels {
|
||||
if ch.AdapterName == adapterName && ch.ChannelID == groupID && ch.ChannelType == "group" {
|
||||
@@ -1421,6 +1417,7 @@ func (t *Thinker) performThink(triggerReason string) {
|
||||
if platform == "" {
|
||||
platform = "obv11" // fallback
|
||||
}
|
||||
t.muUnlock()
|
||||
log.Printf("[后台思考] 请求查看群聊 %s/%s (adapter=%s)", platform, groupID, adapterName)
|
||||
if t.memClient != nil {
|
||||
namespace := fmt.Sprintf("platform_%s_group_%s", platform, groupID)
|
||||
@@ -1912,7 +1909,6 @@ func (t *Thinker) buildOpenAITools() []llm.OpenAITool {
|
||||
// storeThought 存储思考结果到待推送队列,并异步持久化到 memory-service
|
||||
func (t *Thinker) storeThought(content string, toolCallsJSON string, toolCallCount int) {
|
||||
t.muLock()
|
||||
defer t.muUnlock()
|
||||
t.pendingThoughts = append(t.pendingThoughts, &PendingThought{
|
||||
Content: content,
|
||||
CreatedAt: time.Now(),
|
||||
@@ -1963,19 +1959,20 @@ func (t *Thinker) storeThought(content string, toolCallsJSON string, toolCallCou
|
||||
}
|
||||
}
|
||||
}
|
||||
// 被拦截的主动消息:清除标记,防止 LLM 下次以为发过了
|
||||
if !canPush && proactiveMsg != "" {
|
||||
content = strings.ReplaceAll(content, "【主动消息】", "【未发送】")
|
||||
content = strings.ReplaceAll(content, "【话题发起】", "【未发送】")
|
||||
}
|
||||
// Copy target for use after unlock (avoid race).
|
||||
var targetCopy *ProactiveTarget
|
||||
if proactiveTarget != nil {
|
||||
copy := *proactiveTarget
|
||||
targetCopy = ©
|
||||
}
|
||||
t.muUnlock()
|
||||
// 被拦截的主动消息:清除标记,防止 LLM 下次以为发过了
|
||||
if !canPush && proactiveMsg != "" {
|
||||
content = strings.ReplaceAll(content, "【主动消息】", "【未发送】")
|
||||
content = strings.ReplaceAll(content, "【话题发起】", "【未发送】")
|
||||
}
|
||||
|
||||
log.Printf("[后台思考] 思考已存储 (当前累积 %d 条待推送思考)", len(t.pendingThoughts))
|
||||
log.Printf("[后台思考] 思考已存储 (当前累积 %d 条待推送思考)", len(t.pendingThoughts))
|
||||
|
||||
// Async persist to memory-service.
|
||||
if t.memClient != nil {
|
||||
@@ -2087,8 +2084,6 @@ func (t *Thinker) extractProactiveMessage(content string) (string, *ProactiveTar
|
||||
channelID = "private_" + m[2]
|
||||
}
|
||||
adapterName := platform // fallback to format key
|
||||
t.muLock()
|
||||
defer t.muUnlock()
|
||||
for _, ch := range t.platformChannels {
|
||||
if ch.Platform == platform && ch.ChannelType == chatType && ch.ChannelID == channelID {
|
||||
if ch.AdapterName != "" {
|
||||
|
||||
Reference in New Issue
Block a user