fd44b15d81
**死锁根因修复** - periodicThinkLoop:1015 orphaned lock → 删除(字段已原子化) - RecordUserMessage 隔离为 recordMu - atomic.Int64 替换 lastUserMessage/lastThinkTime 等 **MD3 / Android 17 主题** - 毛玻璃卡片 (backdrop-filter) - MD3 色彩令牌 (pink primary #f472b6) - icons.js 独立矢量图标库 + 运行时 emoji 替换 - 无边框卡片、圆角按钮、阴影层次 **上下文持久化** - AddMessage → saveToDB 异步写 PostgreSQL - LoadFromDB 恢复 (admin-session-main + 懒加载) - LLMMessage.Timestamp 字段 **群聊与适配器** - group_ambient 模式: 非@消息让 LLM 自己判断是否插话 - 戳一戳动作消息总是回复 - NapCat 打字状态 (set_input_status, 最小3秒显示) - HTTP API 配置 (http_url/http_token) **知识库 & 防编造** - knowledge.CanHandle 对 chat 意图也触发 - 关键词预筛选避免无关 embedding 调用 - persona + synthesizer 三重诚实规则 - 工具结果持久化到会话历史 **平台桥接器** - detached:true Go进程独立存活 - ethend 重启自动接管已运行服务 - stop() 接管模式 taskkill/F/ PID - Windows netstat 替代 fuser 获取 PID - 重复适配器种子逻辑修复 - 失败转发日志 Direction: error **崩溃诊断** - crashlog 包 (Recover + WrapHTTP + LLMCall) - /api/v1/debug/goroutines 端点 - thinker 操作日志 + 30s stats - 日志写入 logs/ 目录持久化 Co-Authored-By: Claude <noreply@anthropic.com>
88 lines
2.5 KiB
Go
88 lines
2.5 KiB
Go
package main
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"net/http"
|
|
"strconv"
|
|
"sync"
|
|
"time"
|
|
)
|
|
|
|
// TraceEvent represents a single step in the message processing pipeline.
|
|
type TraceEvent struct {
|
|
ID string `json:"id"`
|
|
Timestamp time.Time `json:"timestamp"`
|
|
SessionID string `json:"session_id,omitempty"`
|
|
UserID string `json:"user_id,omitempty"`
|
|
Hop string `json:"hop"` // message_received, intent, subsession, llm_call, tool_call, vision, synthesis, review, response, think
|
|
Label string `json:"label"`
|
|
Status string `json:"status"` // success, error, running
|
|
Detail string `json:"detail,omitempty"`
|
|
DurationMs int64 `json:"duration_ms,omitempty"`
|
|
Data map[string]interface{} `json:"data,omitempty"`
|
|
}
|
|
|
|
var (
|
|
traceMu sync.Mutex
|
|
traceEvents []TraceEvent
|
|
traceMax = 200
|
|
)
|
|
|
|
// AddTraceEvent appends a trace event to the in-memory ring buffer.
|
|
func AddTraceEvent(hop, sessionID, userID, label, status, detail string, durationMs int64, data map[string]interface{}) {
|
|
traceMu.Lock()
|
|
defer traceMu.Unlock()
|
|
ev := TraceEvent{
|
|
ID: fmt.Sprintf("%s-%d", hop, time.Now().UnixNano()),
|
|
Timestamp: time.Now(),
|
|
SessionID: sessionID,
|
|
UserID: userID,
|
|
Hop: hop,
|
|
Label: label,
|
|
Status: status,
|
|
Detail: detail,
|
|
DurationMs: durationMs,
|
|
Data: data,
|
|
}
|
|
traceEvents = append(traceEvents, ev)
|
|
if len(traceEvents) > traceMax {
|
|
traceEvents = traceEvents[len(traceEvents)-traceMax:]
|
|
}
|
|
}
|
|
|
|
// GetTraceEvents returns recent trace events, optionally filtered by session.
|
|
func GetTraceEvents(sessionID string, limit int) []TraceEvent {
|
|
traceMu.Lock()
|
|
defer traceMu.Unlock()
|
|
if limit <= 0 || limit > len(traceEvents) {
|
|
limit = len(traceEvents)
|
|
}
|
|
result := make([]TraceEvent, 0)
|
|
// Return newest first.
|
|
for i := len(traceEvents) - 1; i >= 0 && len(result) < limit; i-- {
|
|
ev := traceEvents[i]
|
|
if sessionID == "" || ev.SessionID == sessionID {
|
|
result = append(result, ev)
|
|
}
|
|
}
|
|
return result
|
|
}
|
|
|
|
// registerTraceEndpoint adds the trace events API.
|
|
func registerTraceEndpoint(mux *http.ServeMux) {
|
|
mux.HandleFunc("/api/v1/trace/events", func(w http.ResponseWriter, r *http.Request) {
|
|
sessionID := r.URL.Query().Get("session_id")
|
|
limit := 100
|
|
if l, err := strconv.Atoi(r.URL.Query().Get("limit")); err == nil && l > 0 && l <= 500 {
|
|
limit = l
|
|
}
|
|
events := GetTraceEvents(sessionID, limit)
|
|
w.Header().Set("Content-Type", "application/json")
|
|
json.NewEncoder(w).Encode(map[string]interface{}{
|
|
"events": events,
|
|
"total": len(events),
|
|
})
|
|
})
|
|
}
|