架构重构: - 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 完整项目架构文档
133 lines
4.0 KiB
Rust
133 lines
4.0 KiB
Rust
// src/agent/memory/age.rs
|
|
//
|
|
// 记忆时效性追踪 — 参考 Claude Code memdir/memoryAge.ts。
|
|
//
|
|
// LLM 不擅长日期计算,"2026-01-15" 不会触发过时判断,
|
|
// 但 "47 天前" 会。本模块提供人类可读的时效标签,
|
|
// 注入到 system prompt 中以引导模型在使用记忆中之前核实。
|
|
|
|
use std::time::{SystemTime, UNIX_EPOCH};
|
|
|
|
/// 返回当前 Unix 时间戳(秒)
|
|
pub fn now_secs() -> u64 {
|
|
SystemTime::now()
|
|
.duration_since(UNIX_EPOCH)
|
|
.unwrap_or_default()
|
|
.as_secs()
|
|
}
|
|
|
|
/// 距离给定 Unix 时间戳的天数(向下取整)。
|
|
/// 0 = 今天, 1 = 昨天, 2+ = 更早。
|
|
/// 负数输入(未来时间/时钟偏差)截断为 0。
|
|
pub fn memory_age_days(mtime_secs: u64) -> u64 {
|
|
let now = now_secs();
|
|
if mtime_secs >= now {
|
|
return 0;
|
|
}
|
|
(now - mtime_secs) / 86_400
|
|
}
|
|
|
|
/// 人类可读的时效标签。
|
|
pub fn memory_age_label(mtime_secs: u64) -> String {
|
|
let days = memory_age_days(mtime_secs);
|
|
match days {
|
|
0 => "今天".to_string(),
|
|
1 => "昨天".to_string(),
|
|
n => format!("{} 天前", n),
|
|
}
|
|
}
|
|
|
|
/// 返回时效警告文本,如果记忆超过 1 天则返回 Some。
|
|
/// 新鲜记忆(今天/昨天)返回 None — 此时警告只是噪音。
|
|
pub fn memory_freshness_text(mtime_secs: u64) -> Option<String> {
|
|
let days = memory_age_days(mtime_secs);
|
|
if days <= 1 {
|
|
return None;
|
|
}
|
|
Some(format!(
|
|
"此记忆已有 {} 天。记忆是时间点快照,不是实时状态 — \
|
|
关于代码行为或文件:行号的声明可能已过时。\
|
|
请在断言为事实前与当前代码进行核对。",
|
|
days
|
|
))
|
|
}
|
|
|
|
/// 包裹在 <system-reminder> 标签中的时效注释。
|
|
/// 对于 ≤ 1 天的记忆返回空字符串。
|
|
pub fn memory_freshness_note(mtime_secs: u64) -> String {
|
|
match memory_freshness_text(mtime_secs) {
|
|
Some(text) => format!("<system-reminder>{}</system-reminder>", text),
|
|
None => String::new(),
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
|
|
#[test]
|
|
fn test_age_days_today() {
|
|
let now = now_secs();
|
|
assert_eq!(memory_age_days(now), 0);
|
|
assert_eq!(memory_age_days(now - 100), 0); // 100 秒前仍是今天
|
|
}
|
|
|
|
#[test]
|
|
fn test_age_days_yesterday() {
|
|
let now = now_secs();
|
|
assert_eq!(memory_age_days(now - 86_400), 1);
|
|
assert_eq!(memory_age_days(now - 86_400 - 100), 1);
|
|
}
|
|
|
|
#[test]
|
|
fn test_age_days_older() {
|
|
let now = now_secs();
|
|
assert_eq!(memory_age_days(now - 86_400 * 3), 3);
|
|
assert_eq!(memory_age_days(now - 86_400 * 47), 47);
|
|
}
|
|
|
|
#[test]
|
|
fn test_age_days_clamps_future_to_zero() {
|
|
let future = now_secs() + 86_400 * 10;
|
|
assert_eq!(memory_age_days(future), 0);
|
|
}
|
|
|
|
#[test]
|
|
fn test_age_label() {
|
|
let now = now_secs();
|
|
assert_eq!(memory_age_label(now), "今天");
|
|
assert_eq!(memory_age_label(now - 86_400), "昨天");
|
|
assert_eq!(memory_age_label(now - 86_400 * 5), "5 天前");
|
|
}
|
|
|
|
#[test]
|
|
fn test_freshness_text_none_for_fresh() {
|
|
let now = now_secs();
|
|
assert!(memory_freshness_text(now).is_none()); // 今天
|
|
assert!(memory_freshness_text(now - 86_400).is_none()); // 昨天
|
|
}
|
|
|
|
#[test]
|
|
fn test_freshness_text_some_for_old() {
|
|
let now = now_secs();
|
|
let text = memory_freshness_text(now - 86_400 * 2);
|
|
assert!(text.is_some());
|
|
assert!(text.unwrap().contains("2 天"));
|
|
}
|
|
|
|
#[test]
|
|
fn test_freshness_note_contains_tags() {
|
|
let now = now_secs();
|
|
let note = memory_freshness_note(now - 86_400 * 3);
|
|
assert!(note.contains("<system-reminder>"));
|
|
assert!(note.contains("</system-reminder>"));
|
|
}
|
|
|
|
#[test]
|
|
fn test_freshness_note_empty_for_fresh() {
|
|
let now = now_secs();
|
|
assert_eq!(memory_freshness_note(now), "");
|
|
assert_eq!(memory_freshness_note(now - 86_400), "");
|
|
}
|
|
}
|