feat: Agent 全栈升级——模块化重构、Hooks/Skills/Memory/SubAgent/Team 子系统、审计与任务持久化

架构重构:
  - Agent Runtime 由单文件拆为 runtime/ 目录 12 模块(熔断/流式执行/Token预算/文件缓存/权限等)
  - Agent Tools 由单文件拆为 tools/ 目录 20+ 模块(filesystem/astro/memory/skill/subagent/team 等)
  - 解析器体系重构(common.rs 836行变更),各解析器同步升级
  - Download 服务重构(562行),反爬策略强化
  - LLM 客户端重构(266行),流式调用优化

  新子系统:
  - Hooks 生命周期系统(9种事件类型,PreToolUse/PostToolUse 支持输入输出拦截)
  - Skills 双层加载系统(system-reminder 轻量注入 + LoadSkillTool 按需加载,notify 文件监听热更新)
  - Memory 项目记忆管理(类型/提取/去重/衰减/保活/选择策略/护栏 7 模块)
  - SubAgent 上下文隔离子代理运行器(独立 ReAct 循环 + Hook 管道)
  - Team 多智能体团队协作(文件 inbox 通信、lead/teammate 协调)
  - TaskBoard DAG 任务依赖管理
  - Trajectory 会话轨迹、Terminal 终止信号、Autonomous 自主模式、Background 异步通知

  数据库:
  - agent_tasks 表(DAG 依赖模式,blocked_by JSON 数组)
  - agent_audit_log 表(工具调用审计:名称/状态/耗时/输出预览)
  - agent_identity 迁移(消息/审计/任务的 agent_name 归属,agent_team_members 团队注册表)

  API:
  - GET /chat/metrics 聚合指标端点
  - GET /chat/sessions/:id/audit 会话审计查询
  - GET /chat/questions + POST /chat/answer 人机交互问答

  工程:
  - 新增依赖:serde_yaml、notify、glob、walkdir、lru
  - Skills 目录含 methodology/plotting/presentation 三个初始 SKILL.md
  - CLAUDE.md 完整项目架构文档
