feat(agent): 实现 Agent Runtime 核心胶水层 (Phase 4a)

- 添加 Agent trait、AgentSession、RuntimeBundle、AgentBuilder
- 添加 Plan/Step/StepStatus 任务规划数据结构
- 添加 AgentError 统一错误类型(聚合 LlmError/ToolError/MemoryError)
- 实现 submit_turn 单轮对话流程(含 hook 触发与 cost 累计)
- 扩展 LlmCycle 支持 Arc<dyn LlmProvider>
- 扩展 HookEvent 添加 OnTurnStart/OnTurnEnd
- 更新 roadmap 状态
This commit is contained in:
徐涛
2026-06-11 21:45:28 +08:00
parent 59ec0f5597
commit 2b189880a9
11 changed files with 1025 additions and 19 deletions
+121
View File
@@ -0,0 +1,121 @@
//! 任务规划数据结构 + Phase 4b 任务执行 trait。
//!
//! Phase 4a 范围:仅 `Plan` / `Step` / `StepStatus` 纯数据结构。
//! Phase 4b 在此文件追加 `TaskAgent` trait / `PlanParser` trait / `JsonPlanParser` 参考实现。
//!
//! 设计意图(参见 `docs/7-agent-runtime.md` §3.2.4、§3.3.1):
//!
//! - `StepStatus` 用 enum 而非简单 bool,便于 UI 展示和统计
//! - 状态机单向:`Pending → Running → (Completed | Failed | Skipped)`,不回退
//! - 重试由上层新建 `Plan` 实现,`TaskAgent` 不做自动重试
use crate::agent::error::AgentError;
use crate::llm::types::ChatResponse;
/// 任务规划 —— 一组有序的 Step。
#[derive(Debug)]
pub struct Plan {
/// 规划唯一标识。
pub id: String,
/// 规划目标(人类可读)。
pub goal: String,
/// 步骤列表。
pub steps: Vec<Step>,
}
/// 任务步骤。
#[derive(Debug)]
pub struct Step {
/// 步骤在 Plan 中的位置(0-based)。
pub index: usize,
/// 步骤描述(注入 LLM 作为 user prompt)。
pub description: String,
/// 当前状态。
pub status: StepStatus,
}
impl Step {
/// 创建一个初始为 `Pending` 的步骤。
pub fn new(index: usize, description: impl Into<String>) -> Self {
Self {
index,
description: description.into(),
status: StepStatus::Pending,
}
}
}
/// 步骤状态机。
///
/// 转换路径:`Pending → Running → (Completed | Failed | Skipped)`,单向不回退。
///
/// **不实现 `Clone`**`Failed` 变体携带 `AgentError`,下层 `LlmError` / `MemoryError`
/// 均未派生 `Clone`(保留原始错误信息,传递所有权而非克隆)。如需复制 `Plan`,
/// 只能 clone 处于 `Pending` / `Running` / `Completed` / `Skipped` 状态的步骤。
#[derive(Debug)]
pub enum StepStatus {
/// 初始状态 —— 等待执行。
Pending,
/// 正在执行(`TaskAgent::execute_plan` 进入)。
Running,
/// 已完成(含 LLM 响应)。
Completed(ChatResponse),
/// 失败(含错误)。
Failed(AgentError),
/// 跳过(上层主动跳过)。
Skipped,
}
impl StepStatus {
/// 状态是否处于"未完成"。
pub fn is_pending(&self) -> bool {
matches!(self, Self::Pending)
}
/// 状态是否处于终态。
pub fn is_terminal(&self) -> bool {
matches!(self, Self::Completed(_) | Self::Failed(_) | Self::Skipped)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn step_initial_state_is_pending() {
let s = Step::new(0, "do something");
assert!(s.status.is_pending());
assert!(!s.status.is_terminal());
assert_eq!(s.index, 0);
assert_eq!(s.description, "do something");
}
#[test]
fn terminal_states_classified() {
let err = AgentError::Other("x".into());
assert!(StepStatus::Failed(err).is_terminal());
assert!(StepStatus::Skipped.is_terminal());
}
#[test]
fn running_is_not_terminal() {
assert!(!StepStatus::Running.is_terminal());
assert!(!StepStatus::Running.is_pending());
}
#[test]
fn plan_holds_steps() {
let plan = Plan {
id: "p1".into(),
goal: "test goal".into(),
steps: vec![
Step::new(0, "first"),
Step::new(1, "second"),
],
};
assert_eq!(plan.steps.len(), 2);
assert_eq!(plan.steps[0].index, 0);
assert_eq!(plan.steps[1].index, 1);
}
}