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:
2026-06-28 20:22:09 +08:00
parent 236dbfcc92
commit 44048b333a
14 changed files with 491 additions and 72 deletions
+31 -6
View File
@@ -80,7 +80,7 @@ func main() {
msgLogger.Log(logging.LogEntry{
Timestamp: time.Now(),
Direction: "incoming",
Platform: msg.Platform,
Platform: logKey(msg),
ChannelID: msg.ChannelID,
SenderID: msg.OriginalSenderUID,
SenderName: msg.OriginalSenderName,
@@ -100,7 +100,7 @@ func main() {
msgLogger.Log(logging.LogEntry{
Timestamp: time.Now(),
Direction: "incoming",
Platform: msg.Platform,
Platform: logKey(msg),
ChannelID: msg.ChannelID,
SenderID: msg.OriginalSenderUID,
SenderName: msg.OriginalSenderName,
@@ -186,7 +186,7 @@ func main() {
msgLogger.Log(logging.LogEntry{
Timestamp: time.Now(),
Direction: "error",
Platform: msg.Platform,
Platform: logKey(msg),
ChannelID: msg.ChannelID,
SenderID: msg.OriginalSenderUID,
Success: false,
@@ -241,7 +241,7 @@ func main() {
msgLogger.Log(logging.LogEntry{
Timestamp: time.Now(),
Direction: "error",
Platform: msg.Platform,
Platform: logKey(msg),
ChannelID: msg.ChannelID,
SenderID: msg.OriginalSenderUID,
Success: false,
@@ -256,7 +256,7 @@ func main() {
msgLogger.Log(logging.LogEntry{
Timestamp: time.Now(),
Direction: "outgoing",
Platform: msg.Platform,
Platform: logKey(msg),
ChannelID: msg.ChannelID,
SenderID: msg.BotUID,
SenderName: "Cyrene",
@@ -438,10 +438,15 @@ func startOBv11Readers(router *bridge.PlatformRouter) {
messageType := msg.MessageType
userID := msg.UserID
groupID := msg.GroupID
// Filter non-empty messages.
// Filter non-empty messages and strip 【不发送】 self-censored ones.
var toSend []bridge.ResponseMessage
for _, rm := range response.Messages {
if rm.Content != "" {
// 昔涟可以加 【不发送】 标签表示这条消息不发送——她醒了但选择不说话。
if strings.Contains(rm.Content, "【不发送】") {
fmt.Printf("[qq:%s] 昔涟自我审查,跳过发送: %s\n", adapterKey, truncateString(rm.Content, 60))
continue
}
rm.Content = qqadapter.ConvertMarkdownToQQ(rm.Content)
toSend = append(toSend, rm)
}
@@ -458,6 +463,16 @@ func startOBv11Readers(router *bridge.PlatformRouter) {
if interval <= 0 {
interval = 2 * time.Second
}
// 群聊用更长间隔避免刷屏,且限制最多2条消息
if messageType == "group" {
minGroupInterval := 3 * time.Second
if interval < minGroupInterval {
interval = minGroupInterval
}
if len(toSend) > 2 {
toSend = toSend[:2]
}
}
for i, rm := range toSend {
if i > 0 && interval > 0 {
select {
@@ -1095,6 +1110,16 @@ func formatDuration(d time.Duration) string {
return fmt.Sprintf("%dh", h)
}
// logKey returns the log file key for a message.
// Uses the adapter config name (e.g., "obv11-main") if set, falling back to platform type (e.g., "obv11").
// This ensures each config writes to its own uniquely-named log file.
func logKey(msg *bridge.UnifiedMessage) string {
if msg.AdapterName != "" {
return msg.AdapterName
}
return msg.Platform
}
// truncateString truncates a string to maxRunes runes, appending "…" if truncated.
func truncateString(s string, maxRunes int) string {
runes := []rune(s)