新增: 跨会话召回四层架构与脉络卡
- 会话终态提炼: 删除前+停靠双挂点,快照与清水位同临界区 - 目标快照: 恢复引导按最终有效意图生成,旧目标不复活 - 知识检索项目维度软排序,WorkingContext 持久化迁移 - 脉络卡: published 知识按项目聚合纯读侧,带入上下文经输入框显式注入 - 压缩与缓存能力在主循环/子代理/提示词三面接线 - 两份专项设计文档与看板销账
This commit is contained in:
@@ -65,7 +65,9 @@ pub fn run(conn: &Connection) -> Result<()> {
|
||||
// V51 = 待发箱 ai_outbox 表(定时消息与待发箱持久化 P0 地基):队列/定时统一实体,
|
||||
// 调度器按 status+due_at 索引到点派发。纯新增表,可逆=DROP TABLE ai_outbox。
|
||||
// V52 = ai_messages.source 列(#958 消息溯源:外部渠道原始消息 ID JSON,NULL=桌面本端)。
|
||||
let steps: [(i32, fn(&Connection) -> Result<()>); 52] = [
|
||||
// V53 = WorkingContext 持久化(ai_conversations.working_context_json TEXT,跨会话召回 L4
|
||||
// 重启不失忆:save 随行写,会话载入与 parked 恢复读还原,NULL=无上下文行为同旧)。
|
||||
let steps: [(i32, fn(&Connection) -> Result<()>); 53] = [
|
||||
(1, migrate_v1),
|
||||
(2, migrate_v2),
|
||||
(3, migrate_v3),
|
||||
@@ -125,6 +127,8 @@ pub fn run(conn: &Connection) -> Result<()> {
|
||||
(51, migrate_v51),
|
||||
// V52 = ai_messages 加 source 列(#958 消息溯源:飞书等外部渠道原始消息 ID)。
|
||||
(52, migrate_v52),
|
||||
// V53 = WorkingContext 持久化(ai_conversations.working_context_json,跨会话召回 L4)。
|
||||
(53, migrate_v53),
|
||||
];
|
||||
|
||||
for (version, migrate_fn) in steps {
|
||||
@@ -1532,7 +1536,7 @@ fn migrate_v50(conn: &Connection) -> Result<()> {
|
||||
/// 调度器启动时 reset_stale_sending 复位为 pending。
|
||||
/// 迁移链顶点版本(单一真值来源):**新增迁移时必须同步 +1**,否则下方迁移测试会失败
|
||||
/// (测试用它断言「全量迁移后 schema_version == 顶点」「重跑仍到顶点」——既防链中断,也免多处写死)。
|
||||
pub const LATEST_SCHEMA_VERSION: i32 = 52;
|
||||
pub const LATEST_SCHEMA_VERSION: i32 = 53;
|
||||
|
||||
fn migrate_v51(conn: &Connection) -> Result<()> {
|
||||
if !table_exists(conn, "ai_outbox") {
|
||||
@@ -1584,6 +1588,27 @@ fn migrate_v52(conn: &Connection) -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// V53: WorkingContext 持久化 — ai_conversations 加 working_context_json TEXT 列。
|
||||
///
|
||||
/// 跨会话召回 L4(重启不失忆):WorkingContext(自动目标/决策/未解项/当前步骤/脏标记版本)
|
||||
/// 此前纯内存,重启后压缩重锚定回填的决策与未解项全丢,恢复的会话半失忆续跑。本列随 save
|
||||
/// 每轮随行写、会话载入与 parked 恢复读还原;NULL=无上下文(老会话/全空),行为同旧。
|
||||
/// 目标快照裁决语义另批,本列只做存储载体。
|
||||
/// - **幂等**:column_exists 守卫(同 V44/V45/V49 模式),崩溃重跑/全链两遍不炸;
|
||||
/// - **可逆**:`ALTER TABLE ai_conversations DROP COLUMN working_context_json`(SQLite 3.35+)。
|
||||
fn migrate_v53(conn: &Connection) -> Result<()> {
|
||||
if !column_exists(conn, "ai_conversations", "working_context_json") {
|
||||
conn.execute(
|
||||
"ALTER TABLE ai_conversations ADD COLUMN working_context_json TEXT",
|
||||
[],
|
||||
)?;
|
||||
tracing::info!("v53: 补建 ai_conversations.working_context_json 列(WorkingContext 持久化)");
|
||||
}
|
||||
conn.execute("INSERT OR IGNORE INTO schema_version (version) VALUES (?)", [53])?;
|
||||
tracing::info!("迁移 v53 完成: ai_conversations 加 working_context_json 列");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// 回填 conversation_checkpoints.token_total:按 snapshot JSON 内逐条消息的
|
||||
/// prompt_tokens+completion_tokens 求真实总量。
|
||||
///
|
||||
@@ -2951,4 +2976,51 @@ mod tests {
|
||||
"逐条相加,缺字段按 0"
|
||||
);
|
||||
}
|
||||
|
||||
/// V53 幂等:最小老库补建 working_context_json 列;重跑(含版本号回退模拟崩溃断点)
|
||||
/// 不炸不报 duplicate column;列可写可读(NULL 语义=无 WorkingContext)。
|
||||
#[test]
|
||||
fn v53_working_context_json_idempotent() {
|
||||
let conn = setup_legacy_db();
|
||||
assert!(
|
||||
!column_exists(&conn, "ai_conversations", "working_context_json"),
|
||||
"迁移前应无 working_context_json 列"
|
||||
);
|
||||
|
||||
migrate_v53(&conn).expect("v53 首次迁移应成功");
|
||||
assert!(column_exists(&conn, "ai_conversations", "working_context_json"), "应有 working_context_json 列");
|
||||
let v: i64 = conn
|
||||
.query_row("SELECT MAX(version) FROM schema_version", [], |r| r.get(0))
|
||||
.unwrap();
|
||||
assert_eq!(v, 53, "应写入版本号 53");
|
||||
|
||||
// 模拟崩溃断点:版本号回退但列已建 → 重跑走 column_exists 短路
|
||||
conn.execute("DELETE FROM schema_version WHERE version = 53", []).unwrap();
|
||||
migrate_v53(&conn).expect("列已存在时重跑应幂等跳过 ALTER 不报错");
|
||||
let v2: i64 = conn
|
||||
.query_row("SELECT MAX(version) FROM schema_version", [], |r| r.get(0))
|
||||
.unwrap();
|
||||
assert_eq!(v2, 53, "重跑后版本号恢复 53");
|
||||
|
||||
// 列可写可读;老会话行(不带该列插入)读回 NULL
|
||||
conn.execute(
|
||||
"INSERT INTO ai_conversations (id, messages, created_at, updated_at, working_context_json) \
|
||||
VALUES ('c1', '[]', '0', '0', '{\"goals\":[],\"decisions\":[]}')",
|
||||
[],
|
||||
)
|
||||
.unwrap();
|
||||
conn.execute(
|
||||
"INSERT INTO ai_conversations (id, messages, created_at, updated_at) VALUES ('c2', '[]', '0', '0')",
|
||||
[],
|
||||
)
|
||||
.unwrap();
|
||||
let got: Option<String> = conn
|
||||
.query_row("SELECT working_context_json FROM ai_conversations WHERE id = 'c1'", [], |r| r.get(0))
|
||||
.unwrap();
|
||||
assert!(got.is_some(), "working_context_json 应可写可读");
|
||||
let legacy: Option<String> = conn
|
||||
.query_row("SELECT working_context_json FROM ai_conversations WHERE id = 'c2'", [], |r| r.get(0))
|
||||
.unwrap();
|
||||
assert!(legacy.is_none(), "老会话行应为 NULL(无 WorkingContext)");
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user