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
+43 -11
View File
@@ -4,9 +4,10 @@ import (
"context"
"crypto/rand"
"fmt"
"log"
"github.com/yourname/cyrene-ai/pkg/logger"
"sync"
"github.com/yourname/cyrene-ai/ai-core/internal/bus"
"github.com/yourname/cyrene-ai/ai-core/internal/llm"
"github.com/yourname/cyrene-ai/ai-core/internal/model"
)
@@ -17,6 +18,7 @@ type Manager struct {
mu sync.RWMutex
providers map[model.SubSessionType]Provider
llmClient LLMClient
eventBus bus.Bus
}
// NewManager 创建子会话管理器
@@ -27,12 +29,24 @@ func NewManager(llmClient LLMClient) *Manager {
}
}
// SetBus sets the event bus (optional, for Phase 1).
func (m *Manager) SetBus(b bus.Bus) {
m.eventBus = b
}
func (m *Manager) getBus() bus.Bus {
if m.eventBus == nil {
return &bus.NopBus{}
}
return m.eventBus
}
// Register 注册子会话提供者
func (m *Manager) Register(provider Provider) {
m.mu.Lock()
defer m.mu.Unlock()
m.providers[provider.Type()] = provider
log.Printf("[subsession] 注册子会话提供者: %s (优先级=%d, 超时=%v)", provider.Type(), provider.Priority(), provider.Timeout())
logger.Printf("[subsession] 注册子会话提供者: %s (优先级=%d, 超时=%v)", provider.Type(), provider.Priority(), provider.Timeout())
}
// RegisterWithOverride 注册或覆盖子会话提供者
@@ -40,7 +54,7 @@ func (m *Manager) RegisterWithOverride(provider Provider) {
m.mu.Lock()
defer m.mu.Unlock()
m.providers[provider.Type()] = provider
log.Printf("[subsession] 注册(覆盖)子会话提供者: %s (优先级=%d, 超时=%v)", provider.Type(), provider.Priority(), provider.Timeout())
logger.Printf("[subsession] 注册(覆盖)子会话提供者: %s (优先级=%d, 超时=%v)", provider.Type(), provider.Priority(), provider.Timeout())
}
// GetProvider 获取指定类型的 Provider
@@ -82,7 +96,7 @@ func (m *Manager) Dispatch(
for _, provider := range providers {
if !provider.CanHandle(ctx, intent, userMessage) {
log.Printf("[subsession] 跳过子会话 %s: CanHandle 返回 false", provider.Type())
logger.Printf("[subsession] 跳过子会话 %s: CanHandle 返回 false", provider.Type())
continue
}
@@ -91,11 +105,16 @@ func (m *Manager) Dispatch(
defer wg.Done()
defer func() {
if r := recover(); r != nil {
log.Printf("[subsession] dispatch goroutine panic 恢复 (type=%s): %v", p.Type(), r)
logger.Printf("[subsession] dispatch goroutine panic 恢复 (type=%s): %v", p.Type(), r)
}
}()
result := model.SubSessionResult{Type: p.Type()}
m.getBus().Publish(bus.BusEvent{
Type: bus.EventSubSessionStarted,
Payload: bus.SubSessionPayload{SubType: p.Type(), Status: "started"},
})
// 创建带超时的 context
subCtx, cancel := context.WithTimeout(ctx, p.Timeout())
@@ -105,18 +124,18 @@ func (m *Manager) Dispatch(
llmMessages, err := p.CreateContext(subCtx, params)
if err != nil {
result.Error = fmt.Sprintf("创建上下文失败: %v", err)
log.Printf("[subsession] %s 创建上下文失败: %v", p.Type(), err)
logger.Printf("[subsession] %s 创建上下文失败: %v", p.Type(), err)
resultCh <- result
return
}
log.Printf("[subsession] %s 开始执行 (上下文 %d 条消息)", p.Type(), len(llmMessages))
logger.Printf("[subsession] %s 开始执行 (上下文 %d 条消息)", p.Type(), len(llmMessages))
// 执行子会话
subResult, execErr := p.Execute(subCtx, llmMessages)
if execErr != nil {
result.Error = fmt.Sprintf("执行失败: %v", execErr)
log.Printf("[subsession] %s 执行失败: %v", p.Type(), execErr)
logger.Printf("[subsession] %s 执行失败: %v", p.Type(), execErr)
resultCh <- result
return
}
@@ -125,15 +144,20 @@ func (m *Manager) Dispatch(
select {
case <-subCtx.Done():
result.Error = "子会话超时"
log.Printf("[subsession] %s 超时 (limit=%v)", p.Type(), p.Timeout())
logger.Printf("[subsession] %s 超时 (limit=%v)", p.Type(), p.Timeout())
default:
if subResult != nil {
result = *subResult
result.Type = p.Type()
log.Printf("[subsession] %s 完成: 摘要=%s", p.Type(), truncate(result.Summary, 50))
logger.Printf("[subsession] %s 完成: 摘要=%s", p.Type(), truncate(result.Summary, 50))
}
}
m.getBus().Publish(bus.BusEvent{
Type: bus.EventSubSessionCompleted,
Payload: bus.SubSessionPayload{SubType: p.Type(), Status: resultSummaryStatus(result), Summary: result.Summary, Details: result.Details},
})
resultCh <- result
}(provider)
}
@@ -142,7 +166,7 @@ func (m *Manager) Dispatch(
go func() {
defer func() {
if r := recover(); r != nil {
log.Printf("[subsession] wait goroutine panic 恢复: %v", r)
logger.Printf("[subsession] wait goroutine panic 恢复: %v", r)
}
}()
wg.Wait()
@@ -159,6 +183,14 @@ func generateID() string {
return fmt.Sprintf("sub-%x", b)
}
// resultSummaryStatus returns "completed" or "failed" for bus events.
func resultSummaryStatus(r model.SubSessionResult) string {
if r.Error != "" {
return "failed"
}
return "completed"
}
// truncate 截断字符串
func truncate(s string, maxLen int) string {
runes := []rune(s)