This commit is contained in:
fmq
2026-06-17 00:14:02 +08:00
parent b1fb884f21
commit 49784739fa
113 changed files with 20253 additions and 2869 deletions
+261 -30
View File
@@ -14,7 +14,7 @@ use serde::{Deserialize, Serialize};
use sqlx::Row;
use std::convert::Infallible;
use std::sync::Arc;
use tracing::{info, error};
use tracing::{error, info};
use super::AppState;
use crate::agent::runtime::{AgentRuntime, AgentStreamEvent};
@@ -32,7 +32,10 @@ pub async fn chat_agent(
State(state): State<Arc<AppState>>,
Json(req): Json<AgentChatRequest>,
) -> Result<Sse<impl Stream<Item = Result<Event, Infallible>>>, (StatusCode, String)> {
info!("接收到智能体对话请求: question='{}', session_id={:?}", req.question, req.session_id);
info!(
"接收到智能体对话请求: question='{}', session_id={:?}",
req.question, req.session_id
);
let runtime = AgentRuntime::new(Arc::clone(&state));
let (tx, mut rx) = tokio::sync::mpsc::unbounded_channel::<AgentStreamEvent>();
@@ -102,24 +105,30 @@ pub async fn list_sessions(
FROM agent_sessions \
WHERE deleted_at IS NULL \
ORDER BY updated_at DESC \
LIMIT ? OFFSET ?"
LIMIT ? OFFSET ?",
)
.bind(limit)
.bind(offset)
.fetch_all(&state.db)
.await
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, format!("查询会话列表失败: {}", e)))?;
.map_err(|e| {
(
StatusCode::INTERNAL_SERVER_ERROR,
format!("查询会话列表失败: {}", e),
)
})?;
let sessions: Vec<SessionSummary> = rows.iter().map(|r| {
SessionSummary {
let sessions: Vec<SessionSummary> = rows
.iter()
.map(|r| SessionSummary {
session_id: r.get(0),
title: r.get(1),
model: r.get(2),
turn_count: r.get(3),
created_at: r.get(4),
updated_at: r.get(5),
}
}).collect();
})
.collect();
Ok(Json(sessions))
}
@@ -156,12 +165,17 @@ pub async fn get_session(
let session_row = sqlx::query(
"SELECT session_id, title, model, turn_count, created_at, updated_at \
FROM agent_sessions \
WHERE session_id = ? AND deleted_at IS NULL"
WHERE session_id = ? AND deleted_at IS NULL",
)
.bind(&session_id)
.fetch_optional(&state.db)
.await
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, format!("查询会话失败: {}", e)))?
.map_err(|e| {
(
StatusCode::INTERNAL_SERVER_ERROR,
format!("查询会话失败: {}", e),
)
})?
.ok_or((StatusCode::NOT_FOUND, format!("会话 {} 不存在", session_id)))?;
let session = SessionSummary {
@@ -185,24 +199,27 @@ pub async fn get_session(
.await
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, format!("查询消息列表失败: {}", e)))?;
let messages: Vec<MessageRecord> = msg_rows.iter().map(|r| {
let tool_calls_json: Option<String> = r.get(6);
let metadata_json: Option<String> = r.get(9);
let messages: Vec<MessageRecord> = msg_rows
.iter()
.map(|r| {
let tool_calls_json: Option<String> = r.get(6);
let metadata_json: Option<String> = r.get(9);
MessageRecord {
id: r.get(0),
turn_index: r.get(1),
step_index: r.get(2),
role: r.get(3),
content: r.get(4),
thought: r.get(5),
tool_calls: tool_calls_json.and_then(|s| serde_json::from_str(&s).ok()),
tool_call_id: r.get(7),
token_count: r.get(8),
metadata: metadata_json.and_then(|s| serde_json::from_str(&s).ok()),
created_at: r.get(10),
}
}).collect();
MessageRecord {
id: r.get(0),
turn_index: r.get(1),
step_index: r.get(2),
role: r.get(3),
content: r.get(4),
thought: r.get(5),
tool_calls: tool_calls_json.and_then(|s| serde_json::from_str(&s).ok()),
tool_call_id: r.get(7),
token_count: r.get(8),
metadata: metadata_json.and_then(|s| serde_json::from_str(&s).ok()),
created_at: r.get(10),
}
})
.collect();
Ok(Json(SessionDetail { session, messages }))
}
@@ -223,11 +240,16 @@ pub async fn delete_session(
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, format!("删除会话失败: {}", e)))?;
if result.rows_affected() == 0 {
return Err((StatusCode::NOT_FOUND, format!("会话 {} 不存在或已删除", session_id)));
return Err((
StatusCode::NOT_FOUND,
format!("会话 {} 不存在或已删除", session_id),
));
}
info!("会话已软删除: {}", session_id);
Ok(Json(serde_json::json!({ "status": "deleted", "session_id": session_id })))
Ok(Json(
serde_json::json!({ "status": "deleted", "session_id": session_id }),
))
}
// ── POST /api/chat/sessions/:id/stop ──
@@ -240,5 +262,214 @@ pub async fn stop_agent(
cancelled.insert(session_id.clone());
}
info!("已接收并记录手动中止请求,会话 ID: {}", session_id);
Ok(Json(serde_json::json!({ "status": "stopping", "session_id": session_id })))
Ok(Json(
serde_json::json!({ "status": "stopping", "session_id": session_id }),
))
}
// ── GET /api/chat/metrics ──
// 返回聚合的智能体运行指标
#[derive(Debug, Serialize)]
pub struct AgentMetricsResponse {
pub total_sessions: i64,
pub total_tool_calls: i64,
pub tool_call_breakdown: serde_json::Value,
pub avg_steps_per_session: f64,
pub error_rate: f64,
}
pub async fn get_agent_metrics(
State(state): State<Arc<AppState>>,
) -> Result<Json<AgentMetricsResponse>, (StatusCode, String)> {
// 总会话数
let total_sessions: i64 =
sqlx::query_scalar("SELECT COUNT(*) FROM agent_sessions WHERE deleted_at IS NULL")
.fetch_one(&state.db)
.await
.unwrap_or(0);
// 工具调用统计(从审计日志聚合)
let total_tool_calls: i64 =
sqlx::query_scalar("SELECT COUNT(*) FROM agent_audit_log WHERE status IN ('OK', 'FAIL')")
.fetch_one(&state.db)
.await
.unwrap_or(0);
// 各工具调用次数
let breakdown_rows: Vec<(String, i64)> = sqlx::query_as(
"SELECT COALESCE(tool_name, 'unknown'), COUNT(*) as cnt \
FROM agent_audit_log \
WHERE status IN ('OK', 'FAIL') \
GROUP BY tool_name \
ORDER BY cnt DESC",
)
.fetch_all(&state.db)
.await
.unwrap_or_default();
let tool_call_breakdown: serde_json::Value = breakdown_rows
.iter()
.map(|(name, cnt)| serde_json::json!({ name: cnt }))
.fold(serde_json::json!({}), |mut acc, v| {
if let serde_json::Value::Object(map) = &mut acc {
if let serde_json::Value::Object(v_map) = v {
for (k, val) in v_map {
map.insert(k.clone(), val.clone());
}
}
}
acc
});
// 平均步数
let avg_steps: f64 = sqlx::query_scalar(
"SELECT COALESCE(AVG(CAST(turn_count AS REAL)), 0.0) \
FROM agent_sessions WHERE deleted_at IS NULL",
)
.fetch_one(&state.db)
.await
.unwrap_or(0.0);
// 错误率
let total_errors: i64 =
sqlx::query_scalar("SELECT COUNT(*) FROM agent_audit_log WHERE status = 'FAIL'")
.fetch_one(&state.db)
.await
.unwrap_or(0);
let error_rate = if total_tool_calls > 0 {
total_errors as f64 / total_tool_calls as f64
} else {
0.0
};
Ok(Json(AgentMetricsResponse {
total_sessions,
total_tool_calls,
tool_call_breakdown,
avg_steps_per_session: avg_steps,
error_rate,
}))
}
// ── GET /api/chat/sessions/:id/audit ──
// 返回指定会话的审计日志
#[derive(Debug, Serialize)]
pub struct AuditLogEntry {
pub id: i64,
pub step: i32,
pub tool_name: Option<String>,
pub status: String,
pub elapsed_ms: i32,
pub output_preview: Option<String>,
pub created_at: String,
}
pub async fn get_session_audit(
State(state): State<Arc<AppState>>,
Path(session_id): Path<String>,
) -> Result<Json<Vec<AuditLogEntry>>, (StatusCode, String)> {
let rows = sqlx::query(
"SELECT id, step, tool_name, status, elapsed_ms, output_preview, created_at \
FROM agent_audit_log \
WHERE session_id = ? \
ORDER BY id ASC",
)
.bind(&session_id)
.fetch_all(&state.db)
.await
.map_err(|e| {
(
StatusCode::INTERNAL_SERVER_ERROR,
format!("查询审计日志失败: {}", e),
)
})?;
let entries: Vec<AuditLogEntry> = rows
.iter()
.map(|r| AuditLogEntry {
id: r.get(0),
step: r.get(1),
tool_name: r.get(2),
status: r.get(3),
elapsed_ms: r.get(4),
output_preview: r.get(5),
created_at: r.get(6),
})
.collect();
Ok(Json(entries))
}
// ── POST /api/chat/answer_question ──
// 用户回答 Agent 的提问(ask_user 工具配合使用)
#[derive(Debug, Deserialize)]
pub struct AnswerQuestionRequest {
pub question_id: String,
pub answers: Vec<String>,
pub free_text: Option<String>,
}
pub async fn answer_question(
State(state): State<Arc<AppState>>,
Json(req): Json<AnswerQuestionRequest>,
) -> Result<Json<serde_json::Value>, (StatusCode, String)> {
use crate::agent::tools::ask_user::UserAnswer;
let mut pending = match state.pending_questions.lock() {
Ok(p) => p,
Err(_) => {
return Err((
StatusCode::INTERNAL_SERVER_ERROR,
"服务器内部状态异常,请稍后重试".to_string(),
));
}
};
let question_id = req.question_id.clone();
match pending.remove(&question_id) {
Some(pq) => {
let answer = UserAnswer {
question_id: question_id.clone(),
answers: req.answers.clone(),
free_text: req.free_text.clone(),
};
match pq.answer_tx.send(answer) {
Ok(()) => {
info!("[API] 用户回答了问题: id={}", question_id);
Ok(Json(
serde_json::json!({"status": "ok", "question_id": question_id}),
))
}
Err(_) => Err((StatusCode::GONE, "问题已超时或已被回答".to_string())),
}
}
None => Err((
StatusCode::NOT_FOUND,
format!("未找到待回答问题: {}", question_id),
)),
}
}
// ── GET /api/chat/pending_questions ──
// 获取当前待回答的问题(前端轮询或初始化)
pub async fn get_pending_questions(
State(state): State<Arc<AppState>>,
) -> Json<Vec<serde_json::Value>> {
let pending = match state.pending_questions.lock() {
Ok(p) => p,
Err(_) => return Json(Vec::new()),
};
let questions: Vec<serde_json::Value> = pending
.iter()
.map(|(id, pq)| {
serde_json::from_str::<serde_json::Value>(&pq.question_json)
.unwrap_or(serde_json::json!({"question_id": id}))
})
.collect();
Json(questions)
}