重构:架构重组 + 6轮缺陷修复

Phase 1: 工具模块统一
- 新建 tools/executor.rs — 统一工具执行器,消除 main/worker/daemon 三处重复
- tools/builtin.rs → tools/registry.rs
- tools/mod.rs 新增 build_tools_list() / build_env_map(),daemon/main 共用

Phase 2: 主模块重组 context/ → core/
- core/pipeline.rs — 消息处理流水线 (从 main.rs 抽取 listen_loop)
- core/session.rs — ChatSession 会话管理
- core/context.rs — 上下文构建 + token 估算
- core/summary.rs — 摘要管理 (溢出/空闲触发)
- core/memory.rs — MemoryStore 长期记忆
- main.rs 精简至 ~200 行

Phase 3: listen 模式全局会话锁 + Pipeline 收尾
- PipelineContext 持有全局 conversation_lock,所有消息串行处理防串话

Phase 4: wechat/ → channel/ 重命名

缺陷修复:
- daemon send_frame 失败后消息重回 waiting 队列
- daemon 审批通过后按 code_hash 精确执行,不重跑 LLM
- listen 模式执行器从 session.current_user_id 动态获取用户
- listen 模式 LLM 用量统计传入实际 user_id
- send_text 响应校验改用 serde_json::Value 宽松解析
- pi_subagent 工具 (JSON 事件流模式, RiskLevel::High, timeout clamp 1-300)
- listen 审批超时 300s→60s,cancel_by_code 精确保护
- approval.rs 新增 cancel_by_code 方法同步 DB
This commit is contained in:
2026-06-04 16:18:06 +08:00
parent 8dc232ebab
commit af0620aa2d
25 changed files with 1405 additions and 890 deletions
+187
View File
@@ -0,0 +1,187 @@
use crate::core::session::{ChatSession, SummaryEntry, SummaryReason};
use crate::llm::conversation::Summarizer;
use crate::llm::types::Message;
use std::sync::Arc;
use tokio::sync::Mutex;
/// 触发溢出摘要:压缩 checkpoint 到当前位置之间的新消息
pub async fn trigger_overflow_summary(
session: &Arc<Mutex<ChatSession>>,
summarizer: Option<&Summarizer>,
) -> bool {
let mut s = session.lock().await;
if s.messages.is_empty() {
return false;
}
let prev_checkpoint = s.checkpoint;
let end = s.messages.len();
// checkpoint 始终在 user+assistant 对边界,直接用它作为摘要起点
let start = prev_checkpoint;
if start >= end {
return false;
}
let to_summarize: Vec<_> = s.messages[start..end]
.iter()
.take(40) // 最多 40 条
.cloned()
.collect();
if to_summarize.is_empty() {
return false;
}
let summary_text = generate_summary(&to_summarize, summarizer).await;
s.summaries.push(SummaryEntry {
checkpoint: prev_checkpoint,
text: summary_text.clone(),
reason: SummaryReason::Overflow,
created_at: chrono::Utc::now(),
});
// 持久化到数据库
s.save_summary_to_db(&summary_text, "overflow", to_summarize.len() as i32)
.await;
s.checkpoint = end;
// 清理太旧的消息(checkpoint 之前的保留最近 100 条用于调试)
if s.checkpoint > 200 {
let keep = s.checkpoint - 100;
s.messages.drain(0..keep);
// 调整 checkpoint 和 summary checkpoint
let shift = keep;
s.checkpoint -= shift;
for sum in &mut s.summaries {
if sum.checkpoint >= shift {
sum.checkpoint -= shift;
}
}
}
true
}
/// 触发空闲摘要(不注入,保存到 summaries 供 LLM 工具查询)
pub async fn trigger_idle_summary(
session: &Arc<Mutex<ChatSession>>,
summarizer: Option<&Summarizer>,
) {
let mut s = session.lock().await;
if s.messages.is_empty() {
return;
}
let summary_text = generate_summary(&s.messages, summarizer).await;
let cp = s.messages.len();
s.summaries.push(SummaryEntry {
checkpoint: cp,
text: summary_text.clone(),
reason: SummaryReason::Timeout,
created_at: chrono::Utc::now(),
});
let msg_count = cp as i32;
s.save_summary_to_db(&summary_text, "timeout", msg_count)
.await;
s.checkpoint = s.messages.len();
// 清理旧消息(checkpoint 之前的全部清除)
if s.checkpoint > 0 {
let shift = s.checkpoint;
s.messages.drain(0..shift);
s.checkpoint = 0;
for sum in &mut s.summaries {
if sum.checkpoint >= shift {
sum.checkpoint -= shift;
}
}
}
}
/// 生成摘要:优先使用 LLM,不可用时回退到简单截断
async fn generate_summary(messages: &[Message], summarizer: Option<&Summarizer>) -> String {
if let Some(summarizer) = summarizer {
if messages.len() >= 3 {
let prompt = build_summary_prompt(messages);
match summarizer(prompt).await {
Ok(text) if !text.is_empty() => {
tracing::info!("LLM 摘要: {:.100}...", text);
return text;
}
_ => tracing::warn!("LLM 摘要失败,回退到简单截断"),
}
}
}
summarize_messages(messages)
}
/// 构建给 LLM 的摘要 prompt
fn build_summary_prompt(messages: &[Message]) -> String {
let convo: String = messages
.iter()
.filter(|m| {
m.role == crate::llm::types::Role::User
|| m.role == crate::llm::types::Role::Assistant
})
.map(|m| {
let role = if m.role == crate::llm::types::Role::User {
"用户"
} else {
"助手"
};
let content: String = m.content.chars().take(200).collect();
format!("{}: {}", role, content)
})
.collect::<Vec<_>>()
.join("\n");
format!(
"请将以下对话压缩为简短摘要,保留关键事实、用户偏好、决定和待办事项。只输出摘要,不要其他内容。\n\n对话:\n{}",
convo
)
}
/// 简单的摘要生成(提取最近 N 条消息的关键内容)
fn summarize_messages(messages: &[Message]) -> String {
// 简单实现:提取用户消息的前 80 个字符作为摘要
let lines: Vec<String> = messages
.iter()
.filter(|m| {
m.role == crate::llm::types::Role::User
|| m.role == crate::llm::types::Role::Assistant
})
.map(|m| {
let role = match m.role {
crate::llm::types::Role::User => "用户",
crate::llm::types::Role::Assistant => "助手",
_ => "",
};
let preview: String = m.content.chars().take(80).collect();
format!("{}: {}", role, preview)
})
.collect();
if lines.is_empty() {
"暂无历史对话".to_string()
} else {
let result = format!(
"历史对话摘要({} 条消息):\n{}",
lines.len(),
lines.join("\n")
);
if result.chars().count() > 2000 {
format!(
"{}...(已截断)",
result.chars().take(2000).collect::<String>()
)
} else {
result
}
}
}