修复: 消息溯源P2切读确认完成 + 走查报告高优项
- 更新 P2 切读方案文档(确认批次B已上线) - ㉑ query LIKE 通配符转义(project/idea/task/knowledge 4 处) - ⑱ dag.rs deep_merge null 覆盖全局配置 - ⑲ dag.rs _ 通配 match 展开显式变体 - ⑩ INDEX.md 补漏 10 个文档索引 - ⑫ ARCHITECTURE.md 删除与新文档逐字重复 - ㉒ coordinator.rs 加 #[deprecated] 编译守卫 - ㉛ AiChat.vue 空值传播加 console.warn
This commit is contained in:
123
ARCHITECTURE.md
123
ARCHITECTURE.md
@@ -535,6 +535,8 @@ CREATE TABLE app_settings (
|
|||||||
|
|
||||||
## 七、三层模型:模板 → 工作流 → 人设
|
## 七、三层模型:模板 → 工作流 → 人设
|
||||||
|
|
||||||
|
> 本章详细设计已迁至专项文档,详见 [docs/02-架构设计/专项设计/三层模型-流程模板与人设体系-2026-06-28.md](./docs/02-架构设计/专项设计/三层模型-流程模板与人设体系-2026-06-28.md)。此处仅保留摘要性定义。
|
||||||
|
|
||||||
### 7.1 三层定义
|
### 7.1 三层定义
|
||||||
|
|
||||||
| 层 | 回答的问题 | 本质 | 生命周期 | 当前状态 |
|
| 层 | 回答的问题 | 本质 | 生命周期 | 当前状态 |
|
||||||
@@ -543,126 +545,9 @@ CREATE TABLE app_settings (
|
|||||||
| **工作流 (Workflow)** | 怎么执行 | 模板的运行时实例(DAG + 状态 + 数据绑定) | 随项目启停,单次执行归档 | ✅ df-workflow 核心完成 |
|
| **工作流 (Workflow)** | 怎么执行 | 模板的运行时实例(DAG + 状态 + 数据绑定) | 随项目启停,单次执行归档 | ✅ df-workflow 核心完成 |
|
||||||
| **人设 (Persona)** | 谁来做 | Agent 角色卡(system prompt + 工具集 + 行为规则) | 长期存在,跨节点复用 | ⬜ 待设计 |
|
| **人设 (Persona)** | 谁来做 | Agent 角色卡(system prompt + 工具集 + 行为规则) | 长期存在,跨节点复用 | ⬜ 待设计 |
|
||||||
|
|
||||||
### 7.2 三者关系
|
**关键原则**:模板不绑定具体人设、工作流不感知人设、人设与模板解耦。
|
||||||
|
|
||||||
```
|
详细定义、三者关系、实例化流程、数据结构及 YAML 模板示例见 [专项设计文档](./docs/02-架构设计/专项设计/三层模型-流程模板与人设体系-2026-06-28.md)。
|
||||||
流程模板 (蓝图)
|
|
||||||
│ 实例化(填入项目参数)
|
|
||||||
▼
|
|
||||||
工作流实例 (运行时 DAG)
|
|
||||||
│ 分配人设(绑定到每个 AINode)
|
|
||||||
▼
|
|
||||||
人设驱动 Agent 执行
|
|
||||||
```
|
|
||||||
|
|
||||||
**关键点**:
|
|
||||||
- 模板不绑定具体人设,只标注"建议人设类型",实例化时由用户覆盖
|
|
||||||
- 工作流不感知人设,只管 DAG 执行;人设注入发生在 AINode 执行时
|
|
||||||
- 人设与模板解耦:同一个人设可用于不同模板的同类节点
|
|
||||||
|
|
||||||
### 7.3 模板实例化流程
|
|
||||||
|
|
||||||
```
|
|
||||||
① 选择模板 → "功能开发模板"
|
|
||||||
模板定义: [需求分析]→[架构设计]→[编码]→[测试]→[审查]→[发布]
|
|
||||||
建议人设: analyst → architect → coder → tester → reviewer → none
|
|
||||||
|
|
||||||
② 实例化为工作流 DAG
|
|
||||||
┌─ AINode(需求分析) 建议人设: analyst
|
|
||||||
├─ AINode(架构设计) 建议人设: architect
|
|
||||||
├─ AINode(编码) 建议人设: coder
|
|
||||||
├─ AINode(单元测试) 建议人设: tester
|
|
||||||
├─ ScriptNode(运行测试) 无人设(纯脚本)
|
|
||||||
├─ HumanNode(审批发布) 无人设(人工)
|
|
||||||
└─ AINode(发布说明) 建议人设: writer
|
|
||||||
|
|
||||||
③ 实际分配人设(可覆盖)
|
|
||||||
编码节点 → coder-rust(项目是 Rust 后端)
|
|
||||||
测试节点 → tester-py(测试框架是 Python)
|
|
||||||
|
|
||||||
④ 执行
|
|
||||||
工作流引擎按 DAG 拓扑调度节点
|
|
||||||
每个 AINode 执行时载入对应人设的 system_prompt + 工具集
|
|
||||||
NodeContext 中传递 persona_id,AI Node 据此取人设配置
|
|
||||||
```
|
|
||||||
|
|
||||||
### 7.4 人设定义
|
|
||||||
|
|
||||||
```rust
|
|
||||||
/// 智能体人设(Agent 角色卡)
|
|
||||||
pub struct AgentPersona {
|
|
||||||
pub id: PersonaId,
|
|
||||||
pub name: String, // 人设名称
|
|
||||||
pub description: String, // 一句话描述
|
|
||||||
pub system_prompt: String, // 角色系统提示词
|
|
||||||
pub allowed_tools: Vec<String>, // 可用工具列表
|
|
||||||
pub suggested_tier: ModelTier, // 推荐模型层级
|
|
||||||
pub output_format: OutputFormat, // 输出格式约束
|
|
||||||
pub rules: Vec<BehaviorRule>, // 行为规则
|
|
||||||
pub examples: Vec<PersonaExample>, // Few-shot 样例
|
|
||||||
}
|
|
||||||
|
|
||||||
// 预置人设(平台内置,用户可自定义扩展)
|
|
||||||
Persona::coder → "你是资深 Rust/TS 工程师..."
|
|
||||||
Persona::reviewer → "你是代码审查员,关注安全漏洞..."
|
|
||||||
Persona::architect → "你是系统架构师,关注模块划分..."
|
|
||||||
Persona::tester → "你是测试工程师,生成边界用例..."
|
|
||||||
Persona::analyst → "你是需求分析师,拆解用户故事..."
|
|
||||||
```
|
|
||||||
|
|
||||||
### 7.5 模板示例:功能开发模板(YAML)
|
|
||||||
|
|
||||||
```yaml
|
|
||||||
# templates/feature-dev.yaml
|
|
||||||
id: feature-dev
|
|
||||||
name: 功能开发模板
|
|
||||||
description: 标准功能开发流程,从需求分析到发布
|
|
||||||
template_version: 1
|
|
||||||
|
|
||||||
nodes:
|
|
||||||
- id: analysis
|
|
||||||
type: ai
|
|
||||||
persona_hint: analyst
|
|
||||||
prompt: "分析以下需求:{{inputs.requirement}}"
|
|
||||||
inputs:
|
|
||||||
requirement: "$ctx.requirement"
|
|
||||||
outputs:
|
|
||||||
prd: text
|
|
||||||
|
|
||||||
- id: design
|
|
||||||
type: ai
|
|
||||||
persona_hint: architect
|
|
||||||
prompt: "基于 PRD 设计架构:{{inputs.analysis.prd}}"
|
|
||||||
inputs:
|
|
||||||
analysis: "$nodes.analysis"
|
|
||||||
outputs:
|
|
||||||
arch_doc: text
|
|
||||||
|
|
||||||
- id: coding
|
|
||||||
type: ai
|
|
||||||
persona_hint: coder
|
|
||||||
prompt: "按架构设计实现:{{inputs.design.arch_doc}}"
|
|
||||||
inputs:
|
|
||||||
design: "$nodes.design"
|
|
||||||
outputs:
|
|
||||||
code: text
|
|
||||||
|
|
||||||
- id: review
|
|
||||||
type: ai
|
|
||||||
persona_hint: reviewer
|
|
||||||
prompt: "审查代码:{{inputs.coding.code}}"
|
|
||||||
inputs:
|
|
||||||
coding: "$nodes.coding"
|
|
||||||
|
|
||||||
quality_gates:
|
|
||||||
- node: review
|
|
||||||
condition: "output.verdict == 'pass'"
|
|
||||||
on_fail: "goto coding"
|
|
||||||
|
|
||||||
artifacts:
|
|
||||||
prd: "$nodes.analysis.prd"
|
|
||||||
code: "$nodes.coding.code"
|
|
||||||
review_report: "$nodes.review.text"
|
|
||||||
|
|
||||||
## 八、Phase 规划
|
## 八、Phase 规划
|
||||||
|
|
||||||
|
|||||||
@@ -5,6 +5,7 @@
|
|||||||
/// Agent 协调器
|
/// Agent 协调器
|
||||||
///
|
///
|
||||||
/// TODO: 实现多 Agent 协作逻辑
|
/// TODO: 实现多 Agent 协作逻辑
|
||||||
|
#[deprecated(note = "B 路线占位,勿用于生产")]
|
||||||
pub struct AgentCoordinator;
|
pub struct AgentCoordinator;
|
||||||
|
|
||||||
impl AgentCoordinator {
|
impl AgentCoordinator {
|
||||||
|
|||||||
@@ -301,10 +301,11 @@ impl IdeaRepo {
|
|||||||
params_vec.push(Box::new(s.clone()));
|
params_vec.push(Box::new(s.clone()));
|
||||||
}
|
}
|
||||||
if let Some(kw) = &keyword {
|
if let Some(kw) = &keyword {
|
||||||
let pat = format!("%{kw}%");
|
let escaped = kw.replace('%', "\\%").replace('_', "\\_");
|
||||||
|
let pat = format!("%{escaped}%");
|
||||||
let p1 = params_vec.len() + 1;
|
let p1 = params_vec.len() + 1;
|
||||||
let p2 = p1 + 1;
|
let p2 = p1 + 1;
|
||||||
where_clauses.push(format!("(title LIKE ?{p1} OR description LIKE ?{p2})"));
|
where_clauses.push(format!("(title LIKE ?{p1} OR description LIKE ?{p2}) ESCAPE '\\'"));
|
||||||
params_vec.push(Box::new(pat.clone()));
|
params_vec.push(Box::new(pat.clone()));
|
||||||
params_vec.push(Box::new(pat));
|
params_vec.push(Box::new(pat));
|
||||||
}
|
}
|
||||||
@@ -545,7 +546,8 @@ impl KnowledgeRepo {
|
|||||||
/// 克制检索: top-N≤3(由调用方 limit 控制),精确匹配优先(语义模糊后做)。
|
/// 克制检索: top-N≤3(由调用方 limit 控制),精确匹配优先(语义模糊后做)。
|
||||||
pub async fn search(&self, query: &str, kind: Option<&str>, limit: usize) -> Result<Vec<KnowledgeRecord>> {
|
pub async fn search(&self, query: &str, kind: Option<&str>, limit: usize) -> Result<Vec<KnowledgeRecord>> {
|
||||||
let conn = self.conn.clone();
|
let conn = self.conn.clone();
|
||||||
let pattern = format!("%{}%", query);
|
let escaped = query.replace('%', "\\%").replace('_', "\\_");
|
||||||
|
let pattern = format!("%{escaped}%");
|
||||||
let kind = kind.map(|s| s.to_owned());
|
let kind = kind.map(|s| s.to_owned());
|
||||||
let limit_i = limit as i64;
|
let limit_i = limit as i64;
|
||||||
tokio::task::spawn_blocking(move || {
|
tokio::task::spawn_blocking(move || {
|
||||||
@@ -553,7 +555,7 @@ impl KnowledgeRepo {
|
|||||||
let mut results = Vec::new();
|
let mut results = Vec::new();
|
||||||
if let Some(k) = &kind {
|
if let Some(k) = &kind {
|
||||||
let mut stmt = guard
|
let mut stmt = guard
|
||||||
.prepare(&format!("SELECT {KNOWLEDGE_COLS} FROM knowledges WHERE status = 'published' AND (title LIKE ?1 OR content LIKE ?2) AND kind = ?3 ORDER BY reuse_count DESC LIMIT ?4"))
|
.prepare(&format!("SELECT {KNOWLEDGE_COLS} FROM knowledges WHERE status = 'published' AND (title LIKE ?1 ESCAPE '\\' OR content LIKE ?2 ESCAPE '\\') AND kind = ?3 ORDER BY reuse_count DESC LIMIT ?4"))
|
||||||
.map_err(storage_err)?;
|
.map_err(storage_err)?;
|
||||||
let rows = stmt
|
let rows = stmt
|
||||||
.query_map(params![pattern, pattern, k, limit_i], |row| knowledge_from_row(row))
|
.query_map(params![pattern, pattern, k, limit_i], |row| knowledge_from_row(row))
|
||||||
@@ -563,7 +565,7 @@ impl KnowledgeRepo {
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
let mut stmt = guard
|
let mut stmt = guard
|
||||||
.prepare(&format!("SELECT {KNOWLEDGE_COLS} FROM knowledges WHERE status = 'published' AND (title LIKE ?1 OR content LIKE ?2) ORDER BY reuse_count DESC LIMIT ?3"))
|
.prepare(&format!("SELECT {KNOWLEDGE_COLS} FROM knowledges WHERE status = 'published' AND (title LIKE ?1 ESCAPE '\\' OR content LIKE ?2 ESCAPE '\\') ORDER BY reuse_count DESC LIMIT ?3"))
|
||||||
.map_err(storage_err)?;
|
.map_err(storage_err)?;
|
||||||
let rows = stmt
|
let rows = stmt
|
||||||
.query_map(params![pattern, pattern, limit_i], |row| knowledge_from_row(row))
|
.query_map(params![pattern, pattern, limit_i], |row| knowledge_from_row(row))
|
||||||
|
|||||||
@@ -242,8 +242,9 @@ impl ProjectRepo {
|
|||||||
if let Some(kw) = &q.keyword {
|
if let Some(kw) = &q.keyword {
|
||||||
let trimmed = kw.trim();
|
let trimmed = kw.trim();
|
||||||
if !trimmed.is_empty() {
|
if !trimmed.is_empty() {
|
||||||
sql.push_str(" AND (name LIKE ? OR description LIKE ?)");
|
let escaped = trimmed.replace('%', "\\%").replace('_', "\\_");
|
||||||
let pattern = format!("%{trimmed}%");
|
sql.push_str(" AND (name LIKE ? OR description LIKE ?) ESCAPE '\\'");
|
||||||
|
let pattern = format!("%{escaped}%");
|
||||||
params_vec.push(Box::new(pattern.clone()));
|
params_vec.push(Box::new(pattern.clone()));
|
||||||
params_vec.push(Box::new(pattern));
|
params_vec.push(Box::new(pattern));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -378,10 +378,11 @@ impl TaskRepo {
|
|||||||
}
|
}
|
||||||
// keyword: title/description LIKE %kw%(P2,对齐知识库 search 的 LIKE 模式)
|
// keyword: title/description LIKE %kw%(P2,对齐知识库 search 的 LIKE 模式)
|
||||||
if let Some(kw) = &keyword {
|
if let Some(kw) = &keyword {
|
||||||
let pat = format!("%{kw}%");
|
let escaped = kw.replace('%', "\\%").replace('_', "\\_");
|
||||||
|
let pat = format!("%{escaped}%");
|
||||||
let p1 = params_vec.len() + 1;
|
let p1 = params_vec.len() + 1;
|
||||||
let p2 = p1 + 1;
|
let p2 = p1 + 1;
|
||||||
where_clauses.push(format!("(title LIKE ?{p1} OR description LIKE ?{p2})"));
|
where_clauses.push(format!("(title LIKE ?{p1} OR description LIKE ?{p2}) ESCAPE '\\'"));
|
||||||
params_vec.push(Box::new(pat.clone()));
|
params_vec.push(Box::new(pat.clone()));
|
||||||
params_vec.push(Box::new(pat));
|
params_vec.push(Box::new(pat));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -165,9 +165,10 @@ impl Default for Dag {
|
|||||||
///
|
///
|
||||||
/// 规则:
|
/// 规则:
|
||||||
/// - 两端均为 Object:递归合并(同 key 时节点级覆盖全局级,新增 key 各自保留)。
|
/// - 两端均为 Object:递归合并(同 key 时节点级覆盖全局级,新增 key 各自保留)。
|
||||||
/// - 非两端 Object:节点级直接覆盖全局级(包括 `Null`,符合 JSON 显式空语义)。
|
/// - 非 Object/Null 两端:节点级直接覆盖全局级。
|
||||||
/// - 节点级为 `Null` 的语义是「显式置空」,按非 Object 规则覆盖(直接返回 Null);
|
/// - 节点级为 `Null` 时跳过(保留全局配置),对应语义「节点未指定该配置」。
|
||||||
/// 若调用方希望「跳过节点配置」,应在构造 Dag 时不写入该 key(node_configs 缺失即用全局)。
|
/// 若调用方希望「显式置空」,应在构造 Dag 时不写入该 key(node_configs 缺失即用全局),
|
||||||
|
/// 而非传 JSON null。
|
||||||
///
|
///
|
||||||
/// 用途:DagExecutor 构造 NodeContext 时 `deep_merge(initial_config, node_config)`,
|
/// 用途:DagExecutor 构造 NodeContext 时 `deep_merge(initial_config, node_config)`,
|
||||||
/// 让节点定义里写的 NodeDef.config 优先于 run_workflow 传入的全局 config。
|
/// 让节点定义里写的 NodeDef.config 优先于 run_workflow 传入的全局 config。
|
||||||
@@ -187,8 +188,15 @@ pub fn deep_merge(global: &serde_json::Value, node: &serde_json::Value) -> serde
|
|||||||
}
|
}
|
||||||
Value::Object(merged)
|
Value::Object(merged)
|
||||||
}
|
}
|
||||||
// 非两端 Object:节点级覆盖全局级(含 Null、标量、数组)
|
// 节点级为 Null → 保留全局配置(不覆盖)
|
||||||
_ => node.clone(),
|
(_, Value::Null) => global.clone(),
|
||||||
|
// 显式覆盖剩余变体,防止新变体被 _ 吞没
|
||||||
|
(Value::Number(_), _)
|
||||||
|
| (Value::String(_), _)
|
||||||
|
| (Value::Array(_), _)
|
||||||
|
| (Value::Bool(_), _)
|
||||||
|
| (Value::Null, _)
|
||||||
|
| (Value::Object(_), _) => node.clone(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -226,11 +234,10 @@ mod deep_merge_tests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn missing_node_config_treated_as_noop() {
|
fn null_node_config_keeps_global() {
|
||||||
// DagExecutor 对缺失 key 的节点不调 deep_merge(直接用 global),此处覆盖语义对照:
|
// 节点级为 Null → 保留全局(不覆盖),区别于「缺失 key 不调 deep_merge」。
|
||||||
// 若强行把缺失视作 Null,应覆盖(显式空语义),与「缺失」不同(缺失走 if let Some 分支)。
|
|
||||||
let global = json!({"a": 1});
|
let global = json!({"a": 1});
|
||||||
assert_eq!(deep_merge(&global, &json!(null)), json!(null));
|
assert_eq!(deep_merge(&global, &json!(null)), global);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|||||||
@@ -49,6 +49,8 @@
|
|||||||
| [F-260620-01-跨端AIChat-微信小程序-2026-06-20.md](./已编号方案/F-260620-01-跨端AIChat-微信小程序-2026-06-20.md) | F-260620-01 | 📐 草案(灵感 4495fbcd 待晋升) | 微信小程序 ↔ Rust 云后端(df-relay) ↔ DevFlow 桌面端(df-tunnel)实时同步,Rust 选型代码复用 |
|
| [F-260620-01-跨端AIChat-微信小程序-2026-06-20.md](./已编号方案/F-260620-01-跨端AIChat-微信小程序-2026-06-20.md) | F-260620-01 | 📐 草案(灵感 4495fbcd 待晋升) | 微信小程序 ↔ Rust 云后端(df-relay) ↔ DevFlow 桌面端(df-tunnel)实时同步,Rust 选型代码复用 |
|
||||||
| [B-03-人工审批响应机制-2026-06-14.md](./已编号方案/B-03-人工审批响应机制-2026-06-14.md) | B-260614-03 | ✅ 已落地(B-03a) | HumanNode execute subscribe/send/select 完整审批链 |
|
| [B-03-人工审批响应机制-2026-06-14.md](./已编号方案/B-03-人工审批响应机制-2026-06-14.md) | B-260614-03 | ✅ 已落地(B-03a) | HumanNode execute subscribe/send/select 完整审批链 |
|
||||||
| [B-260616-21排查方案-2026-06-16.md](./已编号方案/B-260616-21排查方案-2026-06-16.md) | B-260616-21 | 📐 排查方案 | 工具卡片重复渲染根因(audit 重复 emit Started) + 修复方案 |
|
| [B-260616-21排查方案-2026-06-16.md](./已编号方案/B-260616-21排查方案-2026-06-16.md) | B-260616-21 | 📐 排查方案 | 工具卡片重复渲染根因(audit 重复 emit Started) + 修复方案 |
|
||||||
|
| [F-260622-01-跨端AIChat-Phase3联调设计-2026-06-22.md](./已编号方案/F-260622-01-跨端AIChat-Phase3联调设计-2026-06-22.md) | F-260622-01 | 📐 设计 | 微信小程序跨端联调:云隧道握手 / MCP 工具路由 / DF 事件同步 |
|
||||||
|
| [消息级溯源P2-切读方案-2026-06-28.md](./已编号方案/消息级溯源P2-切读方案-2026-06-28.md) | — | 📐 设计 | 消息级溯源 Phase 2:切读切换到 ai_messages 视图 |
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -74,6 +76,13 @@
|
|||||||
| [消息级溯源设计-2026-06-19.md](./专项设计/消息级溯源设计-2026-06-19.md) | 📐 设计待实施(F-260619-04) | ChatMessage.id + source_ref/audit/idea 四场景从对话级升级消息级 |
|
| [消息级溯源设计-2026-06-19.md](./专项设计/消息级溯源设计-2026-06-19.md) | 📐 设计待实施(F-260619-04) | ChatMessage.id + source_ref/audit/idea 四场景从对话级升级消息级 |
|
||||||
| [全局事件数据总线-2026-06-21.md](./专项设计/全局事件数据总线-2026-06-21.md) | 📐 构想定稿待评审 | pub-sub + request-reply + 流式 reply 统一总线:跨模块解耦 / 响应式根治死等 / 跨端透传 |
|
| [全局事件数据总线-2026-06-21.md](./专项设计/全局事件数据总线-2026-06-21.md) | 📐 构想定稿待评审 | pub-sub + request-reply + 流式 reply 统一总线:跨模块解耦 / 响应式根治死等 / 跨端透传 |
|
||||||
| [三层模型-流程模板与人设体系-2026-06-28.md](./专项设计/三层模型-流程模板与人设体系-2026-06-28.md) | 📐 设计 | 模板→工作流→人设三层架构:流程模板YAML定义、人设AgentPersona数据结构、三层实例化流程 |
|
| [三层模型-流程模板与人设体系-2026-06-28.md](./专项设计/三层模型-流程模板与人设体系-2026-06-28.md) | 📐 设计 | 模板→工作流→人设三层架构:流程模板YAML定义、人设AgentPersona数据结构、三层实例化流程 |
|
||||||
|
| [aichat体验与agent能力系统化重构-2026-06-21.md](./专项设计/aichat体验与agent能力系统化重构-2026-06-21.md) | 📐 设计 | AI Chat 体验 + Agent 能力统一重构:消息模型 / 上下文管理 / 工具路由 |
|
||||||
|
| [查询能力补全方案-2026-06-21.md](./专项设计/查询能力补全方案-2026-06-21.md) | 📐 设计 | 查询接口能力补全:多维度筛选 / 聚合 / 跨表关联 |
|
||||||
|
| [AST符号解析-设计-2026-06-24.md](./专项设计/AST符号解析-设计-2026-06-24.md) | 📐 设计 | AST 符号解析:代码结构提取 / 符号索引 / 跳转支持 |
|
||||||
|
| [插件机制-设计-2026-06-24.md](./专项设计/插件机制-设计-2026-06-24.md) | 📐 设计 | 插件系统:动态加载 / 生命周期 / 权限沙箱 |
|
||||||
|
| [AI对话目标丢失诊断-2026-06-26.md](./专项设计/AI对话目标丢失诊断-2026-06-26.md) | 📐 诊断 | AI 对话目标丢失根因分析:上下文漂移 / 意图衰减 |
|
||||||
|
| [AI原生上下文地图与去AI化进化系统-2026-06-26.md](./专项设计/AI原生上下文地图与去AI化进化系统-2026-06-26.md) | 📐 设计 | AI 原生上下文地图:语义索引 / 去 AI 化渐进演进路径 |
|
||||||
|
| [项目知识图谱与任务队列系统-2026-06-26.md](./专项设计/项目知识图谱与任务队列系统-2026-06-26.md) | 📐 设计 | 项目级知识图谱 + 任务队列:依赖解析 / 优先级调度 |
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -98,6 +107,7 @@
|
|||||||
| [意图识别层论证-2026-06-19.md](./构想审查/意图识别层论证-2026-06-19.md) | 📐 论证(供决策) | 通用前置意图识别层 8 维度论证 + 触发时机 |
|
| [意图识别层论证-2026-06-19.md](./构想审查/意图识别层论证-2026-06-19.md) | 📐 论证(供决策) | 通用前置意图识别层 8 维度论证 + 触发时机 |
|
||||||
| [多主题上下文管理愿景-2026-06-19.md](./构想审查/多主题上下文管理愿景-2026-06-19.md) | 💡 远期愿景 | 无感多主题对话:主题检测前置 + 多主题多摘要(关联 F-15 / 意图识别) |
|
| [多主题上下文管理愿景-2026-06-19.md](./构想审查/多主题上下文管理愿景-2026-06-19.md) | 💡 远期愿景 | 无感多主题对话:主题检测前置 + 多主题多摘要(关联 F-15 / 意图识别) |
|
||||||
| [多主题并存补充论证-多轮模式-2026-06-19.md](./构想审查/多主题并存补充论证-多轮模式-2026-06-19.md) | 📐 论证(供决策) | agentic 多轮模式可突破天花板,但近期结论不变 |
|
| [多主题并存补充论证-多轮模式-2026-06-19.md](./构想审查/多主题并存补充论证-多轮模式-2026-06-19.md) | 📐 论证(供决策) | agentic 多轮模式可突破天花板,但近期结论不变 |
|
||||||
|
| [跑题改进试验记录-2026-06-20.md](./构想审查/跑题改进试验记录-2026-06-20.md) | 📋 试验 | AI 跑题改进试验:系统 prompt 加固 / 上下文钳制 / 行为约束 |
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
@@ -2,50 +2,18 @@
|
|||||||
|
|
||||||
> 决策:2026-06-28 — 一次性切读 + 脚本迁移历史数据
|
> 决策:2026-06-28 — 一次性切读 + 脚本迁移历史数据
|
||||||
|
|
||||||
## 现状
|
## 现状(2026-06-28 代码核查)
|
||||||
|
|
||||||
- `ai_messages` 表 ✅ 已存在(V21 迁移)
|
**P2 切读已完成**。上一阶段「批次 B」已经实现了读写路径切换:
|
||||||
- `AiMessageRepo` ✅ CRUD 已实现
|
|
||||||
- V21 已有一次性数据迁移(从 `ai_conversations.messages` JSON 读 → 写入 `ai_messages`)
|
|
||||||
- **但当前读写路径仍走 `ai_conversations.messages` JSON 列**,`ai_messages` 表未启用
|
|
||||||
|
|
||||||
## 目标
|
### ✅ 读路径
|
||||||
|
`ai_conversation_switch` 已优先从 `ai_messages` 表加载(`list_by_conversation`),
|
||||||
|
空时 fallback 旧 `messages` JSON 列。见 `conversation.rs:257-282`。
|
||||||
|
|
||||||
读/写消息完全切换到 `ai_messages` 表,删除 `ai_conversations.messages` JSON 列。
|
### ✅ 写路径
|
||||||
|
`save_conversation` 已走 `AiMessageRepo.replace_conversation`(`DELETE + INSERT` 事务原子),
|
||||||
|
旧 messages JSON 列保留不写作备份。见 `conversation.rs` 中 `save_conversation` 实现。
|
||||||
|
|
||||||
## 涉及改动
|
### 剩余工作
|
||||||
|
- `ai_conversations.messages` 列标记废弃(暂不删列)
|
||||||
### 1. 数据迁移(已有 V21,补充幂等脚本)
|
|
||||||
|
|
||||||
V21 迁移已有从 `ai_conversations.messages` → `ai_messages` 的逻辑。补充:
|
|
||||||
- V21 已幂等(`INSERT OR IGNORE`),可重复跑
|
|
||||||
- 补充校验:迁移后 `ai_messages` 行数 = 各对话 messages JSON 汇总行数
|
|
||||||
|
|
||||||
### 2. 切读 — 后端代码
|
|
||||||
|
|
||||||
| 模块 | 当前 | 改为 |
|
|
||||||
|------|------|------|
|
|
||||||
| `restore_from_messages` | 从 `rec.messages` JSON 反序列化 | 从 `ai_messages` 表按 `conversation_id` 查询 + seq 排序 |
|
|
||||||
| `save_conversation` | 写 `rec.messages` JSON 列 | 写 `ai_messages` 表(insert_batch + delete_range) |
|
|
||||||
| `ai_conversation_switch` | 返回 `rec.messages`(JSON 反序列化) | 返回 `AiMessageRepo.list_by_conversation()` |
|
|
||||||
| `clear_context` | 读/写 messages JSON | 读写 ai_messages 表 |
|
|
||||||
| `compress_context` | 同上 | 同上 |
|
|
||||||
| `replace_last_active_user_content` | 同上 | 同上 |
|
|
||||||
| messages 相关 IPC 查询 | 走 JSON 列 | 走 ai_messages 表 |
|
|
||||||
|
|
||||||
### 3. 清理
|
|
||||||
|
|
||||||
- `ai_conversations.messages` 列标记废弃(暂不删列,避免大表 ALTER 风险)
|
|
||||||
- 后续 V22 可选删除该列
|
- 后续 V22 可选删除该列
|
||||||
|
|
||||||
### 4. 风险控制
|
|
||||||
|
|
||||||
- **双写保护**:切读后写入同时写 ai_messages 表 + messages JSON 列(双写期 1 周,可回退)
|
|
||||||
- **回退方案**:撤切读 → 恢复从 messages JSON 列读
|
|
||||||
|
|
||||||
## 执行顺序
|
|
||||||
|
|
||||||
1. 实现切读:改 `restore_from_messages` / `save_conversation` / `switch_conversation` 等核心路径
|
|
||||||
2. 运行 V21 迁移(幂等,补全遗留数据)
|
|
||||||
3. 双写期观察(ai_messages 数据一致)
|
|
||||||
4. 切读完成,标记 messages 列废弃
|
|
||||||
|
|||||||
@@ -237,6 +237,7 @@ function scrollToFirstPending(): void {
|
|||||||
const { confirmState, confirmDialog, answerConfirm } = useConfirm()
|
const { confirmState, confirmDialog, answerConfirm } = useConfirm()
|
||||||
async function confirmDeleteConversation(id: string) {
|
async function confirmDeleteConversation(id: string) {
|
||||||
const conv = store.state.conversations.find(c => c.id === id)
|
const conv = store.state.conversations.find(c => c.id === id)
|
||||||
|
if (!conv) console.warn('confirmDeleteConversation: conv not found, id=', id)
|
||||||
const title = conv?.title || t('aiChat.newConversation')
|
const title = conv?.title || t('aiChat.newConversation')
|
||||||
// UX-260617-24:删除当前活跃对话加醒目提示(通用文案不区分,活跃对话误删影响更大)。
|
// UX-260617-24:删除当前活跃对话加醒目提示(通用文案不区分,活跃对话误删影响更大)。
|
||||||
const isActive = id === store.state.activeConversationId
|
const isActive = id === store.state.activeConversationId
|
||||||
|
|||||||
Reference in New Issue
Block a user