feat(core): 新增 Phase 10 ContextSlot 多上下文分区管理
- 新增 ContextSlot 类型(Full / Focused / Readonly 三种模式, New / Derived / Static 三种来源),支持 JSON blob 批次持久化 - AgentSession 新增 slots 字段与 5 个管理方法 (create_slot / switch_slot / list_slots / derive_slot / delete_slot), 自动创建 "default" slot - submit_turn / finalize_turn 改造为基于当前 slot 的增量追加写回, 确保 Focused 模式"读时过滤"语义不丢失数据 - finalize_turn 签名变更(新增 new_messages_from_cycle 参数, 返回 Result<(), AgentError>),向后兼容列于 docs/17 - 新增 3 个 AgentError 变体(SlotReadonly / SlotNotFound / SlotAlreadyExists) - 新增分支对话示例 context_slot_demo(法律咨询→两个派生方向→切换→隔离验证) - 新增 43 个测试覆盖持久化、Focused 过滤、Readonly 阻断、delete 保护、 派生逻辑、流式 finalize_turn、key 注入防护等场景 - 方案文档:docs/17-phase10-contextslot.md(含 §5 推荐方案、§6 实施建议、 §9 实施计划,经过 4 轮方案/计划/实施审查 + 1 轮非阻塞建议修复)
This commit is contained in:
+53
-3
@@ -36,6 +36,18 @@ pub enum AgentError {
|
||||
#[error("Plan 解析错误: {0}")]
|
||||
PlanParse(String),
|
||||
|
||||
/// Readonly slot 不允许写入(Phase 10 新增)。
|
||||
#[error("Readonly slot 不允许写入: {0}")]
|
||||
SlotReadonly(String),
|
||||
|
||||
/// Slot 不存在(Phase 10 新增)。
|
||||
#[error("Slot '{0}' 不存在")]
|
||||
SlotNotFound(String),
|
||||
|
||||
/// Slot 已存在(Phase 10 新增)。
|
||||
#[error("Slot '{0}' 已存在")]
|
||||
SlotAlreadyExists(String),
|
||||
|
||||
/// 钩子阻断操作(Agent 层特有)。
|
||||
#[error("钩子阻断: {0}")]
|
||||
HookBlocked(String),
|
||||
@@ -60,6 +72,7 @@ impl AgentError {
|
||||
/// - `Tool`:由内层 `is_recoverable()` 决定
|
||||
/// - `HookBlocked` / `LimitExceeded`:不可恢复(需人工介入或终止循环)
|
||||
/// - `Config` / `Other`:不可恢复
|
||||
/// - `SlotReadonly` / `SlotNotFound` / `SlotAlreadyExists`:不可恢复(结构性错误)
|
||||
pub fn is_recoverable(&self) -> bool {
|
||||
match self {
|
||||
Self::Llm(e) => matches!(
|
||||
@@ -69,9 +82,13 @@ impl AgentError {
|
||||
Self::Tool(e) => e.is_recoverable(),
|
||||
Self::Memory(e) => e.is_recoverable(),
|
||||
Self::PlanParse(_) => false,
|
||||
Self::HookBlocked(_) | Self::LimitExceeded(_) | Self::Config(_) | Self::Other(_) => {
|
||||
false
|
||||
}
|
||||
Self::SlotReadonly(_)
|
||||
| Self::SlotNotFound(_)
|
||||
| Self::SlotAlreadyExists(_)
|
||||
| Self::HookBlocked(_)
|
||||
| Self::LimitExceeded(_)
|
||||
| Self::Config(_)
|
||||
| Self::Other(_) => false,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -181,4 +198,37 @@ mod tests {
|
||||
let err = caller().unwrap_err();
|
||||
assert!(matches!(err, AgentError::Memory(_)));
|
||||
}
|
||||
|
||||
// ====== Phase 10: Slot 错误变体测试 ======
|
||||
|
||||
#[test]
|
||||
fn slot_readonly_not_recoverable() {
|
||||
assert!(!AgentError::SlotReadonly("readonly".into()).is_recoverable());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn slot_not_found_not_recoverable() {
|
||||
assert!(!AgentError::SlotNotFound("missing".into()).is_recoverable());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn slot_already_exists_not_recoverable() {
|
||||
assert!(!AgentError::SlotAlreadyExists("dup".into()).is_recoverable());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn slot_error_messages() {
|
||||
assert_eq!(
|
||||
format!("{}", AgentError::SlotReadonly("readonly".into())),
|
||||
"Readonly slot 不允许写入: readonly"
|
||||
);
|
||||
assert_eq!(
|
||||
format!("{}", AgentError::SlotNotFound("foo".into())),
|
||||
"Slot 'foo' 不存在"
|
||||
);
|
||||
assert_eq!(
|
||||
format!("{}", AgentError::SlotAlreadyExists("bar".into())),
|
||||
"Slot 'bar' 已存在"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user