fix: 后台思考身份混淆 + 静默模式视觉理解 + QQ卡片解析 + 仪表盘状态修复

- 后台思考对话历史增加标签说明,严格区分群聊中不同发送者
- 静默观察模式传入图片URL并预处理,供后台思考参考
- 视觉+OCR双模型结果合并格式优化,避免LLM误认为多张图片
- QQ卡片消息(CQ:json)正确解析标题/类型,不再丢失为[JSON]
- 进程管理器stop()在进程为null时重置pid/startTime,消除矛盾状态

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
2026-05-31 21:07:25 +08:00
parent a9c79d7887
commit b085e58031
7 changed files with 179 additions and 33 deletions
+39 -13
View File
@@ -180,7 +180,7 @@ func main() {
case isAdmin && !isBotMentioned && shouldAdminBeSilent(msg, router):
msg.RouteType = "silent"
namespace := buildMemoryNamespace(msg.Platform, msg.ChannelType, msg.ChannelID)
response, routeErr = forwardToAICore(cfg, msg, "platform_silent", namespace, namespace, nil, videoURLs, voiceURLs, isAdmin)
response, routeErr = forwardToAICore(cfg, msg, "platform_silent", namespace, namespace, imageURLs, videoURLs, voiceURLs, isAdmin)
case isAdmin:
msg.RouteType = "normal"
@@ -195,12 +195,12 @@ func main() {
// the admin already gets QQ's native @notification. Observe silently.
msg.RouteType = "silent"
namespace := buildMemoryNamespace(msg.Platform, msg.ChannelType, msg.ChannelID)
response, routeErr = forwardToAICore(cfg, msg, "platform_silent", namespace, namespace, nil, videoURLs, voiceURLs, isAdmin)
response, routeErr = forwardToAICore(cfg, msg, "platform_silent", namespace, namespace, imageURLs, videoURLs, voiceURLs, isAdmin)
case isSilent:
msg.RouteType = "silent"
namespace := buildMemoryNamespace(msg.Platform, msg.ChannelType, msg.ChannelID)
silentResponse, silentErr := forwardToAICore(cfg, msg, "platform_silent", namespace, namespace, nil, videoURLs, voiceURLs, isAdmin)
silentResponse, silentErr := forwardToAICore(cfg, msg, "platform_silent", namespace, namespace, imageURLs, videoURLs, voiceURLs, isAdmin)
if silentErr != nil {
msgLogger.Log(logging.LogEntry{
Timestamp: time.Now(),
@@ -769,23 +769,49 @@ func parseSSEAndAccumulate(body string) string {
return strings.Join(deltas, "")
}
// splitContent splits text by ♪ (sentence-break marker), then by \n\n within each segment.
// Non-empty segments are each wrapped as a chat message; empty input returns a single empty message.
// splitContent splits text into separate chat messages.
// It first splits by \n\n (message separator), then within each message
// optionally splits further by ♪ (sentence-break marker).
// Very short segments (< 10 chars) are merged with their neighbors to avoid
// one-word messages followed by a wall of text.
func splitContent(text string) []bridge.ResponseMessage {
// First split by ♪ sentence-break marker.
var rawParts []string
if strings.Contains(text, "♪") {
rawParts = strings.Split(text, "♪")
} else {
rawParts = strings.Split(text, "\n\n")
}
// Step 1: split by \n\n (message-level separator) always.
rawParts := strings.Split(text, "\n\n")
var parts []string
for _, p := range rawParts {
p = strings.TrimSpace(p)
if p != "" {
if p == "" {
continue
}
// Step 2: within each \n\n segment, split by ♪ if present.
if strings.Contains(p, "♪") {
for _, sub := range strings.Split(p, "♪") {
sub = strings.TrimSpace(sub)
if sub != "" {
parts = append(parts, sub)
}
}
} else {
parts = append(parts, p)
}
}
// Step 3: merge very short segments with neighbors.
const minRunes = 8
var merged []string
for _, part := range parts {
if len(merged) > 0 && len([]rune(merged[len(merged)-1])) < minRunes {
// Previous segment is too short: merge current into it.
merged[len(merged)-1] = merged[len(merged)-1] + "\n" + part
} else if len(merged) > 0 && len([]rune(part)) < minRunes {
// Current segment is too short: merge it into previous.
merged[len(merged)-1] = merged[len(merged)-1] + "\n" + part
} else {
merged = append(merged, part)
}
}
parts = merged
var msgs []bridge.ResponseMessage
for _, part := range parts {
msgs = append(msgs, bridge.ResponseMessage{