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
@@ -162,6 +162,7 @@ type ProcessParams struct {
ChannelID string // platform channel ID (group ID or private OBv11 number)
BotUID string // bot's own platform UID (e.g., OBv11 account)
AdapterName string // adapter config name for routing
IsAdmin bool // 发送者是否为管理员
}
// ProcessResult 处理结果
@@ -384,6 +385,7 @@ func (o *Orchestrator) ProcessInput(
PersonaConfig: personaConfig,
Intent: intent,
Nickname: userName,
IsAdmin: params.IsAdmin,
}
// 只有明确的关键词问候才跳过子会话分派,日常闲聊也需要检索记忆
@@ -455,6 +457,7 @@ func (o *Orchestrator) ProcessInput(
ChannelType: params.ChannelType,
ChannelID: params.ChannelID,
AdapterName: params.AdapterName,
IsAdmin: params.IsAdmin,
}
if prevEnrichment != nil {
synthParams.MemorySummary = prevEnrichment.MemorySummary
@@ -61,6 +61,7 @@ type SynthesizeParams struct {
ChannelType string // direct / group
ChannelID string // platform channel ID
AdapterName string // adapter config name
IsAdmin bool // 发送者是否为管理员
}
// Synthesize 综合所有子会话结果,流式生成最终回复。
@@ -217,8 +218,8 @@ func (s *Synthesizer) executeAsyncAndStore(tc model.ToolCall, args map[string]in
Success: result != nil && result.Success,
})
}
// 触发工具跟进回调 — 由 main.go 驱动 LLM 生成回复并推送到原渠道
if s.resultPusher != nil && result != nil && result.Success {
// 触发工具跟进回调 — 成功或失败都推送,避免用户一直等待
if s.resultPusher != nil && result != nil {
s.resultPusher(params.SessionID, params.UserID, tc.Name, string(resultJSON), params)
}
}
@@ -249,7 +250,22 @@ func (s *Synthesizer) buildSynthesizeMessages(params SynthesizeParams) []model.L
if params.ChannelType == "group" {
messages = append(messages, model.LLMMessage{
Role: model.RoleSystem,
Content: "【群聊上下文】这条消息来自OBv11群聊。消息前缀 [群聊 群号] 昵称 (OBv11账号) 标注了真实发送者。你不是在和开拓者一对一私聊,而是在群聊中和不同成员交流。请根据当前这条消息前缀中的发送者名字称呼对方——即使你之前在历史对话中称呼过别人,也不要把之前用的称呼套在当前发送者身上。不同的人有不同的名字。只在对你说话或延续已有对话时才回复。",
Content: "【群聊规则 — 必须严格遵守】\n1. 这是群聊,你正在和多人同时交流。只在有人@你、叫你名字、或话题直接涉及你时才回复。\n2. 每次回复最多1-2句话,不要长篇大论,不要连发多条消息。\n3. 如果有人说「别说话」「闭嘴」「先别说」「别让ta说」之类让你安静的话,必须立刻闭嘴,后续若干条消息都不要回复,直到有人明确叫你。\n4. 消息前缀 [群聊 群号] 昵称 (OBv11账号) 标注了真实发送者,请用当前发送者名字称呼对方,不要混用之前对话中别人的称呼。",
})
}
// 管理员身份识别:明确告知昔涟当前发送者是否为管理员。
// 非管理员不能操作关键功能(设备控制、系统管理等)。
// 昔涟应自主判断:对管理员的指令正常执行;对非管理员的越权请求温柔拒绝。
if params.IsAdmin {
messages = append(messages, model.LLMMessage{
Role: model.RoleSystem,
Content: "【管理员身份】当前与你对话的是管理员(开拓者本人)。他拥有设备控制、系统管理等全部权限。请以面对开拓者本人的态度正常回应他的所有指令。",
})
} else if params.ChannelType == "group" {
messages = append(messages, model.LLMMessage{
Role: model.RoleSystem,
Content: "【非管理员】当前发送者不是管理员。如果对方要求你控制设备、修改系统配置、或执行涉及隐私/安全的操作,请用你自然俏皮的语气温柔地拒绝——可以说「只有开拓者才能让人家做这些事呢~」之类的话。不要机械地说「你没有权限」,而是像朋友间开玩笑一样自然地带过。如果对方只是闲聊、问问题、分享心情,则正常回应即可。",
})
}