feat: 全链路优化 — 死锁修复、MD3主题、上下文持久化、群聊自然化、打字状态、知识库
**死锁根因修复** - 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>
This commit is contained in:
@@ -32,8 +32,11 @@ type Adapter struct {
|
||||
port string
|
||||
accessToken string
|
||||
remoteURL string // NapCat OneBot WS server URL, used in client mode
|
||||
sendIntervalMs int // minimum interval between consecutive messages
|
||||
selfID string // bot's own QQ number, populated from incoming messages
|
||||
httpURL string // NapCat HTTP API base URL (optional, derived from remoteURL if empty)
|
||||
httpToken_ string // NapCat HTTP API access token (optional, uses accessToken if empty)
|
||||
sendIntervalMs int // minimum interval between consecutive messages
|
||||
lastTypingSent time.Time // last time typing indicator was sent, for min display duration
|
||||
selfID string // bot's own QQ number, populated from incoming messages
|
||||
conn *websocket.Conn
|
||||
connMu sync.Mutex
|
||||
connected bool
|
||||
@@ -64,6 +67,14 @@ func NewAdapter(configID, configName, mode, port, accessToken, remoteURL string,
|
||||
}
|
||||
}
|
||||
|
||||
// SetHTTPConfig sets the optional HTTP API configuration.
|
||||
func (a *Adapter) SetHTTPConfig(url, token string) {
|
||||
a.httpURL = url
|
||||
if token != "" {
|
||||
a.httpToken_ = token
|
||||
}
|
||||
}
|
||||
|
||||
func (a *Adapter) PlatformName() string { return "obv11" }
|
||||
func (a *Adapter) ConfigID() string { return a.configID }
|
||||
func (a *Adapter) ConfigName() string { return a.configName }
|
||||
@@ -97,8 +108,12 @@ func (a *Adapter) SetGroupName(groupID int64, name string) {
|
||||
a.groupNamesMu.Unlock()
|
||||
}
|
||||
|
||||
// httpBase derives the HTTP base URL from the WebSocket remote URL.
|
||||
// httpBase returns the HTTP API base URL.
|
||||
// Uses configured httpURL if set, otherwise derives from WebSocket remoteURL.
|
||||
func (a *Adapter) httpBase() string {
|
||||
if a.httpURL != "" {
|
||||
return a.httpURL
|
||||
}
|
||||
httpBase := strings.Replace(a.remoteURL, "ws://", "http://", 1)
|
||||
httpBase = strings.Replace(httpBase, "wss://", "https://", 1)
|
||||
if idx := strings.LastIndex(httpBase, "/"); idx > 8 {
|
||||
@@ -107,6 +122,48 @@ func (a *Adapter) httpBase() string {
|
||||
return httpBase
|
||||
}
|
||||
|
||||
// httpToken returns the HTTP API access token.
|
||||
func (a *Adapter) httpToken() string {
|
||||
if a.httpToken_ != "" {
|
||||
return a.httpToken_
|
||||
}
|
||||
return a.accessToken
|
||||
}
|
||||
|
||||
// SetTypingStatus sends a typing indicator to a private chat user.
|
||||
// eventType: 1 = typing started, 0 = typing stopped.
|
||||
// This uses the NapCat HTTP API (not standard OneBot v11).
|
||||
// Enforces a minimum 3s display duration.
|
||||
func (a *Adapter) SetTypingStatus(userID int64, eventType int) error {
|
||||
if eventType == 0 {
|
||||
// 确保"正在输入"至少显示了 3 秒
|
||||
if time.Since(a.lastTypingSent) < 3*time.Second {
|
||||
time.Sleep(3*time.Second - time.Since(a.lastTypingSent))
|
||||
}
|
||||
} else {
|
||||
a.lastTypingSent = time.Now()
|
||||
}
|
||||
url := a.httpBase() + "/set_input_status"
|
||||
if token := a.httpToken(); token != "" {
|
||||
url += "?access_token=" + token
|
||||
}
|
||||
body, _ := json.Marshal(map[string]interface{}{
|
||||
"user_id": fmt.Sprintf("%d", userID),
|
||||
"event_type": eventType,
|
||||
})
|
||||
resp, err := http.Post(url, "application/json", strings.NewReader(string(body)))
|
||||
if err != nil {
|
||||
log.Printf("[qq] set_input_status error: %v (url=%s)", err, url)
|
||||
return fmt.Errorf("set_input_status: %w", err)
|
||||
}
|
||||
resp.Body.Close()
|
||||
if resp.StatusCode != 200 {
|
||||
log.Printf("[qq] set_input_status HTTP %d (url=%s)", resp.StatusCode, url)
|
||||
return fmt.Errorf("set_input_status: HTTP %d", resp.StatusCode)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// fetchGroupName tries to resolve a group name via NapCat HTTP API (client mode).
|
||||
func (a *Adapter) fetchGroupName(groupID int64) {
|
||||
if a.mode != "client" || a.remoteURL == "" {
|
||||
|
||||
Reference in New Issue
Block a user