feat: Phase 1+2 架构进化 — 连续思考链/主动消息决策/情感状态机/离线自主思考 (86文件)

Phase 1 (基础设施):
- ThinkChain 思考链连续性 + 差异化思考提示词 (persistent)
- AutonomousToolPolicy 工具安全策略 (safe/unsafe/conditional)
- MessageScheduler 自适应消息节奏 (Idle/Available/Busy)
- SessionEnrichmentStore 渐进式上下文丰富 (5层)
- ConversationBus 事件总线 + ResponseCache (dedup)
- pkg/logger 统一日志 + 所有 handler 替换 fmt.Printf
- NPE 守卫/链路优化/数据库表修复/Go workspace

Phase 2 (人格交互):
- EmotionState/EmotionTracker 情感状态机 (5种心情, 情绪衰减)
- ProactiveGuard 主动消息多维决策 (静默时段/紧急度/频率/校验)
- Gateway↔ai-core 在线状态感知链路 (presence notification)
- 离线思考频率控制 + 重连问候 + 离线消息排队

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
2026-05-23 15:25:12 +08:00
parent b123a36aae
commit 87214b9441
86 changed files with 3085 additions and 582 deletions
@@ -4,7 +4,7 @@ import (
"database/sql"
"encoding/json"
"fmt"
"log"
"github.com/yourname/cyrene-ai/pkg/logger"
"time"
)
@@ -47,7 +47,7 @@ func NewAutomationStore(db *sql.DB) (*AutomationStore, error) {
return nil, fmt.Errorf("自动化表迁移失败: %w", err)
}
log.Println("[AutomationStore] 自动化持久化存储已初始化")
logger.Println("[AutomationStore] 自动化持久化存储已初始化")
return store, nil
}
@@ -4,7 +4,7 @@ import (
"database/sql"
"encoding/json"
"fmt"
"log"
"github.com/yourname/cyrene-ai/pkg/logger"
"time"
)
@@ -60,7 +60,7 @@ func NewBriefingStore(db *sql.DB) (*BriefingStore, error) {
return nil, fmt.Errorf("简报表迁移失败: %w", err)
}
log.Println("[BriefingStore] 简报持久化存储已初始化")
logger.Println("[BriefingStore] 简报持久化存储已初始化")
return store, nil
}
+2 -2
View File
@@ -3,7 +3,7 @@ package store
import (
"database/sql"
"fmt"
"log"
"github.com/yourname/cyrene-ai/pkg/logger"
"time"
)
@@ -33,7 +33,7 @@ func NewFileStore(db *sql.DB) (*FileStore, error) {
return nil, fmt.Errorf("文件表迁移失败: %w", err)
}
log.Println("[FileStore] 文件持久化存储已初始化")
logger.Println("[FileStore] 文件持久化存储已初始化")
return store, nil
}
@@ -4,7 +4,7 @@ import (
"crypto/rand"
"database/sql"
"fmt"
"log"
"github.com/yourname/cyrene-ai/pkg/logger"
"strings"
"time"
"unicode/utf8"
@@ -73,7 +73,7 @@ func NewKnowledgeStore(db *sql.DB) (*KnowledgeStore, error) {
return nil, fmt.Errorf("知识库表迁移失败: %w", err)
}
log.Println("[KnowledgeStore] 知识库持久化存储已初始化")
logger.Println("[KnowledgeStore] 知识库持久化存储已初始化")
return store, nil
}
@@ -133,7 +133,7 @@ func (s *KnowledgeStore) migrate() error {
// 尝试创建 GIN 索引(可能因权限或扩展问题失败,但不影响功能)
_, err := s.db.Exec(`CREATE INDEX IF NOT EXISTS idx_kc_tsv_gin ON knowledge_chunks USING GIN(tsv)`)
if err != nil {
log.Printf("[KnowledgeStore] ⚠ GIN索引创建失败(将使用ILIKE降级搜索): %v", err)
logger.Printf("[KnowledgeStore] ⚠ GIN索引创建失败(将使用ILIKE降级搜索): %v", err)
}
return nil
@@ -260,7 +260,7 @@ func (s *KnowledgeStore) AddDocument(doc *KnowledgeDocument) error {
// 更新知识库统计
if err := s.updateKBStats(doc.KBID); err != nil {
log.Printf("[KnowledgeStore] 更新知识库统计失败: %v", err)
logger.Printf("[KnowledgeStore] 更新知识库统计失败: %v", err)
}
return nil
@@ -340,7 +340,7 @@ func (s *KnowledgeStore) DeleteDocument(id string) error {
// 更新知识库统计
if err := s.updateKBStats(kbID); err != nil {
log.Printf("[KnowledgeStore] 更新知识库统计失败: %v", err)
logger.Printf("[KnowledgeStore] 更新知识库统计失败: %v", err)
}
return nil
@@ -452,12 +452,12 @@ func (s *KnowledgeStore) ChunkDocument(docID string) (int, error) {
// 更新文档的分块计数
if err := s.UpdateDocumentChunkCount(docID, len(chunks)); err != nil {
log.Printf("[KnowledgeStore] 更新文档分块计数失败: %v", err)
logger.Printf("[KnowledgeStore] 更新文档分块计数失败: %v", err)
}
// 更新知识库统计
if err := s.updateKBStats(doc.KBID); err != nil {
log.Printf("[KnowledgeStore] 更新知识库统计失败: %v", err)
logger.Printf("[KnowledgeStore] 更新知识库统计失败: %v", err)
}
return len(chunks), nil
@@ -474,7 +474,7 @@ func (s *KnowledgeStore) SearchChunks(kbID, query string, limit int) ([]SearchCh
// 尝试使用 PostgreSQL 全文搜索
results, err := s.searchWithFullText(kbID, query, limit)
if err != nil {
log.Printf("[KnowledgeStore] 全文搜索失败,降级为ILIKE: %v", err)
logger.Printf("[KnowledgeStore] 全文搜索失败,降级为ILIKE: %v", err)
// 降级为 ILIKE
results, err = s.searchWithILike(kbID, query, limit)
if err != nil {
@@ -3,7 +3,7 @@ package store
import (
"database/sql"
"fmt"
"log"
"github.com/yourname/cyrene-ai/pkg/logger"
"time"
)
@@ -35,7 +35,7 @@ func NewReminderStore(db *sql.DB) (*ReminderStore, error) {
return nil, fmt.Errorf("提醒表迁移失败: %w", err)
}
log.Println("[ReminderStore] 提醒持久化存储已初始化")
logger.Println("[ReminderStore] 提醒持久化存储已初始化")
return store, nil
}
@@ -3,7 +3,7 @@ package store
import (
"database/sql"
"fmt"
"log"
"github.com/yourname/cyrene-ai/pkg/logger"
"time"
_ "github.com/lib/pq"
@@ -24,6 +24,7 @@ type Message struct {
ID int `json:"id"`
SessionID string `json:"session_id"`
Role string `json:"role"`
MsgType string `json:"msg_type"`
Content string `json:"content"`
CreatedAt time.Time `json:"created_at"`
}
@@ -60,7 +61,7 @@ func NewSessionStore(databaseURL string) (*SessionStore, error) {
return nil, fmt.Errorf("数据库迁移失败: %w", err)
}
log.Println("[SessionStore] PostgreSQL 持久化存储已初始化")
logger.Println("[SessionStore] PostgreSQL 持久化存储已初始化")
return store, nil
}
@@ -82,11 +83,15 @@ func (s *SessionStore) migrate() error {
id SERIAL PRIMARY KEY,
session_id VARCHAR(64) REFERENCES sessions(id) ON DELETE CASCADE,
role VARCHAR(16) NOT NULL,
msg_type VARCHAR(16) DEFAULT 'chat',
content TEXT NOT NULL,
created_at TIMESTAMP DEFAULT NOW()
)`,
`CREATE INDEX IF NOT EXISTS idx_messages_session_id ON messages(session_id)`,
`CREATE INDEX IF NOT EXISTS idx_messages_created_at ON messages(session_id, created_at)`,
// 为已存在的数据库添加 msg_type 列 (Phase 0.1)
`ALTER TABLE messages ADD COLUMN IF NOT EXISTS msg_type VARCHAR(16) DEFAULT 'chat'`,
}
for _, q := range queries {
@@ -200,10 +205,10 @@ func (s *SessionStore) DeleteAllUserSessions(userID string) error {
}
// AddMessage 添加一条消息到会话
func (s *SessionStore) AddMessage(sessionID, role, content string) error {
func (s *SessionStore) AddMessage(sessionID, role, msgType, content string) error {
_, err := s.db.Exec(
`INSERT INTO messages (session_id, role, content) VALUES ($1, $2, $3)`,
sessionID, role, content,
`INSERT INTO messages (session_id, role, msg_type, content) VALUES ($1, $2, $3, $4)`,
sessionID, role, msgType, content,
)
if err != nil {
return fmt.Errorf("添加消息失败: %w", err)
@@ -221,7 +226,7 @@ func (s *SessionStore) GetMessages(sessionID string, limit, offset int) ([]Messa
}
rows, err := s.db.Query(
`SELECT id, session_id, role, content, created_at
`SELECT id, session_id, role, COALESCE(msg_type, 'chat'), content, created_at
FROM messages WHERE session_id = $1
ORDER BY created_at ASC
LIMIT $2 OFFSET $3`,
@@ -235,7 +240,7 @@ func (s *SessionStore) GetMessages(sessionID string, limit, offset int) ([]Messa
var messages []Message
for rows.Next() {
var msg Message
if err := rows.Scan(&msg.ID, &msg.SessionID, &msg.Role, &msg.Content, &msg.CreatedAt); err != nil {
if err := rows.Scan(&msg.ID, &msg.SessionID, &msg.Role, &msg.MsgType, &msg.Content, &msg.CreatedAt); err != nil {
return nil, fmt.Errorf("扫描消息行失败: %w", err)
}
messages = append(messages, msg)
@@ -262,6 +267,7 @@ type SearchResult struct {
SessionID string `json:"session_id"`
SessionTitle string `json:"session_title"`
Role string `json:"role"`
MsgType string `json:"msg_type"`
Content string `json:"content"`
CreatedAt time.Time `json:"created_at"`
}
@@ -287,7 +293,7 @@ func (s *SessionStore) SearchMessages(userID, query string, limit, offset int) (
// 分页查询,关联 sessions 获取会话标题
rows, err := s.db.Query(
`SELECT m.id, m.session_id, COALESCE(s.title, '') AS session_title, m.role, m.content, m.created_at
`SELECT m.id, m.session_id, COALESCE(s.title, '') AS session_title, m.role, COALESCE(m.msg_type, 'chat'), m.content, m.created_at
FROM messages m
JOIN sessions s ON m.session_id = s.id
WHERE s.user_id = $1 AND m.content ILIKE '%' || $2 || '%'
@@ -303,7 +309,7 @@ func (s *SessionStore) SearchMessages(userID, query string, limit, offset int) (
var results []SearchResult
for rows.Next() {
var r SearchResult
if err := rows.Scan(&r.MessageID, &r.SessionID, &r.SessionTitle, &r.Role, &r.Content, &r.CreatedAt); err != nil {
if err := rows.Scan(&r.MessageID, &r.SessionID, &r.SessionTitle, &r.Role, &r.MsgType, &r.Content, &r.CreatedAt); err != nil {
return nil, 0, fmt.Errorf("扫描搜索结果行失败: %w", err)
}
results = append(results, r)