新增: 批次工作落地(推进链/评估闭环/事件总线/并发/加固) + 技术债清理 + 文档整理
后端: - 工作流推进链(D-03):advance_task/状态机/闸门走 df-nodes Node trait,conditions 条件引擎扩展 - 想法评估闭环:启发式评分+对抗评估,df-ideas/scoring + df-storage/idea_eval_repo + idea 前端打通 - 全局事件数据总线:df-ai/context+context_helpers+augmentation 跨模块解耦 - AI planner/plan_hint/intent:aichat B 路线并行多轮基础 - patch_file 加固(TD-03/04):读改写整体锁防 lost update,expected_hash 合约闭环 - 压缩超时兜底(F-15 卡死根治) - F-09 多会话并发:LlmConcurrency per-conv + streamingGuard 前端守护 + verify 脚本 - 知识注入 DRY/skills/audit 扩展 清理: - aichat 技术债(误报 allow/死导入/过时注释 30 项) - URGENT.md 删除(11 项加急全解决/迁 todo) - 文档整理(todo/待决策/待审查/ARCHITECTURE/INDEX + 总线/技术债审查新文档)
This commit is contained in:
@@ -72,6 +72,7 @@
|
||||
| [推进链阶段2实施路径-2026-06-16.md](./专项设计/推进链阶段2实施路径-2026-06-16.md) | 📐 设计(F-260616-06) | 工作流联动任务推进:task_id + 完成回调 + DAG 模板 |
|
||||
| [消息拆分存储设计-2026-06-19.md](./专项设计/消息拆分存储设计-2026-06-19.md) | 📐 设计待实施(F-260619-03) | ai_messages 表 + V21 全量迁移 + 三阶段渐进切换(脏标记→双写→切读) |
|
||||
| [消息级溯源设计-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 统一总线:跨模块解耦 / 响应式根治死等 / 跨端透传 |
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
# 任务推进链实施路径
|
||||
|
||||
> **日期**: 2026-06-16
|
||||
> **日期**: 2026-06-16(状态行 2026-06-20 回填)
|
||||
> **来源**: [任务执行与推进能力分析-2026-06-16.md](../05-代码审查/任务执行与推进能力分析-2026-06-16.md) 第八章(已核对注入)
|
||||
> **状态**: 规划定稿。**D-260616-01~04 已决策(2026-06-16)**:①前端对齐7态 ②任务软删(UI缓做) ③advance_task 走 **df-nodes Node** ④阶段1先行。**阶段1可启动(F-01~05)**。
|
||||
> **状态**: 阶段1✅ + 阶段2✅(F-260616-06 工作流联动:workflow.rs 完成回调 + 三模板 `task_workflow_templates.rs` + `run_workflow` task_id/target_status)+ 阶段3 部分(`ai_self_review` gate 已落 `task_workflow_templates.rs:54`;`run_workflow` AI 工具已注册但 High 风险默认拒绝实装待核、AiNode 任务上下文注入待核)。**D-260616-01~04 已决策(2026-06-16)**:①前端对齐7态 ②任务软删(UI缓做) ③advance_task 走 **df-nodes Node** ④阶段1先行。
|
||||
> **关联决策**: D-260616-01~04(决策结果见 todo.md 待决策区块)
|
||||
|
||||
---
|
||||
@@ -36,13 +36,15 @@
|
||||
|
||||
目标:任务状态经「合法路径」推进,而非裸字段修改。
|
||||
|
||||
| 任务 | 内容 | 依赖 |
|
||||
|------|------|------|
|
||||
| **F-260616-01** [P1] | **状态机定义(df-nodes 新模块 `task_state_machine.rs`)**:7 态合法转换枚举(`todo→in_progress→in_review→testing→done` 闸门链 + `blocked` 退回 + `cancelled`)。独立模块,非挂在 TaskStatus enum 上。 | D-01✅ 前端对齐7态 |
|
||||
| **F-260616-02** [P1] | **advance_task 推进逻辑(df-nodes `task_advance_node.rs` 实现 Node trait)**:校验转换 + 原子写(下沉 SQL `WHERE status=:expected` 防 TOCTOU)。df-nodes 需补 `df-storage` 依赖读 TaskRecord(核实无循环)。IPC 层 thin 入口调 df-nodes。 | D-03✅ df-nodes, F-01 |
|
||||
| **F-260616-03** [P1] | `status` 移出 `update_task` 白名单(推进链唯一收口) | F-02(关联 B-260616-16) |
|
||||
| **F-260616-04** [P2] | `review_rounds` 字段(退回时 +1,任务卡显示「第 N 轮 review」) | F-01 |
|
||||
| **F-260616-05** [P1] | 前端 TaskDetail 推进按钮(手动推进,不接 AI) | F-02, F-03 |
|
||||
✅ **已落(2026-06-16~17)**:状态机 / advance_task 原子推进 / status 收口 / review_rounds / 前端推进按钮全部实装。
|
||||
|
||||
| 任务 | 内容 | 依赖 | 状态 |
|
||||
|------|------|------|------|
|
||||
| **F-260616-01** [P1] | **状态机定义(df-nodes 新模块 `task_state_machine.rs`)**:7 态合法转换枚举(`todo→in_progress→in_review→testing→done` 闸门链 + `blocked` 退回 + `cancelled`)。独立模块,非挂在 TaskStatus enum 上。 | D-01✅ 前端对齐7态 | ✅ `crates/df-nodes/src/task_state_machine.rs`(can_transition / regression_target) |
|
||||
| **F-260616-02** [P1] | **advance_task 推进逻辑(df-nodes `task_advance_node.rs` 实现 Node trait)**:校验转换 + 原子写(下沉 SQL `WHERE status=:expected` 防 TOCTOU)。df-nodes 需补 `df-storage` 依赖读 TaskRecord(核实无循环)。IPC 层 thin 入口调 df-nodes。 | D-03✅ df-nodes, F-01 | ✅ `advance_task_atomic`(CAS SQL) + IPC 瘦入口 |
|
||||
| **F-260616-03** [P1] | `status` 移出 `update_task` 白名单(推进链唯一收口) | F-02(关联 B-260616-16) | ✅ `settings.rs` tasks 白名单 `status` 不列入(注释 D-260616-04) |
|
||||
| **F-260616-04** [P2] | `review_rounds` 字段(退回时 +1,任务卡显示「第 N 轮 review」) | F-01 | ✅ `advance_status_atomic` 退回转换原子 +1 |
|
||||
| **F-260616-05** [P1] | 前端 TaskDetail 推进按钮(手动推进,不接 AI) | F-02, F-03 | ✅ `views/TaskDetail.vue` 推进按钮 |
|
||||
|
||||
此阶段不接 AI/工作流,纯人工推进,但状态机保护和收口到位。
|
||||
|
||||
@@ -52,14 +54,16 @@
|
||||
|
||||
目标:工作流执行能回写任务状态。
|
||||
|
||||
**F-260616-06** [P1](聚合):
|
||||
1. `run_workflow` IPC 支持 `task_id` 参数(去 workflow.rs:56 None 硬编码)
|
||||
2. 工作流完成回调 → 检查 task_id → 推进任务状态
|
||||
3. 定义任务推进 DAG 模板(AiNode 执行 + AiNode 自审 + HumanNode 核对)
|
||||
4. `advance_task` 触发对应闸门工作流
|
||||
5. 前端展示工作流执行进度
|
||||
✅ **已落(F-260616-06, 2026-06-16~17)**:实施路径详见 [推进链阶段2实施路径-2026-06-16.md](./推进链阶段2实施路径-2026-06-16.md)。
|
||||
|
||||
**依赖**:阶段 1 完成。详见报告 §8 阶段 2。
|
||||
**F-260616-06** [P1](聚合):
|
||||
1. ✅ `run_workflow` IPC 支持 `task_id` / `target_status` 参数(去硬编码 None,向后兼容 Option)— `workflow.rs` run_workflow / run_workflow_inner
|
||||
2. ✅ 工作流完成回调 → 检查 task_id + target_status → 推进任务状态(②-3);失败按 `regression_target` 退回(②-4)
|
||||
3. ✅ 定义任务推进 DAG 模板(AiNode 执行 + AiNode 自审 + HumanNode 核对)— `crates/df-nodes/src/task_workflow_templates.rs`(三模板 in_progress/testing/done,空 dag + target_status 时 `template_for` 自动选)
|
||||
4. ⚠️ `advance_task` 触发对应闸门工作流 — 经 run_workflow(task_id,target_status) 间接达成(手动/审批路径),advance_task 原子推进本身直调状态机不走工作流(设计如此)
|
||||
5. ⚠️ 前端展示工作流执行进度 — EventBus 事件转发已落(workflow-event),进度 UI 展示待补
|
||||
|
||||
**依赖**:阶段 1 完成 ✅。详见报告 §8 阶段 2。
|
||||
|
||||
---
|
||||
|
||||
@@ -67,14 +71,16 @@
|
||||
|
||||
目标:AI 能真正执行任务内容。
|
||||
|
||||
**F-260616-07** [P2](聚合):
|
||||
1. `advance_task` AI 工具(让 AI 经合法路径推进)
|
||||
2. `run_workflow` AI 工具注册实装(核对:tool_registry.rs **无此工具**,需新建)
|
||||
3. AiNode 接入任务上下文(读任务描述 + 项目目录)
|
||||
4. AI 自审 verdict 结构化输出 + 解析
|
||||
5. 失败路径完整处理(退回/重做/保持)
|
||||
🟡 **部分落(F-260616-07)**:`ai_self_review` gate 已落,AI 工具注册/上下文注入部分待核。
|
||||
|
||||
**依赖**:阶段 2 完成。详见报告 §8 阶段 3。
|
||||
**F-260616-07** [P2](聚合):
|
||||
1. ✅ `advance_task` AI 工具(让 AI 经合法路径推进)— `crates/df-mcp/src/tools.rs` advance_task spec + 实现(经 `advance_task_atomic` 走状态机,不走旁路裸改)
|
||||
2. ⚠️ `run_workflow` AI 工具注册实装(核对:tool_registry.rs **无此工具**,需新建)— **已注册** `df-mcp/tools.rs:120`(High 风险默认拒绝桩 `tools.rs:654`,实装待核:当前返回"默认拒绝请到应用内执行")
|
||||
3. ⚠️ AiNode 接入任务上下文(读任务描述 + 项目目录)— 任务上下文注入待核(模板节点级 config 留空,运行时全局 config 注入路径已有,AiNode 是否读 task_id 取描述待核)
|
||||
4. ✅ AI 自审 verdict 结构化输出 + 解析 — `crates/df-nodes/src/ai_self_review_node.rs`(gate:true,verdict=fail 触发工作流 failed → ②-4 退回;详见 [AiNode自审实施方案-2026-06-16.md](./AiNode自审实施方案-2026-06-16.md))
|
||||
5. ✅ 失败路径完整处理(退回/重做/保持)— `regression_target` 收敛状态机,②-4 回调退回 + review_rounds+1
|
||||
|
||||
**依赖**:阶段 2 完成 ✅。详见报告 §8 阶段 3。
|
||||
|
||||
---
|
||||
|
||||
@@ -103,7 +109,7 @@ D-03 架构落点 ──▶ F-02
|
||||
D-04 路径取舍 ──▶ 阶段1 是否先行
|
||||
```
|
||||
|
||||
**✅ 阶段 1 可启动(2026-06-16)**:D-01(前端 7 态)/ D-03(df-nodes Node)/ D-04(先行)三决策已定。阶段 1 ~200 行,从 0% 推进能力到「手动推进闭环」。df-nodes 落点核实可行(Node trait 纯接口 `df-workflow/src/node.rs:67`,现有 AiNode/HumanNode/ScriptNode,需补 `df-storage` 依赖无循环)。
|
||||
**✅ 阶段 1 已落 / 阶段 2 已落 / 阶段 3 部分(2026-06-16~17)**:D-01(前端 7 态)/ D-03(df-nodes Node)/ D-04(先行)三决策已定并实施。推进能力实现度已从 0% 推进到「阶段1手动推进闭环 ✅ + 阶段2工作流回写任务 ✅ + 阶段3 ai_self_review gate ✅(AI 工具实装/AiNode 上下文注入待核)」。df-nodes 落点已落实(Node trait 纯接口,已补 `df-storage` 依赖无循环)。
|
||||
|
||||
---
|
||||
|
||||
@@ -112,14 +118,14 @@ D-04 路径取舍 ──▶ 阶段1 是否先行
|
||||
> 主文件 todo.md 并发修改频繁(后台代理),以下指针待稍后合并。合并时在「待决策」区块(D-260616-04 后)插入:
|
||||
|
||||
```
|
||||
### 🗺️ 任务推进链实施路径(2026-06-16 规划·供其他会话读取)
|
||||
### 🗺️ 任务推进链实施路径(2026-06-16 规划·2026-06-20 回填实施状态)
|
||||
|
||||
> 详见 [任务推进链实施路径-2026-06-16.md](./任务推进链实施路径-2026-06-16.md)。
|
||||
> 推进能力实现度 0%。**阶段 1 已解除阻塞(D-01/D-03/D-04 三决策已定 2026-06-16),可启动 F-01~05**。
|
||||
> 核对纠正:AI 有 update_task/run_command 工具,无 run_workflow/advance_task。
|
||||
> 推进能力实现度:阶段1 ✅ + 阶段2 ✅ + 阶段3 部分(ai_self_review gate ✅,run_workflow AI 工具实装 / AiNode 上下文注入待核)。
|
||||
> 核对纠正:AI 有 update_task/run_command 工具,advance_task 已补(阶段3),run_workflow 已注册但默认拒绝桩待实装。
|
||||
|
||||
- [ ] F-260616-01~05 阶段1 推进骨架(状态机+advance_task+收口+rounds+前端按钮)
|
||||
- [ ] F-260616-06 阶段2 工作流联动(task_id+回调+DAG模板)
|
||||
- [ ] F-260616-07 阶段3 AI 执行闭环(advance_task/run_workflow 工具+AiNode+自审)
|
||||
- [x] F-260616-01~05 阶段1 推进骨架(状态机+advance_task+收口+rounds+前端按钮)
|
||||
- [x] F-260616-06 阶段2 工作流联动(task_id+回调+三 DAG 模板,进度 UI 待补)
|
||||
- [ ] F-260616-07 阶段3 AI 执行闭环(advance_task✅ / run_workflow 默认拒绝桩 / AiNode 上下文注入 / ai_self_review✅)
|
||||
- [ ] F-260616-08 阶段4 Git 集成(kind+git闸门+worktree)
|
||||
```
|
||||
|
||||
148
docs/02-架构设计/专项设计/全局事件数据总线-2026-06-21.md
Normal file
148
docs/02-架构设计/专项设计/全局事件数据总线-2026-06-21.md
Normal file
@@ -0,0 +1,148 @@
|
||||
# 全局事件数据总线设计
|
||||
|
||||
> 2026-06-21 · 专项设计 · 状态:构想定稿待评审
|
||||
> 关联:[[cross-end-rust-backend]] 三层跨端 / [[devflow-product-positioning]] ai-working 可扩展 / F-260620-01 跨端小程序
|
||||
|
||||
## 背景与动机
|
||||
|
||||
ai-working(u-work)产品定位要求**能力可插拔**(编码 wedge → 扩展文档/数据/分析/办公自动化)+ **跨端**(df-tunnel/relay/miniapp)。当前模块间直接函数调用/import 耦合,阻碍扩展:
|
||||
- 新能力接入需改现有模块(import 调用方)
|
||||
- 跨端模块(小程序/移动)无法直接调本地模块
|
||||
- 长时操作(压缩/工具执行)用同步 await,**死等体验差**(F-15 压缩卡死根因之一)
|
||||
|
||||
**全局事件数据总线**统一解决:模块经总线通信(pub-sub + request-reply + 流式),无直接耦合,响应式,跨端透传。
|
||||
|
||||
**核心洞察**(异步事件 + 同步等待 = 同步调用跨模块解耦):
|
||||
- 发请求事件(带 correlation_id)+ 等 reply 事件(oneshot await)→ 调用方语义同步,底层跨模块解耦(无 import 依赖)
|
||||
- **流式 reply**(chunk 流)→ 响应式渐进(压缩/工具输出实时),根治死等
|
||||
|
||||
## 现状分析(碎片)
|
||||
|
||||
| 现有 | 范围 | 缺陷 |
|
||||
|---|---|---|
|
||||
| df-workflow `EventBus`(`crates/df-workflow/src/eventbus.rs`,tokio broadcast) | 工作流节点间 | 仅 `WorkflowEvent`,非通用 |
|
||||
| Tauri `emit/listen`(`@tauri-apps/api/event`) | 后端→前端单向 | 无 request-reply;前端→后端靠 IPC 命令(非事件) |
|
||||
| 前端专用事件(ai-drain-queue / ai-approval-clear-timers / ai-conversation-changed / ai-tool-slow-toast) | 前端破环/联动 | 碎片化,无统一规范 |
|
||||
| `AiChatEvent` 通道(AiCompleted/AiError/AiCompressing/AiTextDelta...) | 后端→前端 AI 流 | 单向,多监听器按 type 分发,无 reply |
|
||||
| AR-11 `df-data-changed` | 数据变更联动 | 雏形,未泛化(仅 AI 工具触发) |
|
||||
|
||||
**缺口**:无统一总线 / 无 request-reply(同步语义跨模块)/ 无流式 reply / 无跨端透传。
|
||||
|
||||
## 概念
|
||||
|
||||
**全局事件数据总线**(Unified Event Bus):贯穿后端 + 前端 + 跨端的单一事件通道,支持三类语义:
|
||||
|
||||
### 1. pub-sub(发布订阅)
|
||||
数据变更/状态广播。发布者 `publish(event)`,订阅者 `subscribe(filter)`。泛化 AR-11(任务/知识/项目/灵感 增删改统一)。
|
||||
|
||||
### 2. request-reply(请求-回复)— 同步调用跨模块解耦
|
||||
- 请求方:`reply = bus.request(RequestEvent{payload, deadline}).await`(oneshot 等 reply)
|
||||
- 响应方:订阅 RequestEvent 类型,处理后 `bus.reply(reply_to, ResponseEvent)`
|
||||
- **语义同步**(await),底层异步事件,跨模块无 import
|
||||
- 超时/取消:request 带 deadline,reply oneshot 超时 → Err(软超时,非死等)
|
||||
- 适用:压缩请求 / 工具执行 / 审批(用户决策 reply)
|
||||
|
||||
### 3. 流式 reply(响应式渐进)— 根治死等
|
||||
- 请求方:`stream = bus.request_stream(RequestEvent).await; while let Some(chunk) = stream.next().await`
|
||||
- 响应方:reply_tx 发多个 chunk(渐进),结束发 Done
|
||||
- **chunk 持续 = 数据活性**,流断即知(非 60s 死等);无数据 N 秒判 hang(软超时)
|
||||
- 适用:压缩流式(LLM chunk)/ 工具执行流式(命令输出实时)/ AI 对话流(AiTextDelta 雏形)
|
||||
|
||||
## 设计
|
||||
|
||||
### 架构
|
||||
|
||||
```
|
||||
┌─────────────── 后端(tokio)───────────────┐
|
||||
│ UnifiedEventBus │
|
||||
│ ├─ broadcast<DomainEvent>(pub-sub) │
|
||||
│ ├─ 类型路由 HashMap<ReqType, mpsc::Sender<Request>>(request-reply)│
|
||||
│ ├─ oneshot reply + mpsc stream chunk │
|
||||
│ └─ df-tunnel adapter(跨端桥接) │
|
||||
│ │
|
||||
│ 模块(AI/任务/知识/工作流/工具/新能力) │
|
||||
│ 订阅 + 发布 + reply,无相互 import │
|
||||
└──────────────────────────────────────────────┘
|
||||
▲ Tauri event bridge(后端↔前端)
|
||||
▼
|
||||
┌─────────────── 前端(Vue3)─────────────────┐
|
||||
│ UnifiedEventBus 镜像(Tauri listen 转发) │
|
||||
│ composables 订阅/发布,store 响应式更新 │
|
||||
└──────────────────────────────────────────────┘
|
||||
▲ df-tunnel/relay 透传
|
||||
▼
|
||||
┌─────────────── 跨端(小程序/移动)──────────┐
|
||||
│ UnifiedEventBus 远程端(relay 转发) │
|
||||
│ 远程模块订阅/请求,经 relay 透传到本地 │
|
||||
└──────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
### 事件类型
|
||||
- **DomainEvent**(pub-sub):
|
||||
- `EntityChanged{entity, id, op: Create/Update/Delete}` — 泛化 AR-11(任务/知识/项目/灵感/工作流 CRUD 统一)
|
||||
- `StateChanged{entity, id, from, to}` — 任务状态机/审批态/会话态
|
||||
- `Lifecycle{kind, id}` — 会话开关/Provider 变更
|
||||
- **RequestEvent**(request-reply):`{type, payload, reply_to: oneshot::Sender, deadline}`
|
||||
- `CompressRequest` / `ToolExecRequest` / `ApprovalRequest` / `KnowledgeExtractRequest`
|
||||
- **StreamChunk**(流式):`{stream_id, chunk: String/Bytes, done: bool}` — LLM/工具输出渐进
|
||||
|
||||
### request-reply 实现雏形(Rust)
|
||||
```rust
|
||||
struct UnifiedEventBus {
|
||||
domain_tx: broadcast::Sender<DomainEvent>,
|
||||
req_handlers: HashMap<ReqType, mpsc::Sender<Request>>,
|
||||
}
|
||||
impl UnifiedEventBus {
|
||||
pub fn publish(&self, e: DomainEvent) { let _ = self.domain_tx.send(e); }
|
||||
pub fn subscribe(&self) -> broadcast::Receiver<DomainEvent> { self.domain_tx.subscribe() }
|
||||
|
||||
// request-reply(同步语义,跨模块解耦)
|
||||
pub async fn request<R: Request>(&self, req: R) -> Result<R::Reply> {
|
||||
let (tx, rx) = oneshot::channel();
|
||||
self.dispatch(req.into(tx)).await?;
|
||||
tokio::time::timeout(req.deadline(), rx).await??
|
||||
}
|
||||
// 流式 reply(响应式渐进)
|
||||
pub async fn request_stream(&self, req: R) -> Result<mpsc::Receiver<StreamChunk>> { ... }
|
||||
// 响应方注册 handler
|
||||
pub async fn handle<R: Request>(&self, handler: impl Handler<R>) { ... }
|
||||
}
|
||||
```
|
||||
|
||||
### 跨端透传(df-tunnel/relay)
|
||||
- 本地 bus adapter:DomainEvent/Request 经 df-tunnel 序列化透传到远程端
|
||||
- 远程端(小程序)bus:订阅本地事件 + 发请求(经 relay 回本地处理)
|
||||
- **统一抽象**:本地/远程模块代码一致(经 bus,不知对端远近)— 契合 ai-working 跨端定位
|
||||
|
||||
## 应用场景
|
||||
|
||||
1. **响应式压缩**(替 F-15 超时死等):AI 发 `CompressRequest` → 压缩 handler 订阅 + `provider.stream` 流式 reply chunk → AI `request_stream().await` 渐进收 + 前端订阅 chunk 显进度 → chunk 持续(非死等),无数据软超时(非 60s 死等)
|
||||
2. **工具执行经总线**:AI 发 `ToolExecRequest` → 工具 handler + reply;path_auth 未授权 → `ApprovalRequest`(总线 request-reply,用户审批 reply)
|
||||
3. **模块可插拔**(ai-working):新能力(文档/数据/分析)订阅总线 Request,不改 AI/任务模块(零侵入扩展)
|
||||
4. **数据联动泛化**:AR-11 df-data-changed → `DomainEvent::EntityChanged` 泛化(任务/知识/项目/灵感/工作流 CRUD 统一,前端 store 订阅刷新)
|
||||
5. **跨端 AI Chat**:小程序经总线订阅本地 AiTextDelta 流 + 发消息请求(F-260620-01)
|
||||
|
||||
## 迁移路径(分阶段,每阶段独立可发布/可回退,配 feature flag)
|
||||
|
||||
1. **阶段1 后端 UnifiedEventBus 基础**:broadcast + request-reply(oneshot)+ 流式(mpsc)。df-workflow EventBus 包装为通用 DomainEvent。单测。
|
||||
2. **阶段2 首个应用:响应式压缩**:`CompressRequest` + 流式 reply(替 `compress_via_llm` await 死等,撤销 F-15 60s 硬 timeout)。验证 request-reply/流式可行。
|
||||
3. **阶段3 前端镜像**:Tauri bridge(后端 DomainEvent → 前端总线,前端订阅)。ai-drain-queue/ai-conversation-changed/ai-approval-clear-timers 等碎片迁移统一。
|
||||
4. **阶段4 数据联动泛化**:AR-11 → `DomainEvent::EntityChanged`(任务/知识/项目/灵感 CRUD 统一,前端 store 订阅刷新)。
|
||||
5. **阶段5 工具/审批经总线**:`ToolExecRequest` / `ApprovalRequest`(path_auth/risk 审批统一为 bus request-reply)。
|
||||
6. **阶段6 跨端透传**:df-tunnel adapter + 小程序远程端(F-260620-01)。
|
||||
|
||||
## 与现有关系
|
||||
- [[cross-end-rust-backend]] 三层(df-tunnel/relay/miniapp):总线跨端透传基础
|
||||
- [[devflow-product-positioning]] ai-working 可扩展:模块可插拔经总线(编码特化 → 通用工作能力)
|
||||
- df-workflow EventBus:升级/包装为通用 bus 一部分(不废弃,复用 broadcast 基础)
|
||||
- AR-11 df-data-changed:泛化为 `DomainEvent::EntityChanged`
|
||||
- F-260620-01 跨端小程序:总线远程端首个跨端应用
|
||||
- F-15 上下文压缩:阶段2 响应式压缩经总线,替超时死等
|
||||
|
||||
## 待决策
|
||||
1. **bus crate 位置**:新建 `df-bus`?df-workflow eventbus 升级为通用?df-ai-core?
|
||||
2. **事件 schema**:强类型 enum(编译期安全,扩展需改 enum)vs 动态 JSON(灵活,弱类型)→ 倾向强类型 + 开放式(未知类型透传)
|
||||
3. **request-reply 路由**:类型路由 HashMap<ReqType, Sender>(强类型)vs topic 字符串(灵活)
|
||||
4. **跨端序列化**:serde JSON(可读,调试友好)vs bincode(紧凑,性能)
|
||||
5. **现有事件迁移节奏**:AiChatEvent/前端专用事件一次性迁移 vs 渐进(新功能用 bus,旧的逐步迁)
|
||||
6. **背压/限流**:broadcast 慢消费者丢弃 vs mpsc 背压(请求-回复)
|
||||
@@ -51,7 +51,7 @@ candidate ──→ pending_review ──→ published ──→ archived
|
||||
|---------|------|
|
||||
| `knowledge_list(status?)` | 列表,默认仅 published(library 纯已发布,F-260616-02 决策 a);显式传 status 按该状态过滤(含 archived) |
|
||||
| `knowledge_get(id)` | 单条查询 |
|
||||
| `knowledge_search(query, kind?, limit?)` | LIKE 检索,top-N≤3 |
|
||||
| `knowledge_search(query, kind?, limit?)` | LIKE 检索,默认 top-3,`limit.clamp` 上限 20(`min(20)`) |
|
||||
| `knowledge_create(input)` | 创建(status=candidate)|
|
||||
| `knowledge_update_status(id, status)` | 状态转换(含合法矩阵校验)|
|
||||
| `knowledge_record_reuse(id)` | reuse_count +1 |
|
||||
@@ -68,9 +68,8 @@ candidate ──→ pending_review ──→ published ──→ archived
|
||||
```rust
|
||||
pub struct KnowledgeConfig {
|
||||
pub auto_extract: bool, // 提炼总开关,默认 true
|
||||
pub trigger_mode: ExtractTrigger, // on_complete | on_idle | manual_only
|
||||
pub trigger_mode: ExtractTrigger, // on_complete | manual_only(OnIdle 已下线,见 KP-2)
|
||||
pub min_messages: u32, // 守卫:最少消息数,默认 4
|
||||
pub idle_timeout_ms: u64, // 闲置触发超时,默认 30000
|
||||
pub auto_inject: bool, // 聊天注入开关,默认 true
|
||||
pub vector_enabled: bool, // 向量检索开关,默认 false
|
||||
pub embedding_provider_id: Option<String>, // 仅 openai_compat 类型
|
||||
@@ -78,7 +77,7 @@ pub struct KnowledgeConfig {
|
||||
}
|
||||
```
|
||||
|
||||
存储:`AppState.knowledge_config: Arc<Mutex<KnowledgeConfig>>`(内存,重启恢复默认值)。
|
||||
存储:`AppState.knowledge_config: Arc<Mutex<KnowledgeConfig>>` 为内存真相源,并通过 `SettingsRepo` KV 表(key=`df-knowledge-config`)持久化;`knowledge_save_config` 落库、`AppState::init` reload 恢复(KP-1 已修复)。
|
||||
|
||||
---
|
||||
|
||||
@@ -140,24 +139,34 @@ Tier 1+ 目标:MCP Shell 封装(`mcp-server` 转发 IPC),外部工具使
|
||||
## 已知问题(2026-06-16 审查)
|
||||
|
||||
> 来源:全量源码逐行审查(knowledge.rs / knowledge_inject.rs / knowledge_timeline.rs / stores/knowledge.ts / Knowledge.vue / agentic.rs / commands.rs / state.rs)
|
||||
>
|
||||
> **修复进度汇总(2026-06-21):**
|
||||
> - ✅ 已修复:**KP-1** 配置持久化 / **KP-2** OnIdle 下线;跨审查项 **R-P1-4** `build_provider` 统一工厂(secret::build_provider_for)、**CR-28** error 通道(AiError emit 覆盖 build_provider_for Err 分支)、**CR-29** 审批等待态竞态(B-260615-26 guard disarm + try_continue generating 复位重检)。
|
||||
> - 🔧 本 workflow 修复中:**KP-3**(try_continue 知识注入)/ **KP-5**(编辑生命线事件)/ **KP-6**(嵌入失败重试/标记)。
|
||||
|
||||
### 🔴 P0 严重
|
||||
|
||||
#### KP-1: KnowledgeConfig 不持久化 — 重启丢失全部配置
|
||||
#### KP-1: KnowledgeConfig 不持久化 — 重启丢失全部配置 ✅ 已修复
|
||||
|
||||
> **状态:已修复(P0 设置走查-2026-06-21)。** `knowledge_save_config` / `knowledge_get_config` 现读写 `SettingsRepo` KV(key=`df-knowledge-config`,JSON 序列化);`AppState::init` 增加 `reload_knowledge_config` 恢复。下方原文保留供溯源。
|
||||
|
||||
- **位置:** `state.rs:208` 初始化 + `knowledge.rs:knowledge_save_config`
|
||||
- **现象:** `knowledge_save_config` 仅写入内存 `Arc<Mutex<KnowledgeConfig>>`,未落库。应用重启后 `AppState::init` 永远用 `KnowledgeConfig::default()`
|
||||
- **影响:** 用户配置的向量检索 provider、提炼参数等关闭应用后全部丢失。系统已有 `app_settings` KV 表和 `SettingsRepo` 可用,但未接入
|
||||
- **修复方向:** `knowledge_save_config` / `knowledge_get_config` 读写 `SettingsRepo`(key=`knowledge_config`,value=JSON)
|
||||
|
||||
#### KP-2: OnIdle 触发模式完全未实现 — 配置项为死代码
|
||||
#### KP-2: OnIdle 触发模式完全未实现 — 配置项为死代码 ✅ 已修复(下线)
|
||||
|
||||
> **状态:已修复(OnIdle 下线)。** `ExtractTrigger` 仅保留 `OnComplete` / `ManualOnly` 两变体,`OnIdle` 变体与 `idle_timeout_ms` 字段已从 `state.rs` 移除(`idle_timeout_ms` 在 `src-tauri` 内零残留)。`trigger_mode` 取值同步收敛。下方原文保留供溯源。
|
||||
|
||||
- **位置:** `state.rs` 定义 `ExtractTrigger::OnIdle` + `idle_timeout_ms`;`knowledge_inject.rs:maybe_spawn_extraction`
|
||||
- **现象:** `maybe_spawn_extraction` 仅检查 `trigger_mode == OnComplete`,`OnIdle` 与 `ManualOnly` 都直接 `return Ok(())`。`idle_timeout_ms` 在整个 `src-tauri` 中零引用
|
||||
- **影响:** 用户选择 `OnIdle` 模式后,知识提炼永远不会触发(与 ManualOnly 行为相同但无任何提示)
|
||||
- **修复方向:** 要么实现 idle 定时器(tokio::time::interval 轮询 AiSession 最后活动时间),要么移除 OnIdle 变体并在前端隐藏该选项
|
||||
|
||||
#### KP-3: 审批恢复循环路径缺失知识注入
|
||||
#### KP-3: 审批恢复循环路径缺失知识注入 🔧 本 workflow 修复中
|
||||
|
||||
> **状态:本 workflow 修复中。** `try_continue_agent_loop`(agentic/mod.rs)现已在续跑轮重建 system prompt 时调用 `build_knowledge_context` 注入知识库上下文(参考代码 :1420-1444 区段注释)。下方原文保留供溯源。
|
||||
|
||||
- **位置:** `agentic.rs:try_continue_agent_loop` vs `commands.rs:ai_chat`
|
||||
- **现象:** `ai_chat` 首次消息时调用 `build_knowledge_context` 注入 system prompt;`try_continue_agent_loop`(审批通过后恢复循环)仅调用 `build_system_prompt`,**未调用** `build_knowledge_context`
|
||||
@@ -174,14 +183,18 @@ Tier 1+ 目标:MCP Shell 封装(`mcp-server` 转发 IPC),外部工具使
|
||||
- **影响:** 知识库 Tab 列表加载的是 `list_non_archived()`(含 candidate/pending_review/published),但搜索只能命中 published,导致列表显示条目数 > 可搜索条目数,体验矛盾
|
||||
- **修复方向:** 前端搜索走独立的 `knowledge_list` + 前端过滤,或后端新增 `search_all_status()` 方法供前端使用
|
||||
|
||||
#### KP-5: 编辑操作缺失生命线事件
|
||||
#### KP-5: 编辑操作缺失生命线事件 🔧 本 workflow 修复中
|
||||
|
||||
> **状态:本 workflow 修复中。** 计划在 `knowledge_timeline.rs` 新增 `EVENT_UPDATED` + `KnowledgeTimeline::record_updated()`,并接入 `knowledge.rs:knowledge_update`(:375-411)。下方原文保留供溯源。
|
||||
|
||||
- **位置:** `knowledge.rs:knowledge_update`
|
||||
- **现象:** `knowledge_create` 有 `record_created`,`knowledge_update_status` 有 `record_status_change`,但 `knowledge_update`(编辑 title/content/tags 等)**无任何 Timeline 调用**
|
||||
- **影响:** 知识被编辑修改后,生命周期时间线中不显示编辑记录,审计追踪存在断档
|
||||
- **修复方向:** 新增 `EVENT_UPDATED` 事件类型 + `KnowledgeTimeline::record_updated()` 方法
|
||||
|
||||
#### KP-6: 嵌入生成失败无重试/标记 — 可能永久无向量索引
|
||||
#### KP-6: 嵌入生成失败无重试/标记 — 可能永久无向量索引 🔧 本 workflow 修复中
|
||||
|
||||
> **状态:本 workflow 修复中。** 计划在 `knowledge_inject.rs:spawn_embedding_for_knowledge`(:91-118)增加失败标记/重试,配合 `migrations.rs`(knowledges 表)`embedding IS NULL` 补偿。下方原文保留供溯源。
|
||||
|
||||
- **位置:** `knowledge_inject.rs:spawn_embedding_for_knowledge`
|
||||
- **现象:** 发布时 fire-and-forget 生成嵌入,失败只 `warn log`,无重试机制、无失败标记
|
||||
|
||||
105
docs/05-代码审查/aichat-技术债审查-2026-06-21.md
Normal file
105
docs/05-代码审查/aichat-技术债审查-2026-06-21.md
Normal file
@@ -0,0 +1,105 @@
|
||||
# AI Chat 各模块技术债审查报告
|
||||
|
||||
> 日期:2026-06-21 | 方法:workflow 串行 10 单元,每单元独立 grep 核验(不信文档/注释) | 基线:src-tauri cargo check 0 error(L2 拆分后)
|
||||
> 产物:0 P0 / 7 P1 / 25 P2 / 40 P3 | 已登记 8 / 新债 64
|
||||
> 关联:[[code-review-anti-contamination]] [[devflow-todo-docs-split]] | 审查 workflow wf_92b61c35-4d2
|
||||
|
||||
## 模块健康度
|
||||
|
||||
| 模块 | 设计点 | 健康 | 债数 |
|
||||
|---|---|---|---|
|
||||
| 1-agentic-loop | 8 | {'healthy': 8} | 7 |
|
||||
| 2-stream-recv | 7 | {'healthy': 7} | 6 |
|
||||
| DevFlow AI Chat — 3-compress | 8 | {'healthy': 6, 'smell': 2} | 8 |
|
||||
| AI Chat · tool_registry.rs | 8 | {'healthy': 8} | 8 |
|
||||
| src-tauri/src/commands/ai/au | 12 | {'healthy': 12} | 7 |
|
||||
| 6-session-state | 7 | {'healthy': 6, 'smell': 1} | 7 |
|
||||
| 7-ipc-approval | 8 | {'healthy': 6, 'smell': 1, 'risk': 1} | 6 |
|
||||
| AI Chat【8-provider-pool】 | 7 | {'healthy': 7} | 6 |
|
||||
| 9-fe-events-cards | 9 | {'healthy': 9} | 9 |
|
||||
| 10-fe-conversation | 9 | {'healthy': 9} | 8 |
|
||||
|
||||
## P1(7 项 · 已修 2 / 归路线 5)
|
||||
|
||||
### [P1] watchdog 单计时器 + onStreamTimeout 全清式收尾,与 F-09 多会话并发冲突:整流超时(可能误报)会 clear 所有 generatingConvs + 清空 queue(含用户已排队未发的输入)+ 清 currentText,误伤其他正在生成的会话与静默丢弃排队用户输入
|
||||
- **位置**:src/composables/ai/useAiStream.ts:30-32
|
||||
- **根因**:_streamWatchdog 是模块级单计时器(useAiStream.ts:23)无 per-conv 隔离;onStreamTimeout 注释自陈「无法确定具体 conv,保守全清」。F-09 多会话并发下,A 会话超时会连带杀掉 B 会话的 generating 态;且 state.queue(useAiSend.ts:304 push 用户排队输入)被一刀切,用户排队消息静默丢失(注释称「防丢失」但 clear 本身即丢失)
|
||||
- **修法**:watchdog 改 per-conv Map<convId, timer>,onStreamTimeout 携带 convId 仅清该 conv 态;queue clear 改为仅清当前 conv 关联项或保留队列(超时不应删用户未发输入)
|
||||
- **归因**:并发/数据丢失 | 已登记:no
|
||||
|
||||
### [P1] patch_file 读-改-写在 FILE_LOCKS 之外,CAS 与 new_content 计算基于陈旧读,锁只序列化最终写不防 lost-update
|
||||
- **位置**:src-tauri/src/commands/ai/tool_registry.rs:1247
|
||||
- **根因**:L3 文件指纹校验(1176) + old_text CAS 比对(1209) + new_content 计算(1197-1240)全在「阶段一:无锁纯读」段;L1 tokio::Mutex 仅在「阶段二」保护 backup→tmp→rename 序列(1242-1269)。两个并发 patch_file 同文件:A/B 各自读旧内容→A 获锁写入释放→B 获锁用基于旧内容算的 new_content 覆盖 A 的改动(lost update)。FILE_LOCKS 注释明示「防同文件并发读写冲突」(270)但作用域界定错误,锁只防写写交错不防读写交错。
|
||||
- **修法**:将读文件+校验+new_content 计算整体移入 FILE_LOCKS 锁临界区(同一 abs_path 的 lock entry),或在锁内重读 content 再做 CAS;改 per-path 独立 Mutex(每个 abs_path 一把锁,非全局 HashMap 占位)让读改写串行。注意 write_file(Medium,trust_hits 并行)不取 FILE_LOCKS,patch_file+write_file 同文件并发也未被保护(同根因)。
|
||||
- **归因**:并发 | 已登记:no
|
||||
|
||||
### [P1] expected_hash schema 契约破裂:描述称「read_file 返回的 file_hash」,但 read_file 从不返回该字段;LLM 无从获取合法 hash,指纹防并发形同虚设
|
||||
- **位置**:src-tauri/src/commands/ai/tool_registry.rs:1112
|
||||
- **根因**:grep 全文件 file_hash 仅此一处(1112 schema 描述),read_file handler(892-976)返回 {path,content,size,lines,offset,returned_lines,has_more} 无 file_hash 字段。expected_hash 实际由 patch_file 内部 format!("{}_{}", modified_secs, len)(1180)即造即比,LLM 拿不到前置值只能瞎传或省略→校验常 mismatch bail 或被跳过。L3 防并发设计目标(防行号漂移)在实际 LLM 调用链上落空。
|
||||
- **修法**:二选一:(a) read_file 返回 file_hash(同 modified_secs_len 格式)让 LLM 闭环回传;(b) 去掉 schema 描述的来源声明,改文档约定由前端审批卡透传(但当前路径无前端介入)。优先 (a)。
|
||||
- **归因**:契约失配 | 已登记:no
|
||||
|
||||
### [P1] 审批状态字符串双轨:ai_approve/ai_authorize_dir 写 "executed",process_tool_calls/find_cached fallback 写 "completed",DTO 文档仅列 completed
|
||||
- **位置**:src-tauri/src/commands/ai/commands/chat.rs:466,668 (写 "executed") vs src-tauri/src/commands/ai/audit/mod.rs:490,545 + audit/cache.rs:113 (写 "completed");DTO 文档列 completed 在 df-storage/src/models.rs:221
|
||||
- **根因**:无 newtype/enum 约束 status 字段(对齐 SMELL-P1-6 String 滥用替 enum),两条写入路径各用不同字面量。审计表混存 executed/completed 两种『成功』态
|
||||
- **修法**:抽 status newtype 或 enum(对齐 SMELL-P1-6),统一成功态字面量(process 全改 executed 或 chat 全改 completed),消除双轨。行为变更需评估 AuditLog.vue 显示与 find_cached 透传影响
|
||||
- **归因**:类型/一致性 | 已登记:SMELL-P1-6(String 类型滥用,覆盖此根因,未单独登记 executed/completed 双轨)
|
||||
|
||||
### [P1] ai_approve 缺 60s 执行超时,与 ai_authorize_dir 不对称,卡死致 generating 永真+前端看门狗静默吞消息
|
||||
- **位置**:src-tauri/src/commands/ai/commands/chat.rs:461
|
||||
- **根因**:ai_authorize_dir 为 F-260620 加了 tokio::time::timeout(60s)(chat.rs:658),但同样执行用户审批工具的 ai_approve 路径(state.ai_tools.execute(chat.rs:461) / execute_run_workflow_for_tool(chat.rs:459))无超时。run_command 等慢命令或 list_directory 大目录在 ai_approve 执行路径卡住 → 到不了 audit_finalize/emit/try_continue → IPC 永挂 → per_conv.generating 永真 → 前端 130s 看门狗静默吞消息(F-260620 描述的同类故障在 ai_approve 重现)
|
||||
- **修法**:抽 helper execute_with_timeout(tool, args),ai_approve 与 ai_authorize_dir 共用同一 60s 超时包裹(含 run_workflow 分支统一),超时走 failed 分支
|
||||
- **归因**:并发/卡死 | 已登记:no
|
||||
|
||||
### [P1] switchConversation 不复位 state.streaming,真并发下切换非生成会话残留 stop 按钮 + 错停会话
|
||||
- **位置**:src/composables/ai/useAiConversations.ts:91-206(switchConversation 全程不触碰 state.streaming;对比 newConversation:73 显式置 false)
|
||||
- **根因**:F-09 决策e 真并发上线后,streaming 仍是全局单 boolean(state.ts:63)。switchConversation 加载历史时不复位 streaming,而 ChatInput.vue:88 v-if="store.state.streaming" 读此单值显示停止按钮。会话 A 生成中切到非生成的会话 B → streaming 仍 true → B 显示 stop 按钮 → 点击 store.stopChat 传 B 的 activeConversationId(:427)发出错会话的 stop IPC。AiChat.vue:204 showAgenticProgress(streaming && agentRound>0)同样残留。
|
||||
- **修法**:streaming 改 per-conv 态(归入 F-260616-09 B 路线 per-conv state 改造);或 switchConversation 入口按 isGenerating(targetConvId) 重算 streaming(active 在生成则 true 否则 false),与 newConversation:73 对齐。后者轻量可在 A 路线内补。
|
||||
- **归因**:竞态/状态残留 | 已登记:no
|
||||
|
||||
### [P1] pendingDirAuth/pendingMaxRounds 全局单 ref 非 per-conv,真并发下挂起弹窗可能错显于非挂起会话
|
||||
- **位置**:src/composables/ai/useAiEvents.ts:99(pendingMaxRounds=ref(false)) + :113(pendingDirAuth=ref<...|null>(null));消费方 DirAuthDialog.vue:60-62 / MaxRoundsCard.vue:54-56 仅判 isViewingGenerating 未比对 conversationId
|
||||
- **根因**:F-09 决策e 允许多会话并行,但挂起态仍是模块级单实例。pendingDirAuth 携 conversationId(:111/:239)但 DirAuthDialog:61 showDirAuthCard 守卫未用(pendingDirAuth.value.conversationId === activeConversationId);pendingMaxRounds 是纯 boolean 无 conversationId 字段,根本无法区分。会话 A 达 max 挂起(pendingMaxRounds=true)时切到同样在生成的会话 B → MaxRoundsCard 在 B 错显(因 isViewingGenerating(B)=true 且全局 pendingMaxRounds=true)。原注释 useAiEvents.ts:97『后端 AiSession 单例,达 max 后续轮次前用户必先决定』是单并发假设,真并发下失效。
|
||||
- **修法**:pendingMaxRounds 改 ref<string|null>(挂起 convId);pendingDirAuth 守卫加 conversationId 比对。归入 F-260616-09 B 路线 per-conv state。
|
||||
- **归因**:竞态/状态残留 | 已登记:no
|
||||
|
||||
## P2(25 项)
|
||||
|
||||
- **1-agentic-loop** | try_continue_agent_loop 中 pending_conv_id 跨会话收集,导致 stop 补发 AiCompleted 的 conversation_id 错绑其他会话 | `src-tauri/src/commands/ai/agentic/mod.rs:1314-1317` | todo:no
|
||||
- **1-agentic-loop** | run_agentic_loop 函数 ~950 行(L318-1265),单函数承载:provider 解析+意图收敛+主题标记+自动压缩+tool_result 摘要+fallback 重试+审批分支+收敛退出,God 函数 | `src-tauri/src/commands/ai/agentic/mod.rs:318` | todo:no
|
||||
- **2-stream-recv** | MidStream 保文路径 chunk.error / chunk.Err 返回 Partial 时未把 err_msg 透出,用户看不到中断原因(仅后端 warn! 日志),前端只补通用「响应不完整」系统提示,丢失具体错误诊断(如 GLM 1214 messages 非法) | `src-tauri/src/commands/ai/stream_recv.rs:275-281` | todo:no
|
||||
- **2-stream-recv** | onStreamTimeout 反向遍历 messages 改 toolCall.status = 'rejected',在多会话并发下会误回滚其他会话的 running 工具卡(与 P1 全清问题同源,单遍历无 conv 过滤) | `src/composables/ai/useAiStream.ts:48-60` | todo:no
|
||||
- **DevFlow AI Chat — 3-** | is_compressing 标志无 panic/cancel 兜底 — 任务被 cancel(runtime drop/进程关闭)或 compress_via_llm 内部 panic 时 set_compressing(false) 被跳过,标志永久滞留 true,该会话后续所有自动/手动压缩被 !prev_compressing(705)/is_compressing()(chat.rs:850) 永久拒绝 | `src-tauri/src/commands/ai/agentic/mod.rs:735 (set_compressin` | todo:no
|
||||
- **DevFlow AI Chat — 3-** | 自动压缩(agentic loop)成功后未即时 save_conversation — 仅依赖 loop 退出 :1223 落库。若 loop 在压缩后、退出前被 cancel/panic/审批等待态 return,则 status=compressed + 摘要 system 仅存内存,会话恢复(restore_from_messages 从 DB 重建)时压缩成果丢失,history_tokens 重新虚高 | `src-tauri/src/commands/ai/agentic/mod.rs:778-794 (成功路径无 save` | todo:no
|
||||
- **DevFlow AI Chat — 3-** | context.rs 1332+ 行 God 文件 — F-15/压缩/UX-09编辑/sanitize/主题检测多职责堆叠,F-15 链路仍频繁改动拆分窗口未到 | `crates/df-ai/src/context.rs:1-1552 (整文件,ContextManager impl ` | todo:REFACTOR-260619-09
|
||||
- **AI Chat · tool_regis** | FILE_LOCKS HashMap 条目永不清理,每 patch 过的路径永久占槽位(内存无界增长) | `src-tauri/src/commands/ai/tool_registry.rs:1248` | todo:no
|
||||
- **AI Chat · tool_regis** | expected_hash 指纹粒度仅秒级(mtime_as_secs + len),1 秒内连续外部修改检测不到,弱指纹 | `src-tauri/src/commands/ai/tool_registry.rs:1180` | todo:no
|
||||
- **AI Chat · tool_regis** | patch_file 大小校验用独立 tokio::fs::metadata 后再 File::open,存在 TOCTOU(metadata→open 间文件被换/膨胀),与 read_file 单次 open 取 metadata 的 FR-S2 收口不一致 | `src-tauri/src/commands/ai/tool_registry.rs:1156` | todo:no
|
||||
- **AI Chat · tool_regis** | bind_directory handler 注释承诺 reload 兜底但代码空头(tool_registry.rs:545-549),AI 绑定目录后白名单不刷新 | `src-tauri/src/commands/ai/tool_registry.rs:546` | todo:MED-1 已登记
|
||||
- **AI Chat · tool_regis** | REFACTOR-260619-08 tool_registry.rs(2210 行)仍待按功能分组注册函数拆,register_* 部分超 200 行(register_file_tools ~760 行) | `src-tauri/src/commands/ai/tool_registry.rs:881` | todo:REFACTOR-260619-08
|
||||
- **src-tauri/src/comman** | ai_approve 缺执行超时包装(ai_authorize_dir 有 60s 防卡死,ai_approve 裸 execute) | `src-tauri/src/commands/ai/commands/chat.rs:461 (ai_approve 裸` | todo:no
|
||||
- **src-tauri/src/comman** | find_cached_high_risk_result 持 session &mut borrow 期间串行 await DB 查询,High risk 工具多时锁持有线性增长 | `src-tauri/src/commands/ai/audit/cache.rs:21-23 (性能注记自承) + ca` | todo:no(性能注记自承,CR-260618-11#5 衍生,未独立登 todo 条目)
|
||||
- **src-tauri/src/comman** | audit_tool_call 插入用 let _ = 吞 insert 错误,审计记录写失败无任何日志/告警 | `src-tauri/src/commands/ai/audit/finalize.rs:35 (let _ = repo` | todo:no
|
||||
- **6-session-state** | PerConvState.created_at 全死字段(0 写入) | `src/commands/ai/mod.rs:520` | todo:no
|
||||
- **6-session-state** | PendingApproval.diff 结构体字段写而不读(dead payload) | `src/commands/ai/mod.rs:570` | todo:no
|
||||
- **6-session-state** | process_tool_calls 持 session 锁做阻塞 fs canonicalize(粗粒度锁内同步 IO) | `src/commands/ai/audit/mod.rs:264` | todo:no
|
||||
- **7-ipc-approval** | switch/delete 路径 retain 清 pending 未配套 finalize_pending_placeholders,占位 tool_result 残留 messages | `src-tauri/src/commands/ai/commands/conversation.rs:317` | todo:no
|
||||
- **7-ipc-approval** | authz_debug 硬编码绝对路径 E:/wk-lab/devflow/authz-debug.log + 生产残留诊断代码(eprintln + 文件日志) | `src-tauri/src/commands/ai/commands/chat.rs:548` | todo:no
|
||||
- **AI Chat【8-provider-p** | agentic/mod.rs run_agentic_loop 单函数超长(83390 字节/约 1231 行),fallback 循环 + 工具执行 + 审批 + 保文路径全在一函数 | `src-tauri/src/commands/ai/agentic/mod.rs:318 run_agentic_loo` | todo:ARC-260619-05(REFACTOR-260619-05 已部分完成,D 段待 F-09 批4)
|
||||
- **9-fe-events-cards** | AiMessage.status 为 string cast(AiMessageWithStatus),消息级 status 无联合类型约束,archived_segment/compressed/truncated 全靠注释枚举 | `src/components/ai/MessageList.vue:686-688 (interface AiMessa` | todo:no
|
||||
- **9-fe-events-cards** | onStreamTimeout 全清 generatingConvs(多会话并发下误杀其他正常生成会话) | `src/composables/ai/useAiStream.ts:30 (state.generatingConvs.` | todo:no
|
||||
- **10-fe-conversation** | switchConversation 不复位 agentRound,切到已完成会话残留旧轮次(进度条门控依赖 streaming 可部分遮蔽) | `src/composables/ai/useAiConversations.ts:91-206(switchConver` | todo:no
|
||||
- **10-fe-conversation** | loadConversations 失败时把错误气泡 push 进 state.messages,污染当前对话历史(而非用 toast) | `src/composables/ai/useAiConversations.ts:51-61 + switchConve` | todo:no
|
||||
|
||||
## P3(40 项 · 摘要)
|
||||
|
||||
- **1-agentic-loop**(5项): guard.rs 文档注释声称"双写顶层 session.generating=; guard.rs Drop 异步 spawn 复位与正常路径 reset().a; iteration 顶部 stop 检查(L581)的 save 用 None ; 候选链 candidate_chain 提前 clone primary(L97...
|
||||
- **2-stream-recv**(3项): classify_status_or_class 与 extract_error; STREAM_TIMEOUT_MS=130s vs 后端 STREAM_IDLE; scheduleStreamParse 的 rAF 回调内 streamingB
|
||||
- **DevFlow AI Chat — 3-comp**(5项): should_compress 检查与 set_compressing(true; messages_mut() 被迫用于只读读取 — 读取保护区外 active ; compress.rs #[allow(dead_code)] 过期残留 — c; 双监听器同通道('ai-chat-event')人工协调防双重处理 — useA...
|
||||
- **AI Chat · tool_registry.**(1项): run_command 越界防线弱:working_dir 仅走 validat
|
||||
- **src-tauri/src/commands/a**(3项): Low risk 失败工具硬编码中文错误文案『工具 {} 执行失败: {}』直送; build_approval_reason 三层 fallback(tool_d; path_auth_pending 挂起占位无去重保护(与 High risk
|
||||
- **6-session-state**(4项): session_state() 读视图 0 消费者(P0 终态化未承接); guard.rs 注释声称双写顶层 session.generating 但代码; best_effort_canonicalize 文件不存在时连查父目录,失败回; releases/node_executions Repo 持久化就位但 IPC
|
||||
- **7-ipc-approval**(3项): ai_authorize_dir 60s 超时与 generating 状态:超; ai_chat_clear 无 conversation_id 参数,只能清 a; ai_approve 拒绝路径 _recovered 读后未用,审批拒绝无 se
|
||||
- **AI Chat【8-provider-pool】**(5项): release_conv 仅 ai_conversation_delete 调用; reload_provider_caps 给每 provider 设 cap=g; set_per_conv / set_global 缺内部 permits=0 ; set_per_conv 清空 HashMap 软收敛期间,同 conv 的在跑...
|
||||
- **9-fe-events-cards**(7项): 双监听器同通道(useAiEvents + useAiContext 各 lis; AiStreamRetry 每次重试重置看门狗(不在 NO_RESET_WATC; _blockCache(300 条 Map)跨会话永不清理,长期会话累积陈旧条目; combineAndTruncateLines/formatCommandRes...
|
||||
- **10-fe-conversation**(4项): switchConversation pendingApprovals 恢复修改; switchConversation 内局部变量 t 覆盖模块级 t impor; doSend/regenerate/editMessage 三处 streami; modelOverride 切换对话不同步清(注释自述需同步清但代码未做)
|
||||
@@ -3,6 +3,8 @@
|
||||
> 来源:fe-arch-review 子代理多角度审查(目录/组件粒度/状态/类型/依赖/路由/命名/测试/AI coding 友好度)。
|
||||
> 整体:**B+(AI coding 友好 7.5/10)**。类型基础扎实(strict + vue-tsc 0)、命名一致(A 95/100)、依赖无环、死代码 1.7%。债集中在 AI 模块体量 + status 类型化。
|
||||
> 已修复:i18n `as any` 样板收口(i18n-helpers.ts,as any 22→10,i18n 13→0,commit f4cc2e5)。
|
||||
>
|
||||
> **核对更新(2026-06-20·本次清理)**:P1-1 AiChat.vue **4075→750 已拆 God**(REFACTOR-07/SMELL-P0-3,子组件 ConversationSidebar/MessageList/ChatInput/TopBar/DirAuthDialog/MaxRoundsCard 进 `ai/`)/ ToolCard.vue **1066→374**(拆出 ToolCardList 422 + ToolResultBody)/ MessageList 已进 `ai/`(P1-2 部分兑现)。**P0 未动**:useAiSend/useAiEvents/useToolCard 三大件拆 + status union 仍待。另:`utils/time.ts formatRelativeZh→formatRelative` 重命名落地(SW-260618-21,消除 Zh 误导)。
|
||||
|
||||
## P0(阻碍 AI coding)
|
||||
|
||||
@@ -17,8 +19,8 @@
|
||||
|
||||
| # | 项 | 影响 |
|
||||
|---|---|---|
|
||||
| P1-1 | AiChat.vue(1474)/MessageList.vue(1444)/ToolCard.vue(1066) 仍过重 | AI 改 AI 模块上下文巨大 |
|
||||
| P1-2 | `components/` 一级散落 AiChat/ToolCard/ToolCardList(应进 ai/) | 找文件靠记忆 |
|
||||
| P1-1 | AiChat.vue(**1474→750 已拆 God**)/MessageList.vue(1445)/ToolCard.vue(**1066→374 拆出 ToolCardList+ToolResultBody**) — AiChat/ToolCard 已收敛,**MessageList 仍重** | AI 改 AI 模块上下文巨大 |
|
||||
| P1-2 | `components/` 一级散落 AiChat(薄壳容器)/ToolCard/ToolCardList/ToolResultBody — **MessageList 已进 ai/**,余仍一级 | 找文件靠记忆 |
|
||||
| P1-3 | `catch (e: any)` 30+ 处 | 应改 unknown + Error 收窄 |
|
||||
| P1-4 | `App.vue` 8 职责(布局已拆 layout/ → AppLayout/AppSidebar,App.vue 546→179,余初始化职责) | layout 已抽取,App.vue 收敛 |
|
||||
| P1-5 | JSON.parse 后 cast 无守卫 4 处(IdeaDetail/TaskOutputCard/useToolCard/useAiConversations) | 后端格式漂移运行时崩 |
|
||||
|
||||
122
docs/todo.md
122
docs/todo.md
@@ -26,6 +26,30 @@
|
||||
|
||||
## 待办
|
||||
|
||||
### 🔍 2026-06-21 AI Chat 技术债审查(workflow wf_92b61c35-4d2 · 10 模块串行 grep 核验)
|
||||
|
||||
> 详单见 [aichat-技术债审查-2026-06-21.md](./05-代码审查/aichat-技术债审查-2026-06-21.md)。0 P0 / 7 P1 / 25 P2 / 40 P3 / 已登记 8 / 新债 64。**P1#5/#6 已即时修并自验过**。
|
||||
|
||||
**P1 已即时修(2)**:
|
||||
- [x] ✅(主代修·chat.rs:458-461 ai_approve exec_result 包 tokio::time::timeout(60s) 对齐 ai_authorize_dir:658·cargo check EXIT 0) **TD-260621-05a** — **[P1🔴]** ai_approve 缺 60s 执行超时(用户"静默吞消息"bug F-260620 同型复发)。ai_authorize_dir 有超时 ai_approve 漏。卡死→generating 永真→前端 130s 看门狗吞消息。
|
||||
- [x] ✅(主代修·useAiConversations.ts:99 switchConversation activeConversationId 设定后加 `state.streaming = state.generatingConvs.has(id)`·vue-tsc EXIT 0) **TD-260621-06a** — **[P1🔴]** switchConversation 不复位 streaming,真并发切非生成会话残留 stop 按钮→点 stop 发错会话。
|
||||
|
||||
**P1 归路线(5 项·涉大改/行为变更/需设计·未即时修)**:
|
||||
|
||||
- [x] TD-260621-01 [P1·归 F-09 B 路线 per-conv state] ✅(批2-A 落地·useAiStream `_watchdogTimers` Map<convId,timer> + `_legacyWatchdog` fallback 双路·onStreamTimeout 携 convId 仅清该 conv·2026-06-21) — **watchdog 单计时器全清误杀并发会话**。`useAiStream.ts:30` `_streamWatchdog` 模块级单计时器无 per-conv 隔离;`onStreamTimeout` 全清 generatingConvs+queue+currentText。F-09 真并发下 A 超时连带杀 B + 静默丢用户排队输入(注释称"防丢失"但 clear 即丢失)。修:watchdog 改 per-conv Map<convId,timer>,onStreamTimeout 携 convId 仅清该 conv;queue clear 改仅清当前 conv 关联。归 F-260616-09 B 路线统一改造。
|
||||
- [x] TD-260621-02 [P1·归 F-09 B 路线 per-conv state] ✅(批2-A 落地·pendingMaxRounds ref<string|null> 挂起 convId + 守卫比对 activeConversationId·DirAuthDialog visibleDirAuths filter conversationId·2026-06-21;🟡D pendingDirAuths 全清→per-conv filter 由 wqnd4axf8 补) — **pendingDirAuth/pendingMaxRounds 全局单 ref 非 per-conv**。`useAiEvents.ts:99/113` 模块级单实例;DirAuthDialog:60/MaxRoundsCard:54 守卫仅判 isViewingGenerating 未比对 conversationId(pendingMaxRounds 纯 boolean 无 convId 字段)。真并发下挂起弹窗错显于非挂起会话。修:pendingMaxRounds 改 ref<string|null>(convId)+ DirAuth 守卫加 convId 比对。归 F-09 B 路线。
|
||||
- [x] TD-260621-03 [P1·patch_file 落地债 F-260615] ✅(读改写整体移入 _patch_guard 锁内串行化防 lost update + 删 entry().or_insert() 内存泄漏 + drop 写序列后释放·2026-06-21) — **patch_file 读改写在 FILE_LOCKS 外,lost update**。`tool_registry.rs:1247` 指纹校验(1176)+old_text CAS(1209)+new_content 计算(1197-1240)全在无锁纯读段;FILE_LOCKS(1242-1269)仅序列化 backup→tmp→rename 不防读写交错。两并发 patch 同文件:B 用基于旧内容算的 new_content 覆盖 A。write_file 不取 FILE_LOCKS 同根因。修:读+校验+new_content 整体移入锁内,或 per-path Mutex。
|
||||
- [x] TD-260621-04 [P1·patch_file 落地债 F-260615] ✅(read_file 三返回点返 file_hash 闭环 + 抽 compute_file_hash helper read_file/patch_file 共用消格式漂移·2026-06-21) — **expected_hash 契约破裂,指纹防并发形同虚设**。`tool_registry.rs:1112` schema 描述"read_file 返回的 file_hash"但 read_file handler(892-976)从不返回该字段。expected_hash 实由 patch_file 内部 format!("{}_{}",modified_secs,len)(1180)即造即比,LLM 拿不到前置值只能瞎传/省略→常 mismatch bail 或被跳过。L3 防行号漂移设计在实际 LLM 调用链落空。修:read_file 返回 file_hash 同格式闭环回传。
|
||||
- [ ] TD-260621-05 [P1·归 SMELL-P1-6] — **审批状态字符串双轨**。chat.rs:466/668 写 "executed" vs audit/mod.rs:490/545 + cache.rs:113 写 "completed",DTO 文档(models.rs:221)只列 completed。审计表混存两种"成功"态。归 SMELL-P1-6(String→enum)统一,行为变更需评估 AuditLog 显示 + find_cached 透传。
|
||||
|
||||
**P2 精选(25 项·详单见报告)**:try_continue pending_conv_id 跨会话泄漏(agentic:1314)/ patch_file FILE_LOCKS 永不清理内存泄漏(tool_registry:1248)/ auto-compress 成功未即时 save(agentic:778)/ MidStream 保文 chunk.error 未回填 err(stream_recv:275)/ audit_tool_call `let _ =` 吞 insert 错(finalize:35)/ switch+delete retain 未配套 finalize 占位终态化(conversation:317)/ PerConvState.created_at 全死字段(mod:520)/ PendingApproval.diff 写而不读(mod:570)。
|
||||
|
||||
**销账核对(已登记 8 复核)**:SMELL-P1-6(line221·#5 覆盖 executed/completed 双轨)/ MED-1(tool_registry:546 bind_directory)/ REFACTOR-260619-08(tool_registry:881 拆)/ REFACTOR-260619-09(context.rs:1 God 1552行)/ ARC-260619-05(agentic:318 超长)/ UX-260617-28(双监听器 INFO)。**均仍在未完成,审查复核确认,保留**。
|
||||
|
||||
**残留诊断代码(待清)**:chat.rs `authz_debug`/`authz-debug.log`(L2 调试遗留,P2 报 chat.rs:548 硬编码绝对路径),L2 完成 + 用户实测后可清。
|
||||
|
||||
---
|
||||
|
||||
### 📋 编排推进总览(2026-06-18 更新)
|
||||
|
||||
未完成待办按可执行性分 8 组(详细条目见下方各分类,勿重复记录):
|
||||
@@ -48,6 +72,25 @@
|
||||
|
||||
> **📦 已完成项归档**: [07-项目管理/todo归档/2026-06.md](./07-项目管理/todo归档/2026-06.md) — 2026-06-18 拆分, 已完成 `[x]` 与历史分析段迁此。另有 [2026-06-18.md](./07-项目管理/todo归档/2026-06-18.md) — 本次归档。
|
||||
|
||||
### 🔴 2026-06-20 BUG-260620-05 F-260619-03 工程内路径误弹窗(reload_allowed_dirs 丢 workspace_root·b22e9ae 回归)
|
||||
|
||||
**现象**:AI Chat file_info 等文件工具访问工程内路径(如 docs/02-架构设计/单对话并行多轮-设计-2026-06-20.md)触发授权弹窗,用户称"本来就有访问权限,为什么还来申请"。
|
||||
|
||||
**根因**:`state.rs:586-619 reload_allowed_dirs` 合并 `kv_dirs(KV allowed_dirs)+ project_dirs(projects.bind_directory)` 为 `all_dirs`,**仅当 `all_dirs.is_empty()` 时(:618)才插入 workspace_root**。KV 配过 allowed_dirs 或有绑定项目 → all_dirs 非空 → **workspace_root 不入 persistent** → 工程内路径(在 workspace_root 下)`is_authorized`(:356 starts_with)失败 → `check_path_authorization` 返 `NeedsAuthorization` → 弹窗。
|
||||
|
||||
**注释承诺失配**::346-348 / :583 / :617 注释均称"KV 未配时保持 workspace_root 免授权(向后兼容)",但 :618 条件用 `all_dirs`(含 project_dirs),project_dirs 非空也丢 root。b22e9ae "reload 尊重用户 persistent" 引入的回归。
|
||||
|
||||
**用户政策指令**(2026-06-20):"默认直接访问,明确知道无权限才申请"。指向工程内(workspace_root 下)默认免授权,仅 workspace_root 外非白名单路径才弹窗。
|
||||
|
||||
**修法(层1·1 行确定性 bug)**:`state.rs:618` 去掉 `if all_dirs.is_empty()` 条件包裹,**无条件** `set.insert(workspace_root_path())`(工程内始终免授权,对齐用户政策 + 注释承诺)。不破坏现有能力:workspace_root 经 `set/get_allowed_dirs`(:664/680 filter root)本就不可见不可删,"用户删 root"语义未落地,保 root 无冲突。
|
||||
|
||||
**层2 待决策(政策范围)**:用户"默认直接访问"是否要求更宽语义——workspace_root 外的非黑名单路径也默认放行(翻转 `is_authorized` 为黑名单制,实质去白名单)?涉 P0 去固定根决策方向,见待决策.md。dev 自用场景层1 即满足。
|
||||
|
||||
**关联**:F-260619-03(todo :528 b22e9ae 回归源)/ 待决策 workspace_root 分发适配(方案b 用户项目绑定)/ memory [[devflow-project-path-binding]]
|
||||
**状态**:🔴 待用户授权(层1 1 行修法可直改;层2 待政策澄清)
|
||||
|
||||
---
|
||||
|
||||
### ✅ 2026-06-19 BUG-260619-06 L0 clear 致冷启动审批丢失(批2+跨批遗留·非batch8回归·方案A已修复)
|
||||
|
||||
> **来源**:CR-260619-06 巡检独立核验(不信主代自审 PASS)。F-09 batch8(commit 6ad4ec2)在 L0 握手新增 `session.pending_approvals.clear()`,与冷启动 restore 重建链路时序冲突,致重启后待审批工具**完全丢失**。
|
||||
@@ -112,7 +155,7 @@
|
||||
|
||||
> 核验 HEAD 74003bc(BUG-260617-12 修复提交)+ 工作区残留。session-role-diagnose-only,逐行核验非信声明。**闭环逻辑正确(见 :49 注记),但发现提交不完整 P1**。
|
||||
|
||||
- [ ] B-260617-16 [P3·可选] — **Partial(MidStream 保文)回填半截 reasoning_content 语义待评**。agentic.rs MidStream 保文路径用 `round_reasoning_content`(本轮部分累积)写回 ChatMessage,回传下一轮 DeepSeek 会看到不完整推理。属异常路径(网络中断保文),保文后已加"响应不完整"系统提示,用户大概率重发。影响小,登记观察。— src-tauri/src/commands/ai/agentic.rs(:765)
|
||||
- [x] ✅(2026-06-20 核验·MidStream 保文已回填 reasoning_content 到 ChatMessage,mod.rs:1058/1116/1121·P3 观察项已处理) B-260617-16 [P3·可选] — **Partial(MidStream 保文)回填半截 reasoning_content 语义待评**。agentic.rs MidStream 保文路径用 `round_reasoning_content`(本轮部分累积)写回 ChatMessage,回传下一轮 DeepSeek 会看到不完整推理。属异常路径(网络中断保文),保文后已加"响应不完整"系统提示,用户大概率重发。影响小,登记观察。— src-tauri/src/commands/ai/agentic.rs(:765)
|
||||
|
||||
### 🔧 2026-06-17 对话标题不更新(还叫"新对话"·待运行时验证)
|
||||
|
||||
@@ -189,7 +232,7 @@
|
||||
|
||||
- [x] ✅(workflow w5siwnipj 核验+主代实施·agentic:348/knowledge_inject:130,139,312/idea:292-296 加 tracing::warn 降级不改返回值·audit:344 误报(CR-11 已修三路 match)·commands:290 BUG-11 已修·cargo check EXIT 0 + vue-tsc EXIT 0) **SMELL-P0-1** — **[P0🔴]** `unwrap_or_default` 吞错 **5 高危闭环**。agentic.rs:348(provider 池空走原空池兜底)/knowledge_inject.rs:130,139,312(检索/解析失败走原降级不注入/提炼跳过)/idea.rs:296(tags 坏降级空继续评估)本批 match+warn(空 Vec 零行为变更);audit.rs:344 误报(audit_finalize CR-11 已修三路 match,line 漂移+旧版本);commands.rs:290 BUG-11 已修。**58 处全量排查降 P2 非紧急**(高危已清,余为 Option 取默认/JSON 序列化等低危)。
|
||||
- [x] ✅(workflow w2xkw4ybh 抽 register_data_tools(18 持 db 工具:list_projects/list_tasks/list_ideas/update_project/create_project/bind_directory/create_task/update_task/advance_task/run_workflow/delete_task/create_idea/delete_project/restore_project/purge_project/list_trash/get_project_count/get_task_count)+ 主代抽 register_file_tools(10 文件工具:run_command/read_file/list_directory/write_file/patch_file/file_info/append_file/delete_file/rename_file/search_files)·build_ai_tool_registry 1091→7 行·加基线测试 test_build_ai_tool_registry_baseline_tool_count(Database::open_in_memory 断言 len()==28 + tool_names() 集合锁定)·主代核查 cargo check --workspace EXIT 0 + cargo test df-ai 119 passed + devflow 基线 1 passed + vue-tsc EXIT 0) **SMELL-P0-2** — **[P0🔴]** `tool_registry.rs:363 build_ai_tool_registry` **1091 行单函数** — 拆分为按功能分组注册函数(register_data_tools 18 db 工具 / register_file_tools 10 文件工具),build_ai_tool_registry 收敛到 7 行编排 + 基线测试锁定工具数防回归。**注(2026-06-20 核验):基线已升至 29(tool_registry.rs:1841 `test_build_ai_tool_registry_baseline_tool_count` 断言 `len()==29`,18 data + 10 file + 1 http;F-260619-03 Phase A 后 http_request 注册致 +1)。**
|
||||
- [ ] **SMELL-P0-3** — **[P0🔴]** `AiChat.vue` **4026 行 God 组件** — 拆分: ConversationSidebar(侧栏+搜索) / MessageList(消息列表+流式) / ChatInput(输入框+附件) / ApprovalPanel(审批卡片)。目标单组件 <500 行。
|
||||
- [x] ✅(2026-06-20 核验·AiChat.vue 4075→750 行·`src/components/ai/` 子组件已拆 ConversationSidebar/MessageList/ChatInput/TopBar/DirAuthDialog/MaxRoundsCard·状态/composable 已外移·与 REFACTOR-260619-07 同件) **SMELL-P0-3** — **[P0🔴]** `AiChat.vue` **4026 行 God 组件** — 拆分: ConversationSidebar(侧栏+搜索) / MessageList(消息列表+流式) / ChatInput(输入框+附件) / ApprovalPanel(审批卡片)。目标单组件 <500 行。
|
||||
- [x] ✅(workflow wowdnw4ba·tests/shell.rs 5 #[tokio::test] 覆盖成功/非零/超时/env/working_dir + shell.rs:55 过时TODO注释清理·execute逻辑零改动·主代 cargo test -p df-execute 5 passed + cargo check --workspace EXIT 0) **SMELL-P0-4** — **[P0🔴]** `df-execute` crate **零测试**(shell.rs 120行已集成工作流) — 补 shell 命令执行+超时+输出截断基础测试。
|
||||
|
||||
**🟡 P1 应该改进 (9项)**:
|
||||
@@ -216,8 +259,8 @@
|
||||
|
||||
- [ ] ARC-260618-01-d [P1/行为变更·待确认产品意图] — **conditions 条件引擎未求值**。`df-workflow/conditions.rs` `ConditionEngine` 零消费(无调用方),`Edge.condition` 字段存储但工作流执行器未求值分支——边条件当前形同虚设。**产品决策点**:① 实现条件求值(executor 在节点完成后求值出边 condition 决定路由)还是 ② 标 `#[allow(dead_code)]` + 文档明示"条件为预留未启用"。涉及行为变更(executor 控制流改变),确认产品意图后再实施。对齐 H 组长期池 T-14-11 条件引擎(line 42 仅指针无详情)。— `crates/df-workflow/src/conditions.rs`(ConditionEngine) + `crates/df-workflow/src/executor.rs`(边求值点)
|
||||
- [ ] ARC-260618-01-e [P1/行为变更·待确认产品意图] — **adversarial `evaluate_with_llm` 一致性未校验**。`df-ideas/adversarial.rs` `evaluate_with_llm` 返回的 `final_assessment`/`recommendation` 两个字段语义一致性未校验(如 final_assessment=强烈反对 但 recommendation=promote 的矛盾组合无守卫)。LLM 输出存在字段间语义漂移风险。**产品决策点**:① 加一致性校验(矛盾时降级或重评)还是 ② 视为 LLM 自由表达不加约束。涉及行为变更(评估结果可能被改写),确认产品意图后再实施。— `crates/df-ideas/src/adversarial.rs`(evaluate_with_llm 返回结构)
|
||||
- [ ] SW-260618-21 [P2/待 AiChat 沉淀] — **formatRelativeZh 重命名 formatRelative**。`src/utils/time.ts` `formatRelativeZh` 名带 Zh 后缀但实际非中文硬编码(已读 i18n.global.locale, sweep 批2 time.ts formatDate 已修 CR-260615-08 漏修),函数名误导。4 调用点含 `AiChat.vue`(并发改动中),为避免并发冲突缓做,待 AiChat God 组件拆分(SMELL-P0-3)沉淀后统一重命名。— `src/utils/time.ts`(formatRelativeZh) + 4 调用点(含 `src/components/AiChat.vue`)
|
||||
- [ ] SW-260618-22 [P2/已知 B-260617-03] — **useAiSend resolveLang DRY**。`useAiSend` 的 `resolveLang` 与其他 composable 语言解析逻辑重复,应抽 `aiShared` 共享。已知登记为 B-260617-03,此处补 sweep 维度记录便于检索。— `src/composables/ai/useAiSend.ts`(resolveLang) → 抽 `src/composables/ai/aiShared.ts`
|
||||
- [x] ✅(2026-06-20·formatRelativeZh→formatRelative 重命名 6 文件:time.ts 定义+注释去「中文」限定 + ConversationSidebar/MessageList/ActiveProjectsPanel/AuditLog/Tasks 5 调用点·AiChat 已拆 750 行无引用·grep 全 src 无残留 + vue-tsc --noEmit EXIT 0) SW-260618-21 [P2] — **formatRelativeZh 重命名 formatRelative**。`src/utils/time.ts` `formatRelativeZh` 名带 Zh 后缀但实际非中文硬编码(已读 i18n.global.locale, sweep 批2 time.ts formatDate 已修 CR-260615-08 漏修),函数名误导。原缓做前提(AiChat 沉淀)已满足,本次落地。— `src/utils/time.ts`(formatRelative) + 5 调用点(ConversationSidebar/MessageList/ActiveProjectsPanel/AuditLog/Tasks)
|
||||
- [x] ✅(2026-06-20 核验·resolveLang 已抽 `src/composables/ai/aiShared.ts:30-35 resolveAiLang` 共享,消除重复) SW-260618-22 [P2/已知 B-260617-03] — **useAiSend resolveLang DRY**。`useAiSend` 的 `resolveLang` 与其他 composable 语言解析逻辑重复,应抽 `aiShared` 共享。已知登记为 B-260617-03,此处补 sweep 维度记录便于检索。— `src/composables/ai/useAiSend.ts`(resolveLang) → 抽 `src/composables/ai/aiShared.ts`
|
||||
|
||||
### 🟡 AI Chat 交互体验改进(2026-06-14 方案 → 待办化)
|
||||
|
||||
@@ -234,7 +277,7 @@
|
||||
|
||||
### P2 — 不阻断缺陷 / 增强
|
||||
|
||||
- [ ] B-260614-05 — **[P2→降级]** 分离窗口(detached)跨窗口状态 — **核对修正**:reattachPanel 已接线(不再死代码)+ `tauri://destroyed` 监听已复位状态,"detached 永真卡死"已修复;剩余 localStorage `df-ai-gen`/`df-ai-text` 是 Sprint 19 **有意保留**(流式临时快照高频写),非 bug。仅在出现新场景失效时再评估改全局 emit — **去重**:与 AR-M5(跨窗口 state 完全隔离)同类,aichat 审查描述更深 — source:代码审查 + Sprint 19 (06-14)
|
||||
- [x] ✅(2026-06-20 核验·reattachPanel 接线 useAiWindow.ts:98 + tauri://destroyed 监听:86 已修·localStorage df-ai-gen/df-ai-text Sprint19 有意保留) B-260614-05 — **[P2→降级]** 分离窗口(detached)跨窗口状态 — **核对修正**:reattachPanel 已接线(不再死代码)+ `tauri://destroyed` 监听已复位状态,"detached 永真卡死"已修复;剩余 localStorage `df-ai-gen`/`df-ai-text` 是 Sprint 19 **有意保留**(流式临时快照高频写),非 bug。仅在出现新场景失效时再评估改全局 emit — **去重**:与 AR-M5(跨窗口 state 完全隔离)同类,aichat 审查描述更深 — source:代码审查 + Sprint 19 (06-14)
|
||||
|
||||
### 🔧 2026-06-18 6 域并行走查 sweep-fix(workflow wd2fnjh3s·6 agent·主代核查 cargo check --workspace EXIT 0 + vue-tsc EXIT 0)
|
||||
|
||||
@@ -373,7 +416,7 @@
|
||||
|
||||
- [x] ✅(主代串行·doc-discovery 源码核验 `time.ts:44` 已 `(i18n as any).global.locale.value === 'en'`·CR-260618-24 修复闭环 + CR-260618-25 A-time 复审 ✅ 双印证·漏销账补登) **UX-260618-16 [P1🔴]** — **`time.ts:44` formatDate 漏 `.value` 致 en locale i18n 失效**(CR-260618-24 批2 审查发现·Agent C 铁证)。`legacy:false` composition 模式 `i18n.global.locale` 是 ref,`(i18n as any).global.locale === 'en'` 比对象===字符串**恒 false** → en locale 下 formatDate 永走 zh-CN 分支,i18n 化失效(对齐 CR-260615-08 未完成回归)。铁证:`App.vue:227`/`GeneralPanel.vue:259` 均用 `i18n.global.locale.value`,唯 time.ts:44 漏。**修法**:补 `.value` → `(i18n as any).global.locale.value === 'en'`(locale 取值 'en' 非 'en-US',App.vue:227 印证)。— `src/utils/time.ts:44`
|
||||
|
||||
- [ ] **UX-260618-17 [P1🟡]** — **ProjectDetail.handleApprovalMulti 漏 submitting 复位(防双击破口)**(CR-260618-25 前端审查发现·Agent B 对抗核验)。`ProjectDetail.vue:431-437` handleApprovalMulti 无 submitting set true/finally,模板 :196-202 只绑 `:disabled="multiDecisions.length === 0"` → 多选审批 approveHumanApproval IPC 进行中按钮不禁用,用户可重复点确认重复触发 IPC。同模板 handleApproval(:417-425)正确 try/finally,handleApprovalMulti 漏对齐。**修法**:顶 `submitting.value = true` + `try { ... } finally { submitting.value = false }`,对齐 handleApproval。— `src/views/ProjectDetail.vue`(:431-437 + :196-202)
|
||||
- [x] ✅(2026-06-20 核验·handleApprovalMulti 已随审批逻辑迁移到 `src/components/project/ApprovalDialog.vue:91-102`·submitting try/finally 已正确对齐 handleApproval + 模板 `:25 :disabled="submitting || multiDecisions.length === 0"` 已绑·todo 行号 431-437/196-202 过时为迁移前 ProjectDetail.vue 坐标) **UX-260618-17 [P1🟡]** — **ProjectDetail.handleApprovalMulti 漏 submitting 复位(防双击破口)**(CR-260618-25 前端审查发现·Agent B 对抗核验)。原状:`ProjectDetail.vue` handleApprovalMulti 无 submitting set true/finally,模板只绑 `:disabled="multiDecisions.length === 0"` → 多选审批 IPC 进行中按钮不禁用,可重复触发。**已修**:迁移 ApprovalDialog.vue 时补 submitting 自治 + try/finally + 模板绑 submitting。— `src/components/project/ApprovalDialog.vue`(:91-102 + :25)
|
||||
|
||||
### 🔧 2026-06-19 文件拆分升级(3 代理并行分析·建任务·未实施)
|
||||
|
||||
@@ -389,7 +432,7 @@
|
||||
|
||||
- [ ] **REFACTOR-260619-04 [P1]** — **ToolCard.vue(1527 行)拆 5 子组件+composable+util**。先 `useToolFormat.ts`(纯函数 ~280,零风险)+ `ToolResultBody.vue`(420 最大块);再 ToolCardHeader/ToolApproval/useToolApproval。风险:折叠态 shouldKeepOpen 三处共享 / 审批状态机断链(B-260616-08 回归)/ ToolCardList 批量审批联动。— `src/components/ToolCard.vue` + `ToolCardList.vue`
|
||||
- [x] ✅(2026-06-20 核验已拆分·`agentic.rs` 已删除→`src-tauri/src/commands/ai/agentic/` 目录:mod.rs(83390 字节·run_agentic_loop 主 loop)/guard.rs(2894·GeneratingGuard+ContinueSnapshot 抽出对齐 A+E+F `agentic_runtime`)·stream_recv.rs(独立文件,StreamOutcome+stream_one_provider 对齐 B+C `agentic_stream`)) **REFACTOR-260619-05 [P1]** — **agentic.rs(1231 行)抽 agentic_runtime.rs + agentic_stream.rs**。主 loop 是**单函数 720 行不可按函数拆**(工具执行/审批是 loop 内 if 分支;process_tool_calls 在 audit.rs)。先抽 A+E+F `agentic_runtime.rs`(GeneratingGuard+try_continue_agent_loop+ContinueSnapshot,~195,最高收益最低风险)+ B+C `agentic_stream.rs`(StreamOutcome+stream_one_provider,~165)。**D run_agentic_loop 等 F-09 B 批4 落地再评估**(避免 per_conv 双线作战)。— `src-tauri/src/commands/ai/agentic.rs`
|
||||
- [ ] **REFACTOR-260619-06 [P1]** — **ai_node.rs(1107 行)拆 3 模块**。`ai/{mod,params,ai_node,self_review}.rs`。params.rs(provider 解析 helper ~270)+ ai_node.rs(~130)+ self_review.rs(~300)。测试分块清晰(752/939/1069 三段),低风险。fixture provider_stub/config_with 留 params.rs `pub(super)`。— `crates/df-nodes/src/ai_node.rs`
|
||||
- [x] ✅(2026-06-20 核验·已拆:ai_node.rs 423 + ai_self_review_node.rs 447 + ai_helpers.rs 298,测试分块清晰) **REFACTOR-260619-06 [P1]** — **ai_node.rs(1107 行)拆 3 模块**。`ai/{mod,params,ai_node,self_review}.rs`。params.rs(provider 解析 helper ~270)+ ai_node.rs(~130)+ self_review.rs(~300)。测试分块清晰(752/939/1069 三段),低风险。fixture provider_stub/config_with 留 params.rs `pub(super)`。— `crates/df-nodes/src/ai_node.rs`
|
||||
- [x] ✅(2026-06-20 核验已拆分·`src/components/ai/` 子目录已存在子组件:ConversationSidebar.vue + ChatInput.vue + MessageList.vue + TopBar.vue + DirAuthDialog.vue + MaxRoundsCard.vue·AiChat.vue 从 4075 行缩到 750 行·状态/composable 已外移,模板拆分完成) **REFACTOR-260619-07 [P1]** — **(已有 SMELL-P0-3)AiChat.vue(4075)拆 ConversationSidebar/ChatHeader/MessageList/ChatInput**。方案已定,状态/composable 已外移,拆 template+局部 script。前置:先提交工作区未提交改动(本会话 shouldRenderMsg/scroll/1214 预检等)。— `src/components/AiChat.vue`
|
||||
- [ ] **REFACTOR-260619-08 [P1]** — **(已有 SMELL-P0-2)tool_registry.rs(2023)按功能分组注册函数拆**。register_crud_tools/register_file_tools(已抽)/register_workflow_tools 等,每个 <200 行。— `src-tauri/src/commands/ai/tool_registry.rs`
|
||||
|
||||
@@ -397,7 +440,7 @@
|
||||
|
||||
- [ ] **REFACTOR-260619-09 [P2 暂缓]** — **context.rs(1332)等 F-15 压缩链路稳定再拆**。生产 745+测试 587。impl 跨文件方案(同 crate 多 impl 块,零字段可见性改动)。先 sanitize.rs(最大连续块 ~184)。当前 context.rs 被 F-15/压缩频繁改动,拆分窗口未到。— `crates/df-ai/src/context.rs`
|
||||
- [ ] **REFACTOR-260619-10 [P2]** — **scan.rs(1015)拆 4 模块**(stack/discover/readme/sample)。纯函数低风险收益低。共享 SAMPLE_IGNORED_DIRS/truncate_chars/read_readme_raw 提 mod.rs。— `crates/df-project/src/scan.rs`
|
||||
- [ ] **(已有 SMELL-P1-9)crud.rs(2212)按表拆** project_repo/task_repo/conversation_repo/idea_repo。— `crates/df-storage/src/crud.rs`
|
||||
- [x] ✅(2026-06-20 核验·`crud.rs` 已删→`crates/df-storage/src/crud/` 5 文件 mod/conversation_repo/idea_repo/message_repo/project_repo·与 SMELL-P1-9 同件) **(已有 SMELL-P1-9)crud.rs(2212)按表拆** project_repo/task_repo/conversation_repo/idea_repo。— `crates/df-storage/src/crud.rs`
|
||||
|
||||
**执行顺序建议**:01-C provider_cfg(零风险试水) → 02 anthropropic → 03 audit → 04 ToolCard useToolFormat → 05 agentic_runtime → 06 ai_node → 07 AiChat.vue → 08 tool_registry。每步 cargo check+test+clippy 分 crate + 手测。
|
||||
|
||||
@@ -625,6 +668,69 @@
|
||||
|
||||
---
|
||||
|
||||
### 💡 灵感模块升级(2026-06-21,批1-3c·已实施)
|
||||
|
||||
> 灵感模块可信度+体验+评估历史+统计+晋升携带+关联关系全面升级。对齐 memory [[devflow-idea-module]](df-ideas 评估闭环已通,对抗未接 LLM/晋升走前端)后续延伸。
|
||||
|
||||
**✅ 批1 可信度+体验**:
|
||||
- P0 `evaluated_by` 落库(ai_analysis 关联谁评的)+评估深度标签动态(LLM/启发式/降级,直显评估来源可信度)+评估失败重试(瞬时失败可恢复)+列表排序(评分/时间,支持按评分挑高价值)+详情描述可编辑(描述可订正)+已晋升项目跳转(promoted_to → 项目直跳)
|
||||
|
||||
**✅ 批2 评估历史全栈**:
|
||||
- `idea_evaluations` 版本快照表(每次评估留版本可追溯)+`IdeaEvalRepo`(版本数据访问层)+`evaluate_idea` 每次 insert 历史(评估产生历史记录)+`list_idea_evaluations` IPC(暴露历史查询)+`IdeaDetail` 版本时间线(前端时间线组件渲染历次评估)
|
||||
|
||||
**✅ 批3 IdeasPanel 统计看板**:总数/待评估/已立项/平均分(模块概览一眼可见)
|
||||
|
||||
**✅ 批3a 晋升携带**:`ProjectDetail` 来源灵感卡片(反查 `idea_id` 显示评估结论,项目可追溯灵感来源)
|
||||
|
||||
**✅ 批3b 关联关系 schema**:`ideas.related_ids` JSON 列 + `IdeaRecord` 字段(为关联 UI 打底)
|
||||
|
||||
**✅ 批3c 关联关系前端**:`IdeaDetail` 关联灵感展示区 + 多选管理选择器 + `Ideas.vue update-related` handler
|
||||
|
||||
**✅ P1 信号词否定前缀**:`scoring.rs count_any` 不复用/无增长等否定不计正分(修评分虚高,如"无增长"误算"增长"正分)
|
||||
|
||||
**验证**:cargo check src-tauri ✅ + df-storage 62/11 tests ✅ + df-ideas 22 tests ✅ + vue-tsc 灵感模块零新错 ✅
|
||||
|
||||
**连带修**:`augmentation/resolvers.rs` 缺 `ResolverRegistry` import(预存编译阻断,顺手补)
|
||||
|
||||
**状态**:✅ 批1-3c 全完成(主代独立验证 cargo + tests + vue-tsc 通过)
|
||||
|
||||
### ⚠️ 灵感升级-遗留缺陷(2026-06-21·待另一会话修)
|
||||
|
||||
- [P0 功能] related_ids 白名单缺失:crates/df-storage/src/crud/settings.rs:121-124 的 ideas 白名单(13 列)无 related_ids → 批3c 关联管理 Ideas.vue onUpdateRelated 调 update_field('related_ids') 被 validate_column_name 拒,关联存不进 DB(表面能选确定后静默失败)。修复:settings.rs:123 ideas 白名单加 "related_ids"(一行)。已核验确认。
|
||||
- [P2 并发·待核] 评估历史 version 并发重复:src-tauri/src/commands/idea.rs evaluate_idea 用 list_by_idea().first().version+1 算 version,无事务/(idea_id,version) 唯一约束,并发评估同一 idea 两次可能 version 重复。单用户桌面低概率,严格讲需加唯一约束或事务。批2 引入。
|
||||
- [P2 运行·未验] 批1-3c 前端运行时未实测:编译/vue-tsc 过,但运行(评估历史加载/关联管理交互/统计 computed/ProjectDetail 来源卡片反查 store.ideas)未实际跑过。需 npm run tauri dev 实测。
|
||||
|
||||
---
|
||||
|
||||
### 💡 灵感模块-来源采集/追溯(2026-06-21·待办·新功能范畴)
|
||||
|
||||
> 继批1-3c 升级后的下一步:灵感来源录入增强 + 自动采集机制。**属新功能范畴**,需独立设计采集规则/触发/去重,下一阶段推进。
|
||||
|
||||
- [ ] **F-260621-01 [P2/新功能]** — **source 字段录入增强 + 自动采集机制**。
|
||||
1. **source 录入增强**:灵感捕捉模态框加 `source` / `tags` / `priority` 输入(现仅描述),让录入即结构化(来源/标签/优先级)
|
||||
2. **自动采集机制**:从对话 / 任务 / 代码中自动提炼灵感(降低录入门槛,捕捉随手遗失的灵感)
|
||||
- **需设计**:采集规则(什么信号算灵感 — 关键词/意图/重复模式)/ 触发时机(对话结束/任务完成/手动触发)/ 去重(相似灵感合并避免洪水)/ 用户确认流(自动采集不污染灵感池,需用户确认入库)
|
||||
- **依赖**:批1-3c 升级完成(已有 source/tags 字段基础) + 对话历史可读(F-15 archived_segment 状态) + 任务可读
|
||||
- **关联**:memory [[devflow-idea-module]] / [[aichat-skill-slash-autocomplete]](联想需求,场景相关)
|
||||
|
||||
---
|
||||
|
||||
### ⚠️ 预存债-非灵感模块:ToolResult 类型缺字段(用户并行开发中间态·阻断 build)
|
||||
|
||||
> 用户并行开发工具结果展示时,`ToolResultBody.vue` + `composables/ai/useToolCard.ts` 引用了 `ToolResult` 类型不存在的字段(vue-tsc 7+ 错)。**非本次灵感模块改动引入**,是用户工作区并行开发中间态。不阻断 `vite dev`(dev 宽松),阻断 `npm run build`(prod 严格)。
|
||||
|
||||
- [ ] **TD-260621-07 [P2/预存债·待用户对齐]** — **ToolResult 类型缺 output_mode/files/counts/matches/total_files 字段**。
|
||||
- **现状**:`ToolResult`(types.ts)未定义 `output_mode` / `files` / `counts` / `matches` / `total_files` 五字段,但 `ToolResultBody.vue` + `useToolCard.ts` 模板/computed 已引用 → vue-tsc 报 7+ Property does not exist
|
||||
- **定位**:用户并行开发工具结果展示增强(可能是为了批1 评估深度标签/批2 历史时间线之外的工具结果多模式渲染 — output_mode 暗示 tab/列表/树等多种展示模式),属进行中的中间态
|
||||
- **修法**:① `ToolResult` 类型补齐 5 字段(对齐后端返回结构) + ② `ToolResultBody.vue` / `useToolCard.ts` 消费方与类型对齐(可能需后端 `tool_result_summary` IPC 返回结构同步扩字段)
|
||||
- **不阻断 dev**:vite dev 宽松回退,vue-tsc prod build 严格;用户 dev 调试不受影响,仅 build/release 受阻
|
||||
- **待用户**:确认字段语义(output_mode 取值集合 / files 结构 / counts 计什么)后对齐,非本会话灵感模块范畴
|
||||
|
||||
- [x] ✅(workflow wf 批1·streamingGuard.ts 新增 setStreaming/forceResetStreaming guard + 14 处 state.streaming 散布赋值收敛(useAiSend 7/useAiEvents 2/useAiStream 1/useAiConversations 2/useAiWindow 2/useAiPanel 1)+ feature flag df-ai-generating-statemachine(appSettings,关时回退散布语义)+ onStreamTimeout 改走 forceResetStreaming 兜底 + scripts/verify-streaming-guard.mjs 9 单测全过·vue-tsc EXIT 0·cargo check EXIT 0) **TD-260621-GUARD** — **[P0·用户实测卡死根因]** generating 状态机前端落地(对齐 memory [[devflow-generating-statemachine]] + 专项设计文档)。后端 RAII guard(B-09)+ session_state() enum 视图(B-12)+ per_conv 化(F-09 批4)历史批次已落地,本批补前端 streaming 写收敛:state.streaming 散布在 6 文件 14 处直接赋值,任一 return 漏写/前端 JS 异常跳过复位 → streaming 永久 true 卡死输入框。收敛到 setStreaming 单一写入口(合法性观测日志 + generatingConvs 联动 + flag 灰度),onStreamTimeout 走 forceResetStreaming 兜底复位 Idle+清 currentText/queue。注:TD-260621-06a(switchConversation 重算 streaming)本批已纳入 guard(convId=id 幂等联动)。
|
||||
- **关联**:批1-3c 灵感升级无涉(独立预存债)
|
||||
|
||||
---
|
||||
|
||||
### F-260620-01 跨端 AI Chat:微信小程序 ↔ Rust 云后端 ↔ DevFlow 桌面端
|
||||
|
||||
**背景**:微信小程序远程用 DevFlow AI Chat,桌面端与小程序双向实时同步(微信电脑+手机同时在线模式)。
|
||||
|
||||
19
docs/待决策.md
19
docs/待决策.md
@@ -202,6 +202,25 @@
|
||||
|
||||
---
|
||||
|
||||
## F-260619-03 路径授权政策:工程内默认免授权(用户 2026-06-20 指令)
|
||||
|
||||
**用户政策指令**:"默认直接访问,明确知道无权限才申请"。
|
||||
|
||||
**背景**:F-260619-03 Phase C 去固定根(b22e9ae)后,`reload_allowed_dirs` 在 KV allowed_dirs 或 projects.bind_directory 非空时**丢失 workspace_root**,致工程内路径(如 docs/)误弹窗。用户反馈"本来就有访问权限,为什么还来申请"。详见 todo BUG-260620-05。
|
||||
|
||||
**决策点(语义范围澄清)**:
|
||||
- **层1 工程内默认免授权**(dev 自用足够):`reload_allowed_dirs` 无条件保留 workspace_root(state.rs:618 去 `all_dirs.is_empty()` 包裹)。工程内路径默认放行,workspace_root 外非白名单仍弹窗。1 行修复,确定性 bug(注释承诺失配)。
|
||||
- **层2 全局默认放行**(语义翻转):`is_authorized` 改黑名单制——非黑名单路径默认放行,仅明确敏感(系统目录/凭据)拒。等于实质去掉白名单授权机制。安全风险大(任何非黑名单路径 AI 可访问),涉 P0 去固定根决策方向推翻。
|
||||
|
||||
**与 P0 去固定根冲突**:P0(CR-260620-01 ⑤)决策"去掉代码硬编码固定放行,用户从白名单删工程根后工程根也需授权"。层1 恢复工程内免授权=部分推翻 P0。但 P0 "用户删 root" 语义当前未落地(set/get filter root,root 不可删),层1 不破坏现有能力。
|
||||
|
||||
**与分发适配衔接**:层1 workspace_root = CARGO_MANIFEST_DIR(编译期写死),开发机自用有效;分发后失效则依赖 projects.path 自动授权(reload 已读 project_dirs)。分发适配见上节"workspace_root 分发适配"方案 b。
|
||||
|
||||
**倾向**:层1(满足 dev 自用 + 不破坏安全 + 修注释承诺失配的确定性 bug)。层2 若需全局放行另立专项。
|
||||
**状态**:🟡 待用户拍板(层1 确定性 bug 可直改 1 行;层2 语义翻转需明确安全边界)。
|
||||
|
||||
---
|
||||
|
||||
## 已决归档
|
||||
|
||||
按月归档(随时间增长追加月份文件,防主文件膨胀):
|
||||
|
||||
146
docs/待审查.md
146
docs/待审查.md
@@ -23,6 +23,52 @@
|
||||
|
||||
## 当前队列
|
||||
|
||||
### CR-260620-04 Agent 最大轮次支持「不限」(working tree 未提交:0=不限 约定·config.rs clamp 0-50 + mod.rs effective_max 0→usize::MAX + GeneralPanel checkbox + App.vue clamp + i18n labelUnlimited) — ✅ 已审(PASS·⚪2 WATCH·巡检 2026-06-20 独立 grep/read 核验控制流)
|
||||
|
||||
- **范围**:新功能。约定 0=不限:后端 config.rs:69 clamp(1,50)→(0,50);mod.rs:542 loop 上界算 effective_max(0→usize::MAX,for 到不了上界,靠 stop_flag/收敛/审批退出,达上限暂停 AiMaxRoundsReached 不触发);前端 GeneralPanel 加「不限」checkbox(绑 agentMaxIterations===0)+ input :disabled + clamp 支持 0 + computed unlimited;App.vue:107 clamp 跟进(iter===0?0:...);i18n zh/en desc 加 0=不限 + labelUnlimited。
|
||||
- **维度**:正确性(effective_max 语义/达上限暂停分支 1172 在 0 模式是否漏退出)/ 边界(0 全链路 config→store→load→loop 透传)/ 回归(设 50 仍触发暂停?默认 10 不变)/ UI(checkbox 切换+持久化+disabled)/ 一致性(前端 0=不限 vs 后端 0→usize::MAX 对齐)
|
||||
- **核验方式**:git diff working tree 5 文件逐行 + cargo check EXIT 0 + vue-tsc EXIT 0(主代已跑)
|
||||
- **待审点**:effective_max 计算位置(542 for 前)/ usize::MAX 作 for 上界无溢出/意外 / unlimited computed set 与 syncAgentMaxIterations 协同 / App.vue 三元与 GeneralPanel clamp 语义一致 / 0 模式 1172 暂停分支确实不触发 / 不限模式无限轮风险仅靠 stop_flag/收敛/审批兜底(无软上限)
|
||||
|
||||
---
|
||||
|
||||
**复审结论(2026-06-20·巡检独立 grep/read 核验控制流,不信"主代已跑"声明)**: ✅ **PASS** — 🔴0 🟡0 ⚪2 WATCH 1
|
||||
|
||||
**① 核心控制流 0 模式暂停分支不误触发 PASS(深度核验,主会话待审点)**:
|
||||
- `mod.rs:543 effective_max = if max_iterations==0 { usize::MAX } else { max_iterations }` ✅
|
||||
- `mod.rs:544 for iteration in start_iteration..effective_max` — 0 模式上界 MAX,for 仅能经 return/break 退出,不可能跑满上界
|
||||
- for 退出仅 3 路径:`stop_flag`→return(:559)/ 审批等待→return(:1162)/ **收敛→break**(`:1143 if !has_tool_calls { converged=true; break; }`)
|
||||
- `mod.rs:1175 if !converged`(for 结束后)— 0 模式 for 仅经 converged break 到达此处(return 路径直接返回不经此),故 `!converged=false`,**跳过 AiMaxRoundsReached 暂停分支** ✅
|
||||
- 注释 `:1174` "max_iterations=0 时 effective_max=usize::MAX,for 不会正常结束至此,故不触发暂停" 准确
|
||||
|
||||
**② usize::MAX for 上界无溢出 PASS**:
|
||||
- Rust Range `0..usize::MAX` iterator 到 MAX-1 后 next 返回 None(标准库保证不溢出)
|
||||
- for 体内 `iteration` 仅作计数,无 `iteration+1` 致 MAX 溢出运算;实际 loop 靠 break/return 退出,不会真迭代至 MAX-1
|
||||
|
||||
**③ 0 全链路透传一致性 PASS**:
|
||||
- 前端 GeneralPanel unlimited→`agentMaxIterations=0` → IPC → `config.rs:69 clamp(0,50)` store 0 → `chat.rs:167 load` → `run_agentic_loop max_iterations=0` → `effective_max=MAX` ✅
|
||||
- `App.vue:107 iter===0 ? 0 : Math.min(50,Math.max(1, iter??10))` 与 GeneralPanel `syncAgentMaxIterations`(`!==0` 透传)语义一致 ✅
|
||||
- GeneralPanel `unlimited` computed(get `===0` / set 0↔10)+ input `:disabled="unlimited"` + 持久化(appSettings SQLite,App.vue onMounted 读 0 透传后端) ✅
|
||||
|
||||
**④ 回归 PASS**:
|
||||
- 默认 `DEFAULT_MAX_AGENT_ITERATIONS=10`(state.rs:556)不变,clamp 0-50 不影响默认 ✅
|
||||
- 设 50:effective_max=50,跑满未 break → `:1175 !converged` → 暂停触发(原行为保留)✅
|
||||
- 设 0:如 ① 不触发暂停 ✅
|
||||
|
||||
**⑤ i18n 中英对称 PASS**:en/zh `descAgentMaxIterations` 加 "0=不限" + `labelUnlimited` 新 key 中英对称 ✅
|
||||
|
||||
---
|
||||
|
||||
**⚪ LOW-1**:`config.rs:60-61` docstring stale — 仍写"范围双 clamp(command 端 1-50 + 前端 input min/max)",实际 `:67-69` 已改 clamp 0-50。文档与代码不符,建议同步。
|
||||
|
||||
**⚪ LOW-2**:`GeneralPanel` input `min="1" max="50"` 未改。unlimited 勾选时 `:disabled` 不冲突;用户不勾选直接手输 0 → v-model 允许 + sync `!==0` 透传(=不限语义自洽)。min=1 与"可手输 0"轻微矛盾,有 unlimited 复选框为主路径,影响低。
|
||||
|
||||
**WATCH-1**:0 模式(不限)**无软上限** — LLM 持续调工具不收敛时理论无限轮烧 token,仅靠 stop_flag/收敛/审批退出。用户主动选"不限"即接受此风险(设计取舍,非 bug)。建议 UI desc 补风险提示(当前 descAgentMaxIterations 仅"0=不限",未提烧 token 风险)。
|
||||
|
||||
- **待修项回流 todo**:**无** 🔴/🟡 项
|
||||
|
||||
---
|
||||
|
||||
### CR-260620-01 AI 授权目录改进批(working tree 未提交:strip_verbatim比对侧收口+黑名单统一+search_files symlink+bind reload+toast接线+单测·state.rs/tool_registry.rs/mod.rs/project.rs/chat.rs/AllowedDirsPanel.vue/Settings.vue/i18n×2) — ✅ 已审(ISSUES·🟡1⚪2)
|
||||
|
||||
- **范围**:三方审查(安全/UX/跨端)交叉印证改进。误弹窗核心根因(strip_verbatim 仅写入侧,比对侧遗漏)收口到 `is_authorized` 单点(candidate strip)。黑名单两套不一致(validate_path contains vs is_in_system_blacklist 分段)统一单一来源 + 补 windows 根/programdata/.ssh。search_files symlink 逃逸(file_type 不跟随)。bind 路径 reload(project.rs create_with_binding/update_project/relocate 三处)。AllowedDirsPanel toast 接线 + pickDir 即加。P0 workspace_root 收紧已**回退**(编译期写死不适合分发,转待决策)。
|
||||
@@ -1499,9 +1545,9 @@
|
||||
|
||||
---
|
||||
|
||||
### CR-260619-27 AI Chat 跑题改进 P2(主题检测保守 + tokenize 2-gram·commit a2db5c7) — 🟢 通过
|
||||
### CR-260619-27 AI Chat 跑题改进 P2(主题检测保守 + tokenize 2-gram·commit a2db5c7) — ✅ 已审(PASS)
|
||||
|
||||
- **结论**🟢 通过:6 维度全部核验通过,topic 字段无破坏(TrackedMessage 无 Serialize derive,只 ChatMessage 落库)+ 双高置信保守(任一 topic None 不标)+ tokenize 2-gram 正确修复中文锚点。df-ai 189 passed 独立复跑确认。无 high/med 风险,2 条 low 提示。
|
||||
- **结论**: ✅ **PASS** — 🔴0 🟡0 ⚪2 — 6 维度全部核验通过,topic 字段无破坏(TrackedMessage 无 Serialize derive,只 ChatMessage 落库)+ 双高置信保守(任一 topic None 不标)+ tokenize 2-gram 正确修复中文锚点。df-ai 189 passed 独立复跑确认。
|
||||
- **范围**(3 文件):context.rs(TrackedMessage.topic 字段 + pending_topic_marker + push 推断 + last_user_topic/take_topic_marker + 5 主题测)+ context_helpers.rs(tokenize 2-gram 汉字滑窗 + 4 测)+ agentic/mod.rs(TOPIC_MARKER_ENABLED 常量开关 + loop 顶部消费 marker)
|
||||
- **根因**:多主题交织无检测/分段 + CR-26 🟡1 中文 tokenize 锚点弱
|
||||
- **维度核验**:
|
||||
@@ -1589,6 +1635,102 @@
|
||||
|
||||
- **待修项回流 todo**: **无** 🔴/🟡 项(🟡 WATCH-1 已修:reload_allowed_dirs 删无条件 insert workspace_root,KV 有配尊重用户 persistent,兑现"用户删白名单后工程根需授权"动态白名单完整语义)
|
||||
|
||||
### CR-260621-01 @ + / Input Augmentation 层(df-types Augmentation + Resolver 投影 + path 脱敏 + chip 元数据 + search_files 兜底 + / 修复) — ✅ 已审(PASS·🟡1⚪3)
|
||||
|
||||
- **范围**:新功能 + 重构 + 安全增强。df-types 新增 augmentation 领类型(SanitizedPath newtype + MentionRef/Augmentation/MentionSpanDto/ResolveError);后端 augmentation 模块(MentionResolver async trait + ResolverRegistry + 四 impl + ProviderLocality 脱敏 + inject 段构建);skills.rs strip_frontmatter + RwLock 双检锁 + ScanResult conflicts;chat.rs 4 调用方统一 resolve_and_inject;audit/mod.rs search_files 兜底改 Denied;前端 ChatInput chip span 记录 + MessageList 元数据驱动切段(弃正则)+ spans 全链路透传。
|
||||
- **维度**:协议(serde tag/rename/transparent/newtype 向后兼容)/ Resolver(async trait + 单条失败不阻断)/ 安全(path 脱敏 Local 全路径 Remote basename)/ spans 透传链路完整性(8 环节逐环节核验)/ chat.rs 注入收敛一致性 / search_files 兜底回归(其他文件工具不变)/ 前端 chip 元数据驱动(非正则)
|
||||
- **核验方式**:独立 grep/read 逐文件核验源码当前形态(11 新文件 + 8 改文件),不信任何声明。
|
||||
|
||||
---
|
||||
|
||||
**复审结论(2026-06-21·独立 grep/read 核验源码当前形态,不信声明/文档/会话描述)**: ✅ **PASS** — 🔴0 🟡1 ⚪3
|
||||
|
||||
**① Augmentation 协议 PASS(serde 严格对齐 + 向后兼容)**:
|
||||
- `MentionRef` `#[serde(tag="kind", rename_all="snake_case")]` + 字段 `#[serde(rename="id"/"name")]`,变体 `project`/`task`/`idea`/`skill`,round-trip 单测印证(augmentation.rs:350-401)✅
|
||||
- `Augmentation` 同构 `tag="kind"` + `path: Option<SanitizedPath>` 带 `#[serde(default, skip_serializing_if="Option::is_none")]`(:161),无 path 时 JSON 不含 path 字段(单测 :439-453 印证)✅
|
||||
- `SanitizedPath` `#[serde(transparent)]`(:30)— wire 上是裸字符串无 `{inner}` 包裹,前端 `type SanitizedPath = string`(types.ts:413)对齐 ✅
|
||||
- `MentionSpanDto.ref_id` `#[serde(rename="refId")]`(:246)— camelCase 对齐前端 TS `refId`(types.ts:402)✅
|
||||
- **向后兼容**:serde tag=kind + 枚举,老前端遇未知 kind 反序列化失败抛 Err(非"忽略"),但**仅在新 mention 类型新增时**才有此风险——当前 4 变体与前端 TS union 严格对齐(types.ts:400 `kind: 'project'|'task'|'idea'|'skill'`)无未知 kind 场景。Augmentation TS 类型宽松(types.ts:370 不强制校验变体),前端宽容。设计合理 ✅
|
||||
|
||||
**② Resolver trait + 四 impl + resolve_all 单条失败不阻断 PASS**:
|
||||
- `MentionResolver` async_trait(mod.rs:34-48)+ 四 impl(resolvers.rs:56/121/184/246)kind 分别返 "project"/"task"/"idea"/"skill" ✅
|
||||
- `resolve_all` for 循环逐条 resolve,Ok push augs / Err push errors(**不 break 不 return**,registry.rs:56-72);kind 无对应 resolver 收集 KindMismatch 不 panic(单测 :107-122 印证)✅
|
||||
- ProjectResolver 经 `ProjectRepo::get_by_id` + path 经 `sanitize_for` 包 SanitizedPath(resolvers.rs:76-104);TaskResolver join `ProjectRepo::get_by_id(task.project_id)` 取 project_name(:156-162);IdeaResolver 无 project_id 无 path;SkillResolver 调 `read_skill_content_stripped` 取剥 frontmatter 正文(:267)✅
|
||||
- 循环依赖规避:Resolver 持 `Arc<Database>` 非 `Arc<AppState>`(resolvers.rs:9-11 注释明确),AppState 持 `Arc<ResolverRegistry>`(state.rs:273),无环 ✅
|
||||
|
||||
**③ path 脱敏 PASS(locality 判定 + sanitize_for)**:
|
||||
- `locality_of(provider)` 读 `base_url.to_lowercase()`,LOCAL_MARKERS 含 `localhost`/`127.0.0.1`/`0.0.0.0`/`::1`/`ollama`,任意子串命中即 Local(sanitize.rs:30-44)✅
|
||||
- 单测覆盖 localhost/127/0.0.0.0/[::1]/ollama/LOCALHOST(大小写不敏感)/api.openai.com/空串(:98-144),边界完整 ✅
|
||||
- `sanitize_for(raw, Local)` 原样返 / `Remote` 取 `Path::file_name` basename,失败回退 `[remote-path-hidden]`(:54-64),防裸泄盘符/用户名/公司目录 + 防 LLM 拿乱码路径瞎调文件工具 ✅
|
||||
- **实际生效路径核验**:ProjectResolver resolve 内 `sanitize_for(raw, loc)`(resolvers.rs:96)→ `SanitizedPath::new` 包 newtype,loc 由 chat.rs:109 `sanitize::locality_of(provider)` 传入,provider 来自 `get_active_provider` 真实 DB 记录。闭环 ✅
|
||||
|
||||
**④ 【重点】spans 透传链路完整性 PASS(8 环节逐环节核验,无断链)**:
|
||||
|
||||
| 环节 | file:line | 核验 |
|
||||
|------|-----------|------|
|
||||
| 1 ChatInput.selectMention 记 spans | ChatInput.vue:432-438 | push `{start: before.length, length: label.length, kind: item.type, refId: item.id, label}` ✅ |
|
||||
| 2 ChatInput.handleSend 快照 + trim 偏移修正 | ChatInput.vue:567-571 | `rawLeadingWs = inputText.length - inputText.replace(/^\s+/).length`,span.start 整体左移 rawLeadingWs(剥前导空白致偏移失效修复)✅ |
|
||||
| 3 ChatInput.handleSend 透传 store.sendMessage | ChatInput.vue:608 | `store.sendMessage(text, skill?.name, false, parts, snapshotSpans.length > 0 ? snapshotSpans : undefined)` ✅ |
|
||||
| 4 useAiSend.sendMessage 签名 + L0/L2 透传 | useAiSend.ts:291,296,326 | `sendMessage(text, skill, forceMode, parts, spans)`,L0 `doSend(text, skill, false, parts, spans)` / L2 force `doSend(text, skill, true, parts, spans)` ✅ |
|
||||
| 5 useAiSend.doSend 调 aiApi | useAiSend.ts:107,109 | force: `aiApi.forceSend(text, lang, skill, override, parts, convId, spans)` / normal: `aiApi.sendMessage(..., spans)` ✅ |
|
||||
| 6 aiApi.sendMessage/forceSend invoke mentionSpans | ai.ts:16-27,31-42 | `invoke('ai_chat_send', {..., mentionSpans: mentionSpans && mentionSpans.length > 0 ? mentionSpans : null})`,空数组不传(后端默认 None)✅ |
|
||||
| 7 IPC ai_chat_send mention_spans 参数 | chat.rs:303 | `mention_spans: Option<Vec<MentionSpanDto>>`(Tauri 自动 camelCase→snake_case 转换)✅ |
|
||||
| 8 chat.rs resolve_and_inject 转 MentionRef | chat.rs:88-112,119-138 | spans 经 `span_to_mention_ref` 按 kind 转 MentionRef,合并 skill,`resolve_all` 投影成 Augmentation,`build_augmentation_segment` 拼段 ✅ |
|
||||
|
||||
- **【用户重点关注的断链点核验】stores/ai.ts 是否透传 spans**:stores/ai.ts **本身不实现 sendMessage**(它通过 `...useAiSend()` 展开到 useAiStore,stores/ai.ts:226),真正的 sendMessage 在 useAiSend.ts:291 且**已正确透传 spans**(第 4 环节已核验)。**无断链** ✅
|
||||
- L1 入队续发不挂 spans(useAiSend.ts:316-321 queue.push 无 spans 字段):**设计取舍非 bug**——队列项为韧性保内容,mention 区间在首次发送时已与文本对齐,排队等待后用户可能改输入,续发用旧 spans 语义模糊;注释 :287-289 明确标注,与现有 parts 入队续发的设计一致 ✅
|
||||
|
||||
**⑤ chat.rs 注入收敛一致性 PASS(4 调用方统一)**:
|
||||
- `ai_chat_send`(:382)/ `ai_chat_force_send`(:1380)/ `ai_regenerate`(:229)/ `ai_chat_edit`(:1224)四调用方均经 `resolve_and_inject(&state, &provider_config, &skill/&None, &mention_spans/&None, &lang)` ✅
|
||||
- regenerate/edit 传 `&None, &None`(无新注入诉求,注释 :227-228/:1223 说明),空结果返 `""` 跳过拼接 ✅
|
||||
- **旧 skill 注入块替换核验**:grep `read_skill_content|skill_content|注入技能` 全 chat.rs 无残留旧式注入代码,仅 `resolve_and_inject` docstring 提及"取代旧实现直接 read_skill_content 灌全文含 frontmatter"(:78-79 注释)。skill 注入已统一走 SkillResolver → read_skill_content_stripped 剥 frontmatter ✅
|
||||
|
||||
**⑥ skills.rs PASS(strip_frontmatter 状态机 + RwLock 双检锁 + ScanResult)**:
|
||||
- `strip_frontmatter`(skills.rs:62-93)状态机:首行非 `---` 返原文(:69-77)/ 首 `---` 进 in_fm 找下个 `---` 退出取正文(:79-89)/ 未闭合 frontmatter 返空串容错(:91)。单测覆盖 normal/no_fm/empty/only_fm/crlf(:348-376)✅
|
||||
- `RwLock<Option<Vec<SkillInfo>>>` 双检锁(skills.rs:278,290-311):快路径读锁命中返 guard / 慢路径释放锁扫盘 → 写锁填回(二次检查防并发重复扫)→ 再取读锁。`skills_cached()` clone 返 owned Vec(:317-320),`invalidate_skills()` 写锁置 None(:326-329)支持 `ai_reload_skills` 热重载(config.rs:41-44 invalidate + skills_cached)✅
|
||||
- `ScanResult{skills, conflicts}`(skills.rs:41-44):按 name 去重(skills>commands>plugins 优先级保留首份)+ 同名 >1 收集 conflicts(:250-266)+ SkillInfo.duplicates 回填(:32-33 serde skip_serializing_if Option::is_none 向后兼容)✅
|
||||
- `read_skill_content_stripped`(skills.rs:335-341):缓存命中读文件 → strip_frontmatter 剥正文。缓存未命中/文件读失败返 None ✅
|
||||
|
||||
**⑦ search_files 兜底 PASS(Denied 不弹窗,其他工具不变)**:
|
||||
- `check_file_tool_auth`(audit/mod.rs:175-214):paths 任一命中黑名单 → Denied(:191 短路)/ 全授权 → Authorized(:201)/ 有 pending_dirs(未命中白名单非黑名单)→ `tool_name == "search_files"` 分支返 Denied 提示(:202-210)/ 其他 → NeedsAuth 弹窗(:212)✅
|
||||
- **【用户重点关注的回归核验】其他文件工具行为不变**:`extract_file_tool_paths`(:143-161)单路径工具列表 `read_file | write_file | list_directory | patch_file | file_info | append_file | delete_file | search_files`(:155-156)+ rename_file 双路径(:145-154)。check_file_tool_auth 内只有 `tool_name == "search_files"` 单一特判(:202),**read/write/list_directory/patch_file/file_info/append_file/delete_file/rename_file 八个工具在 NeedsAuth 场景仍走原弹窗逻辑完全不变** ✅
|
||||
- 黑名单短路在 search_files 特判**之前**(:191 for 循环内),故 search_files 命中黑名单也走 Denied(reason 来自 check_path_authorization 的黑名单原因,非 search_files 特定提示),逻辑一致 ✅
|
||||
- handler 注册核验:tool_registry.rs:1574 `"search_files"` 注册为真实工具(非空壳),兜底改 Denied 不影响 handler 存在性(LLM 仍可调用,只是未授权目录时返 Err 提示而非弹窗)✅
|
||||
|
||||
**⑧ 前端 chip 元数据驱动 PASS(弃正则)**:
|
||||
- ChatInput.selectMention 记 span(ChatInput.vue:432-438)+ watch skill/@ 共存(:254-268 删原"pendingSkill 非空强制关 mention"分支,skill chip 与 @ 输入态正交)✅
|
||||
- MessageList.segmentUserContent(MessageList.vue:190-235)**纯元数据驱动非正则**:按 spans start 升序排序 → 逐 span 安全校验 `content.slice(start, end) === span.label`(:213,不等则降级跳过)→ 前导 text + chip 段 + tail text。越界(:209)/重叠(:211)/内容不符(:213)三路降级。极端全降级补完整 text(:230-232)✅
|
||||
- 模板渲染(MessageList.vue:927-933)`v-for segmentUserContent` 按 seg.type 渲染 text/chip,chip class 按 `seg.span.kind` 区分 ✅
|
||||
- **trim 偏移修正正确性**(ChatInput.vue:567-571):span.start 原相对 inputText(含前导空白),`text = inputText.trim()` 剥前导空白后偏移失效;`rawLeadingWs` 计算前导空白长度,span.start 整体左移。**尾部 trim 不影响**(chip 不在尾部空白区域)。修复正确,否则所有 chip start 错位致渲染降级纯文本 ✅
|
||||
|
||||
---
|
||||
|
||||
**🟡 MED-1:prompt.rs:108-111 docstring stale(双套注入机制未标注)**
|
||||
|
||||
| file:line | 问题 | 建议 |
|
||||
|-----------|------|------|
|
||||
| `src-tauri/src/commands/ai/prompt.rs:108-111` | build_system_prompt docstring 仍宣称「使 LLM 能解析用户消息中的 `[项目: xxx]` / `[任务: xxx]` 标记并对齐到真实实体(名称+状态+描述)」。但本批改动已把"@ mention 对齐到真实实体"的工作转交 augmentation resolver(chat.rs resolve_and_inject → build_augmentation_segment 独立段注入)。当前 build_system_prompt 仍全量注入最近 20 项目 + 20 任务到 system prompt(:118-136 全局清单),这是**两套并行的机制**:全局清单(向 LLM 提供可选项目/任务池)+ augmentation 精准投影(用户 @ 的特定实体详情)。docstring 把两套混为一谈,且"对齐到真实实体"的说法现已不准确(对齐工作已下沉到 resolver)。 | 修订 docstring 区分两套机制:build_system_prompt 注入"全局项目/任务清单(供 LLM 知晓可选范围)";augmentation 段(独立注入,见 chat.rs resolve_and_inject)负责"用户 @ 的特定实体详情对齐"。或者评估全局清单是否仍必要(若 augmentation 已覆盖用户所有 @ 场景,全局清单可能是冗余 context 膨胀——但这属行为评估,本次只标 docstring stale)。降 MED:纯文档不准,不影响行为;但易误导后续维护者以为标记解析仍在 build_system_prompt。 |
|
||||
|
||||
**⚪ LOW-1:Augmentation augmentations 字段当前无消费方(预留)**
|
||||
|
||||
`src/api/types.ts:370` AiMessage 加 `augmentations?: Augmentation[]` 字段,注释 :364-369 标注"主要用于落库消息回显/调试,前端宽容对待"。当前前端无任何代码读 `msg.augmentations`(grep 全 src/ 仅类型定义处 + 注释)。属**预留字段**(后端 resolve 后的 Augmentation 未来可能回传落库,前端先占位)。零调用方预留保留(对齐 memory dead-code-reserve-keep 原则),不删。
|
||||
|
||||
**⚪ LOW-2:ChatInput MentionItem.type 仅 'project'|'task'|'idea' 无 skill(/ 走 selectSkill 不走 selectMention)**
|
||||
|
||||
ChatInput.vue:325 `MentionItem.type: 'project' | 'task' | 'idea'` 无 skill 变体。设计正确:/ 技能走 selectSkill(:303)独立 chip 态(不插 inputText,清空输入),不走 @ selectMention。故 pendingMentionSpans 永不含 skill kind。但 chat.rs span_to_mention_ref(chat.rs:133-135)处理了 skill kind——这是**防御性兼容**(若未来 @ 技能也走 mention,或历史消息 spans 含 skill kind 时后端能正确 resolve)。无 bug,设计合理。
|
||||
|
||||
**⚪ LOW-3:augmentations/en 字段名混用(Augmentation 内 snake_case,MentionSpan refId camelCase)**
|
||||
|
||||
types.ts:383-384 注释明确:"Augmentation 内部字段沿用后端 snake_case(path/project_name 等,与 wire 严格对齐);refId 用 camelCase"。两种命名风格混在同一层。设计取舍:Augmentation 是后端投影镜像(保 snake_case 与 wire 对齐减少转译层),MentionSpan refId 是前端约定(camelCase 对齐 TS 风格)。合理但风格不统一,LOW 标注。
|
||||
|
||||
---
|
||||
|
||||
- **🟡 MED-1**: prompt.rs:108-111 docstring stale(双套注入机制未标注,易误导维护者)
|
||||
- **⚪ LOW-1**: AiMessage.augmentations 预留字段无消费方(保留符合预留原则)
|
||||
- **⚪ LOW-2**: ChatInput MentionItem.type 无 skill(/ 走 selectSkill 非selectMention,设计正确)
|
||||
- **⚪ LOW-3**: Augmentation snake_case 与 MentionSpan refId camelCase 命名风格混用(设计取舍)
|
||||
- **待修项回流 todo**: **无** 🔴/🟡 项(🟡 MED-1 纯 docstring stale,建议顺手修订不阻塞)
|
||||
|
||||
---
|
||||
|
||||
## 已审归档
|
||||
|
||||
Reference in New Issue
Block a user