docs: 巡检简报+todo 回写(2026-06-15 第2轮)
巡检发现:
- 2196c77 workflow 整文件替换回退破坏(AiChat+Ideas 12项功能)
- B-260615-03 truncated 标志已落地
- AR-8-scroll scrollToBottom smooth 已补
- CR-260615-09 .ai-md 残余:4详情页各21处 scoped .ai-md
(全局 ai-md.css 75行已建,旧副本待清理但非阻塞)
This commit is contained in:
295
docs/02-架构设计/aichat交互体验改进方案-2025-07-15.md
Normal file
295
docs/02-架构设计/aichat交互体验改进方案-2025-07-15.md
Normal file
@@ -0,0 +1,295 @@
|
||||
# AIChat 交互体验改进方案
|
||||
|
||||
> 创建: 2025-07-15 | 状态: 待讨论
|
||||
> 范围: 消息发送、流式渲染、对话管理、错误恢复、技能/Provider、窗口布局等非授权类交互
|
||||
|
||||
---
|
||||
|
||||
## 一、消息输入与发送
|
||||
|
||||
### 1.1 无法编辑已发送消息
|
||||
|
||||
**现象**:用户发出消息后发现措辞有误,只能重新打一条新消息。无法像 ChatGPT/Claude 那样编辑上一条用户消息并重新生成。
|
||||
|
||||
**根因**:`sendMessage`(useAiSend.ts)push 后的消息是只追加不可变的。前端没有编辑入口,后端 `ai_chat_send` 也没有"替换最后一条 user 消息并重跑"的语义。
|
||||
|
||||
**方案**:
|
||||
- 用户消息气泡 hover 显示"编辑"按钮
|
||||
- 点击后消息内容回填到输入框,用户修改后发送时后端截断该消息之后的所有历史(含 AI 回复),重新跑 agentic loop
|
||||
- 后端新增 `ai_chat_edit` 命令,接收 `message + 截断位置`,替换 messages 数组中对应 user 消息并清掉后续消息,然后走正常 `run_agentic_loop`
|
||||
|
||||
### 1.2 无法重新生成 AI 回复
|
||||
|
||||
**现象**:AI 回答不满意时没有"重新生成"按钮,只能重新措辞追问。
|
||||
|
||||
**根因**:`AiChat.vue` 的 AI 消息气泡上没有任何操作按钮。后端也没有 `ai_regenerate` 命令。
|
||||
|
||||
**方案**:
|
||||
- AI 消息气泡 hover 显示操作栏(复制 | 重新生成)
|
||||
- "重新生成":后端删除最后一条 AI 消息,用倒数第二条 user 消息重新触发 `run_agentic_loop`
|
||||
- 后端新增 `ai_regenerate` 命令
|
||||
|
||||
### 1.3 无法一键复制消息内容
|
||||
|
||||
**现象**:代码或文本只能手动选中复制。流式渲染中选中文字会被后续 delta 打断。
|
||||
|
||||
**根因**:消息气泡上没有复制按钮。
|
||||
|
||||
**方案**:
|
||||
- AI 消息气泡 hover 显示"复制"按钮
|
||||
- 点击后 `navigator.clipboard.writeText(msg.content)`,toast 提示"已复制"
|
||||
- 代码块单独提供"复制代码"按钮(hover 代码块右上角浮出)
|
||||
|
||||
### 1.4 缺少 `@` 实体引用
|
||||
|
||||
**现象**:用户无法在输入时引用某个项目、任务或文件。描述需求时无法精确指定上下文,AI 可能猜错对象。
|
||||
|
||||
**根因**:输入框只有 `/` 技能联想,没有 `@` 实体引用机制。
|
||||
|
||||
**方案**:
|
||||
- 输入框支持 `@` 触发实体联想浮层(复用技能联想的 popover 架构)
|
||||
- 联想源:项目列表、任务列表、最近编辑的文件
|
||||
- 选中后在消息中展开为 `[项目: u-desk]` 等标记文本,后端 system prompt 注入对应实体的上下文摘要
|
||||
|
||||
### 1.5 输入框高度过紧
|
||||
|
||||
**现象**:textarea 最大高度 120px(约 5-6 行),超过后内部滚动。用户写长提示词时看不到全貌。
|
||||
|
||||
**根因**:`autoResize` 中 `Math.min(el.scrollHeight, 120)` 限制太紧。
|
||||
|
||||
**方案**:
|
||||
- 最大高度提升到 200px(约 10 行),超过后再内部滚动
|
||||
- 或改为可拖拽调整高度(底部 resize handle)
|
||||
|
||||
---
|
||||
|
||||
## 二、流式渲染与消息展示
|
||||
|
||||
### 2.1 流式渲染中无法稳定选中文字
|
||||
|
||||
**现象**:AI 正在流式输出时,用户尝试选中已渲染的文字,新的 delta 触发 DOM 更新导致选区丢失。
|
||||
|
||||
**根因**:`renderContent` 在流式时返回 `streamingHtml.value`(rAF 节流重 parse),每次更新都 `v-html` 替换整个 DOM 子树,浏览器选区被清除。
|
||||
|
||||
**方案**:
|
||||
- 方案 A(推荐):检测到用户正在选择文字时(`selectionchange` 事件 + 选区非空且在消息容器内),暂停 rAF 流式 parse,选区结束后恢复
|
||||
- 方案 B:流式渲染时在已完成的块(blockCache 命中的块)上使用独立 DOM 节点不参与 v-html 替换,仅末块动态更新
|
||||
|
||||
### 2.2 代码块无语法高亮、无复制按钮
|
||||
|
||||
**现象**:Markdown 代码块只有纯文本渲染,没有语法高亮,也没有一键复制按钮。
|
||||
|
||||
**根因**:`useMarkdown` composable 配置了 marked + DOMPurify,但没有集成 highlight.js / Prism 等高亮器。
|
||||
|
||||
**方案**:
|
||||
- 集成 highlight.js(体积小、语言全),在 marked renderer 的 `code` 回调中调用 `hljs.highlightAuto` 或按 info string 指定语言
|
||||
- 代码块右上角浮出"复制"按钮(纯前端,hover 显示)
|
||||
- 高亮器按需加载(与 marked 一样后台预热,不阻塞首屏)
|
||||
|
||||
### 2.3 历史消息无分页懒加载
|
||||
|
||||
**现象**:切换对话时 `switchConversation` 一次性加载全部 messages 到 `state.messages`。超长对话可能一次加载几百条消息。
|
||||
|
||||
**根因**:`switchConversation`(useAiConversations.ts)直接 `state.messages = rawMsgs.filter().map()`,无分页。
|
||||
|
||||
**方案**:
|
||||
- 首次加载最近 50 条,滚动到顶部时加载更多(前端 slice + 后端支持 offset/limit 查询 messages)
|
||||
- 或前端全量加载但虚拟滚动只渲染可视区域(见 7.2)
|
||||
|
||||
### 2.4 消息不显示时间戳
|
||||
|
||||
**现象**:消息气泡上不显示发送时间。用户无法判断某条消息是多久前发的。
|
||||
|
||||
**根因**:模板中 AI/用户消息都没有渲染 `msg.timestamp`。
|
||||
|
||||
**方案**:
|
||||
- 消息气泡下方或侧边以极小字号(9px)+ dim 颜色展示相对时间(`formatRelativeZh`)
|
||||
- hover 时 tooltip 展示完整时间
|
||||
|
||||
---
|
||||
|
||||
## 三、对话管理
|
||||
|
||||
### 3.1 对话搜索缺失
|
||||
|
||||
**现象**:侧栏对话列表只能滚动浏览,没有搜索框。对话多了之后找不到特定对话。
|
||||
|
||||
**根因**:侧栏 `.ai-conv-list` 直接渲染 `groupedActive`,上方只有"新建"按钮,没有搜索输入框。
|
||||
|
||||
**方案**:
|
||||
- 侧栏 header 下方增加搜索输入框(实时过滤 `state.conversations`,匹配 title)
|
||||
- 搜索时取消分组,按相关度/时间平铺展示
|
||||
- 支持 `Ctrl+K` 快捷键聚焦搜索框
|
||||
|
||||
### 3.2 对话无法置顶
|
||||
|
||||
**现象**:没有置顶或收藏功能。重要对话会被新对话挤到下面。
|
||||
|
||||
**根因**:对话列表只按 `updated_at` 排序,无 `pinned` 字段。
|
||||
|
||||
**方案**:
|
||||
- `ai_conversations` 表增加 `pinned INTEGER DEFAULT 0`
|
||||
- 排序逻辑改为 `pinned DESC, updated_at DESC`
|
||||
- 侧栏对话项 hover 显示"置顶/取消置顶"按钮(图钉图标)
|
||||
|
||||
### 3.3 对话无法导出
|
||||
|
||||
**现象**:无法将对话导出为 Markdown / JSON / 文本文件。
|
||||
|
||||
**根因**:没有导出 IPC 命令和前端入口。
|
||||
|
||||
**方案**:
|
||||
- 后端新增 `ai_conversation_export(conv_id, format)` 命令,支持 `markdown` / `json` / `txt` 三种格式
|
||||
- 前端侧栏对话项 hover 显示"导出"按钮,或对话操作菜单中提供
|
||||
- Markdown 格式:`## 用户` / `## 助手` 交替,代码块保留围栏
|
||||
|
||||
### 3.4 新建对话强制中断已有生成
|
||||
|
||||
**现象**:用户在 AI 生成中点"新建对话",后端 `ai_conversation_create` 会强制复位 `generating=false` + 置 `stop_flag`,中断当前生成。
|
||||
|
||||
**根因**:`commands.rs ai_conversation_create` 中 `if session.generating` 分支主动结束生成(B-260615-10 设计)。
|
||||
|
||||
**方案**:
|
||||
- 生成中点"新建对话"时弹 ConfirmDialog:"当前对话正在生成,确定要新建对话并中断吗?"
|
||||
- 用户确认后才执行中断+新建;取消则不操作
|
||||
- 或改为不中断:新对话仅切换视图,旧对话后台继续生成(需 AiSession 多实例化支持,属远期方案)
|
||||
|
||||
---
|
||||
|
||||
## 四、错误处理与恢复
|
||||
|
||||
### 4.1 错误气泡无操作入口
|
||||
|
||||
**现象**:错误气泡只显示一行文字(如 `[GLM-4] AI 调用失败(HTTP 401): Unauthorized`),没有"重试"按钮或"去设置"链接。
|
||||
|
||||
**根因**:`AiError` 事件只携带 `error: String`,前端 `handleEvent` push 一条 `isError: true` 的消息气泡,无操作按钮。
|
||||
|
||||
**方案**:
|
||||
- 错误气泡底部增加操作按钮区:
|
||||
- **重试**:取上一条 user 消息重新发送(调 `ai_chat_send`)
|
||||
- **去设置**(仅 401/403/Provider 未配置时显示):跳转到 Settings → AI Tab
|
||||
- 错误消息结构扩展:后端 `AiError` 增加 `error_type: Option<ErrorType>` 枚举(`auth` / `network` / `timeout` / `provider_config` / `unknown`),前端据此决定显示哪些按钮
|
||||
|
||||
### 4.2 流式中断丢失已生成文本
|
||||
|
||||
**现象**:网络波动导致流式中断(idle timeout 或 mid-stream error),已接收的文本被丢弃(`stream_llm` return None → `agentic.rs` emit AiError 并退出),用户看到错误气泡,之前生成的几百字全部丢失。
|
||||
|
||||
**根因**:`stream_llm` 遇到错误时 `return None`,不保留已接收的部分文本。`agentic.rs` 收到 None 后直接结束。
|
||||
|
||||
**方案**:
|
||||
- `stream_llm` mid-stream error 时改为返回 `Some((partial_text, tool_calls, usage))` + 一个 `incomplete: bool` 标志
|
||||
- `agentic.rs` 收到 incomplete=true 时:
|
||||
- 已有文本正常入库(标注 `truncated`)
|
||||
- emit `AiCompleted`(而非 AiError)让前端正常展示已生成内容
|
||||
- 在消息末尾追加系统提示:"⚠ 响应因网络中断不完整"
|
||||
- 前端 AI 消息气泡底部显示"继续生成"按钮(用最后一条 user 消息重新触发,后端识别不完整消息做续写或重跑)
|
||||
|
||||
---
|
||||
|
||||
## 五、技能与 Provider
|
||||
|
||||
### 5.1 Provider 切换零反馈
|
||||
|
||||
**现象**:点击 provider bar 循环切换 provider(`cycleProvider`),切换后只在 bar 上显示名字变化,没有 toast 或动画反馈。
|
||||
|
||||
**根因**:`cycleProvider` 直接调 `store.setProvider`,无 UI 反馈。
|
||||
|
||||
**方案**:
|
||||
- 切换后 toast 提示"已切换到 {providerName}"
|
||||
- provider bar 切换时加 0.15s 淡入动画
|
||||
- bar 上增加 provider 状态指示(model 名称小字展示)
|
||||
|
||||
### 5.2 技能联想不展示参数用法
|
||||
|
||||
**现象**:技能联想浮层显示 name + description + source,但不显示技能的参数格式或示例。选中后 placeholder 里的 `argument_hint` 太短。
|
||||
|
||||
**根因**:浮层设计只展示概要信息,`argument_hint` 仅在选中后作为 placeholder 显示。
|
||||
|
||||
**方案**:
|
||||
- 联想浮层每项增加一行参数提示(`argument_hint` 以等宽字体小字展示)
|
||||
- 选中技能后输入框上方 chip 展示完整的参数格式说明(而非仅 name + description)
|
||||
- 或选中技能后自动在输入框填入模板骨架(如 `/commit `),光标定位到参数位置
|
||||
|
||||
---
|
||||
|
||||
## 六、窗口与布局
|
||||
|
||||
### 6.1 侧栏宽度不可调
|
||||
|
||||
**现象**:侧栏固定 160px,不可拖拽调整宽度。长对话标题被截断。
|
||||
|
||||
**根因**:`.ai-conv-sidebar { width: 160px; min-width: 160px; }` 硬编码。
|
||||
|
||||
**方案**:
|
||||
- 侧栏右边缘增加 2px 拖拽条(`cursor: col-resize`)
|
||||
- 拖拽时实时更新 width,范围 120~280px
|
||||
- 宽度持久化到 `df-ai-ui` 设置(与 sidebarOpen/maximized 同存)
|
||||
|
||||
### 6.2 分离窗口关闭后生成态不同步
|
||||
|
||||
**现象**:分离窗口关闭时,如果正在生成,主窗口虽然 `panelOpen=true` 恢复,但 `state.streaming` / `state.currentText` 可能不同步——如果生成中的对话不是主窗口当前活跃对话,主窗口看不到生成态。
|
||||
|
||||
**根因**:分离窗口关闭走 `closeDetachedWindow`,清了 localStorage 快照,但主窗口的状态依赖事件路由自然恢复。
|
||||
|
||||
**方案**:
|
||||
- 分离窗口关闭时,主窗口检测 `state.generatingConvId` 非空,自动切换到正在生成的对话
|
||||
- 或在主窗口 header 显示"对话 X 正在生成中"提示条,点击切换过去
|
||||
|
||||
---
|
||||
|
||||
## 七、其他交互细节
|
||||
|
||||
### 7.1 无键盘快捷键
|
||||
|
||||
**现象**:除了 Enter 发送 / Shift+Enter 换行,没有其他快捷键。
|
||||
|
||||
**方案**:
|
||||
|
||||
| 快捷键 | 功能 |
|
||||
|--------|------|
|
||||
| `Ctrl+N` | 新建对话 |
|
||||
| `Ctrl+K` | 搜索对话 |
|
||||
| `Ctrl+L` | 清空当前对话 |
|
||||
| `Ctrl+Shift+C` | 复制最后一条 AI 消息 |
|
||||
| `Ctrl+R` | 重新生成最后一条 AI 回复 |
|
||||
| `Esc` | 关闭面板(嵌入模式)/ 关闭窗口(分离模式) |
|
||||
| `Ctrl+B` | 切换侧栏 |
|
||||
|
||||
### 7.2 消息列表无虚拟滚动
|
||||
|
||||
**现象**:消息列表直接 `v-for` 渲染所有消息,长对话几百条消息全量渲染 DOM,滚动卡顿。
|
||||
|
||||
**根因**:没有使用虚拟滚动库。
|
||||
|
||||
**方案**:
|
||||
- 集成 `vue-virtual-scroller` 或自研 IntersectionObserver 懒渲染
|
||||
- 仅渲染可视区域 ±缓冲区的消息节点
|
||||
- 注意:流式渲染的最后一条消息需要始终保持挂载
|
||||
|
||||
### 7.3 空状态无引导
|
||||
|
||||
**现象**:首次打开 AI Chat(有 provider 配置),空状态只显示标题+提示语,没有示例问题或快捷操作。
|
||||
|
||||
**方案**:
|
||||
- 空状态展示 3-4 个示例问题卡片(如"帮我创建一个新项目"、"查看当前任务列表"、"分析这段代码的问题")
|
||||
- 点击卡片自动填入输入框并发送
|
||||
- 无 provider 配置时展示"去配置 AI Provider"引导按钮
|
||||
|
||||
### 7.4 对话标题生成对用户不透明
|
||||
|
||||
**现象**:对话标题由后端 `ensure_conversation_title` 异步生成,侧栏对话从"新对话"突然变成某个标题,没有过渡。
|
||||
|
||||
**方案**:
|
||||
- 标题生成后加 0.3s 淡入动画
|
||||
- 或在标题前加小图标标识"AI 自动生成"(可 hover 查看)
|
||||
|
||||
---
|
||||
|
||||
## 八、落地优先级
|
||||
|
||||
| 批次 | 痛点 | 理由 |
|
||||
|------|------|------|
|
||||
| **第一批** | 2.1 流式选中文字、1.2+1.3 消息操作栏(复制/重新生成)、4.1 错误重试、4.2 断线保文 | 每次对话都会遇到,最高频痛点 |
|
||||
| **第二批** | 2.2 代码高亮+复制、3.1 对话搜索、7.1 键盘快捷键、3.4 新建不中断生成 | 显著提升日常效率 |
|
||||
| **第三批** | 1.1 编辑重发、1.4 `@` 引用、3.3 导出、7.2 虚拟滚动、6.1 侧栏可调 | 按需推进,锦上添花 |
|
||||
| **第四批** | 2.3 分页加载、2.4 时间戳、3.2 置顶、5.1+5.2 技能/Provider 反馈、7.3+7.4 空状态/标题 | 打磨细节 |
|
||||
282
docs/02-架构设计/aichat授权体验改进方案-2025-07-15.md
Normal file
282
docs/02-架构设计/aichat授权体验改进方案-2025-07-15.md
Normal file
@@ -0,0 +1,282 @@
|
||||
# AIChat 授权功能体验改进方案
|
||||
|
||||
> 创建: 2025-07-15 | 状态: 待讨论
|
||||
|
||||
## 一、当前授权机制概览
|
||||
|
||||
### 1.1 核心流程
|
||||
|
||||
```
|
||||
用户发送消息
|
||||
→ Agentic Loop (最多 10 轮)
|
||||
→ LLM 流式响应
|
||||
→ 解析 tool_calls
|
||||
→ 按风险等级分流:
|
||||
├─ Low Risk → 自动并行执行(join_all)
|
||||
├─ Medium → 暂停循环,等待人工审批
|
||||
└─ High → 暂停循环,等待人工审批
|
||||
→ 审批通过 → 执行工具 → try_continue_agent_loop 恢复循环
|
||||
→ 审批拒绝 → tool_result="用户拒绝了此操作" → 恢复循环让 LLM 自行决策
|
||||
```
|
||||
|
||||
### 1.2 工具风险分级矩阵
|
||||
|
||||
| 风险等级 | 工具 | 执行方式 |
|
||||
|---------|------|---------|
|
||||
| **Low** | `list_projects`, `list_tasks`, `list_ideas`, `list_trash`, `read_file`, `list_directory` | 自动执行,并行 `join_all` |
|
||||
| **Medium** | `create_project`, `create_task`, `create_idea`, `update_project`, `update_task`, `bind_directory`, `write_file` | 需人工审批 |
|
||||
| **High** | `delete_project`, `restore_project`, `purge_project`, `delete_task`, `run_workflow`, `run_command` | 需人工审批 |
|
||||
|
||||
### 1.3 已实现的亮点
|
||||
|
||||
- **审计留痕**:`ai_tool_executions` 表完整记录每次工具调用的状态、风险等级、决策者、请求时间和执行时间
|
||||
- **断点恢复**:应用重启后通过 `restore_pending_approvals()` 从数据库重建内存中的待审批状态
|
||||
- **审批卡片可读化**:`build_approval_reason()` 解析工具参数,拼接人类可读的审批理由
|
||||
- **generating 状态 RAII 守护**:`GeneratingGuard` 确保无论正常退出还是 panic,`generating` 标志都能复位
|
||||
- **数据变更联动刷新**:工具执行成功后自动 emit `df-data-changed` 事件,前端自动刷新列表
|
||||
- **安全防护**:API Key 存 OS keyring、文件路径双层校验、write_file 覆盖前自动 .bak 备份 + 原子写
|
||||
|
||||
### 1.4 关键代码位置
|
||||
|
||||
| 模块 | 文件 | 职责 |
|
||||
|------|------|------|
|
||||
| 工具注册表 | `src-tauri/src/commands/ai/tool_registry.rs` | 工具定义 + 风险等级 + handler 同源 |
|
||||
| 审批处理 | `src-tauri/src/commands/ai/audit.rs` | 工具调用审计 + pending 审批恢复 + 审批后状态回填 |
|
||||
| Agentic 循环 | `src-tauri/src/commands/ai/agentic.rs` | 流式接收 → 工具执行 → 结果回传 LLM → 循环 |
|
||||
| IPC 命令 | `src-tauri/src/commands/ai/commands.rs` | `ai_approve` / `ai_chat_send` / `ai_chat_stop` 等 |
|
||||
| 审批卡片 UI | `src/components/ToolCard.vue` | 卡片渲染 + 参数展示 + 批准/拒绝按钮 |
|
||||
| 卡片列表 | `src/components/ToolCardList.vue` | 多卡片折叠管理 |
|
||||
| 聊天面板 | `src/components/AiChat.vue` | 消息列表 + 审批转发 + 输入区 |
|
||||
| AI Store | `src/stores/ai.ts` | 模块级单例 state |
|
||||
| 发送/审批 | `src/composables/ai/useAiSend.ts` | `approveToolCall` 乐观更新 + IPC 调用 |
|
||||
|
||||
---
|
||||
|
||||
## 二、用户体验痛点分析
|
||||
|
||||
### 痛点 1:逐个审批,无法批量操作
|
||||
|
||||
**现象**:当 AI 一次返回多个 Medium/High 工具调用时(比如同时创建 3 个任务 + 写 2 个文件),用户需要逐个点批准/拒绝。
|
||||
|
||||
**根因**:`process_tool_calls`(audit.rs)把每个待审批工具都推入 `pending_approvals` HashMap,前端为每个 pending 渲染独立的 ToolCard,每个卡片只有自己的"批准/拒绝"按钮。`ToolCardList.vue` 没有批量操作入口。
|
||||
|
||||
**影响**:高频交互场景下审批变成体力活,用户体验疲劳。
|
||||
|
||||
### 痛点 2:审批阻塞整个对话,期间无法输入
|
||||
|
||||
**现象**:审批等待期间 `generating=true`,用户虽然能上滑看历史,但无法发送新消息(`sendMessage` 会入队),也无法预知还有多少审批在排队。
|
||||
|
||||
**根因**:`ai_chat_send`(commands.rs:46)检查 `session.generating` 为 true 时拒绝新消息。审批等待期间 `generating` 保持 true(`agentic.rs` 的 `guard.disarm()` 保持 true 以便 `try_continue` 续生成)。
|
||||
|
||||
**影响**:用户处于"被动等待"状态,无法并行做其他事。
|
||||
|
||||
### 痛点 3:Medium 和 High 体验无差异
|
||||
|
||||
**现象**:两者都弹同样的审批卡片、同样的按钮,交互流程完全一致。
|
||||
|
||||
**根因**:`process_tool_calls`(audit.rs:103-120)中 Medium 和 High 走同一个 `pending_approvals.insert` 分支,区别仅在 `build_approval_reason` 的文案后缀("请确认是否执行" vs "高风险,需人工批准")。
|
||||
|
||||
**影响**:High 操作(如 `purge_project` 不可恢复)缺乏足够的警示力度,容易误操作。
|
||||
|
||||
### 痛点 4:write_file 审批信息不够直观
|
||||
|
||||
**现象**:`write_file` 是 Medium 风险,审批卡片只展示 path 和 content 参数。content 可能是几百行代码,在审批卡片里以 `formatArgValue` 截断到 300 字符展示。
|
||||
|
||||
**根因**:`ToolCard.vue` 的 `toolArgsEntries` → `displayArgValue` → `formatArgValue` 对超长值统一截断到 300 字符。
|
||||
|
||||
**影响**:用户无法看清要写入的完整内容,只能盲目批准。尤其覆盖已有文件时,用户不知道会改什么。
|
||||
|
||||
### 痛点 5:审批后无进度反馈
|
||||
|
||||
**现象**:点击"批准"后,卡片乐观置 `running`,但用户不知道:
|
||||
- 工具正在执行还是已执行完等待 LLM 续生成
|
||||
- 还有多少待审批在排队
|
||||
- 整个 agentic 循环进行到第几轮
|
||||
|
||||
**根因**:前端没有全局的 agentic 循环进度指示器。`AiAgentRound` 事件虽然通知了轮次,但没有在 UI 上持久化展示。
|
||||
|
||||
**影响**:用户对系统状态缺乏掌控感,尤其在多轮工具调用时。
|
||||
|
||||
### 痛点 6:审批卡片可能被滚出视口
|
||||
|
||||
**现象**:当消息很多时,审批卡片可能被新消息推到上方滚出视口。
|
||||
|
||||
**根因**:`AiChat.vue` 的 `collapseInactive` 不会收起 `pending_approval` 卡片,但也不会自动滚动到 pending 卡片。没有全局徽标提醒。
|
||||
|
||||
**影响**:用户可能看不到待审批项,对话看起来"卡住了"但不知道在等什么。
|
||||
|
||||
---
|
||||
|
||||
## 三、改进方案
|
||||
|
||||
### P0 — 快速改善体感
|
||||
|
||||
#### 3.1 批量审批
|
||||
|
||||
**方案**:当同一轮有多个 pending 时,在 ToolCardList 顶部显示"全部批准(N) / 全部拒绝"按钮。
|
||||
|
||||
**改动范围**:
|
||||
- `ToolCardList.vue`:新增批量操作栏,监听 toolCalls 中 pending_approval 数量
|
||||
- `useAiSend.ts`:新增 `approveAll(rejectAll)` 方法,循环调用 `ai_approve`
|
||||
|
||||
**交互**:
|
||||
```
|
||||
┌─────────────────────────────────┐
|
||||
│ ⏳ 3 项待审批 │
|
||||
│ [✓ 全部批准] [✕ 全部拒绝] │
|
||||
├─────────────────────────────────┤
|
||||
│ [工具卡片 1 - pending] │
|
||||
│ [工具卡片 2 - pending] │
|
||||
│ [工具卡片 3 - pending] │
|
||||
└─────────────────────────────────┘
|
||||
```
|
||||
|
||||
#### 3.2 审批计数器 + 跳转
|
||||
|
||||
**方案**:在输入框上方或 header 显示 `⏳ 2 项待审批`,点击跳转到第一个 pending 卡片。
|
||||
|
||||
**改动范围**:
|
||||
- `AiChat.vue`:header 区域增加审批徽标
|
||||
- `ToolCardList.vue`:暴露 `scrollToFirstPending` 方法
|
||||
|
||||
**交互**:
|
||||
```
|
||||
┌──────────────────────────────────┐
|
||||
│ 🤖 助手 ⏳2 [+][×] │ ← 徽标在 header
|
||||
├──────────────────────────────────┤
|
||||
│ ...消息列表... │
|
||||
│ ┌─ 工具卡片 (pending) ──┐ │ ← 点击徽标滚动到此
|
||||
│ │ 创建任务:XXX │ │
|
||||
│ │ [批准] [拒绝] │ │
|
||||
│ └────────────────────────┘ │
|
||||
├──────────────────────────────────┤
|
||||
│ [输入框] │
|
||||
└──────────────────────────────────┘
|
||||
```
|
||||
|
||||
#### 3.3 write_file diff 预览
|
||||
|
||||
**方案**:write_file 审批时,如果文件已存在,展示前后对比 diff 而非裸 content。
|
||||
|
||||
**改动范围**:
|
||||
- 后端 `tool_registry.rs`:write_file handler 在执行前读取旧文件内容,返回 diff 信息(或前端请求 diff)
|
||||
- `ToolCard.vue`:pending_approval + name=write_file 时渲染 diff 视图
|
||||
|
||||
**交互**:
|
||||
```
|
||||
┌─ 写入文件 src/main.rs (待审批) ──────┐
|
||||
│ │
|
||||
│ - fn main() { │ ← 红色:删除行
|
||||
│ - println!("hello"); │
|
||||
│ + fn main() { │ ← 绿色:新增行
|
||||
│ + println!("hello, world"); │
|
||||
│ + setup_logging(); │
|
||||
│ │
|
||||
│ ⚠ 覆盖已有文件 (23→45 行) │
|
||||
│ [查看完整内容] [批准] [拒绝] │
|
||||
└───────────────────────────────────────┘
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### P1 — 增强控制力
|
||||
|
||||
#### 3.4 自动审批策略(信任模式)
|
||||
|
||||
**方案**:Settings 中增加"自动审批"配置,用户可选择对特定风险等级或工具类型自动放行。
|
||||
|
||||
**配置项**:
|
||||
```
|
||||
Settings → AI → 自动审批策略
|
||||
○ 严格模式(默认):所有 Medium/High 需人工审批
|
||||
○ 宽松模式:Medium 自动放行,High 需人工审批
|
||||
○ 自定义:按工具类型选择
|
||||
☑ write_file(workspace 内自动放行)
|
||||
☑ create_task / create_idea(自动放行)
|
||||
☐ delete_*(始终需审批)
|
||||
☐ run_command(始终需审批)
|
||||
```
|
||||
|
||||
**改动范围**:
|
||||
- `df-storage`:`app_settings` 表存储配置(KV 已有 V13 表)
|
||||
- `audit.rs`:`process_tool_calls` 读取配置,决定 Medium 工具是否进 pending 或直接执行
|
||||
- `Settings.vue`:新增配置面板
|
||||
|
||||
#### 3.5 High 二次确认
|
||||
|
||||
**方案**:delete/purge/run_command 等高风险操作,批准后弹出二次确认。
|
||||
|
||||
**改动范围**:
|
||||
- `ToolCard.vue`:High 风险 + approved=true 时,先弹 inline 确认("确定要永久删除?此操作不可恢复")
|
||||
- 或用现有 `ConfirmDialog` 组件
|
||||
|
||||
**交互**:
|
||||
```
|
||||
第一次点击"批准":
|
||||
→ 卡片内弹出确认提示
|
||||
→ "确定要永久删除项目「XXX」?此操作不可恢复"
|
||||
→ [确认删除] [取消]
|
||||
|
||||
第二次点击"确认删除":
|
||||
→ 才真正执行 ai_approve
|
||||
```
|
||||
|
||||
#### 3.6 审批超时
|
||||
|
||||
**方案**:可配置超时自动拒绝(默认 5 分钟),避免对话永久卡住。
|
||||
|
||||
**改动范围**:
|
||||
- 后端:`AiSession` 增加 pending 审批的 `created_at` 时间戳,定时检查超时
|
||||
- 或前端:`useAiSend.ts` 在 pending 时启动定时器,超时自动调 `ai_approve(id, false)`
|
||||
|
||||
---
|
||||
|
||||
### P2 — 信息透明度
|
||||
|
||||
#### 3.7 Agentic 进度条
|
||||
|
||||
**方案**:在消息区域底部显示循环进度。
|
||||
|
||||
**改动范围**:
|
||||
- `AiChat.vue`:底部增加进度指示条
|
||||
- `useAiEvents.ts`:处理 `AiAgentRound` 事件时更新进度
|
||||
|
||||
**交互**:
|
||||
```
|
||||
┌──────────────────────────────────┐
|
||||
│ ...消息列表... │
|
||||
│ │
|
||||
│ 🔄 循环 3/10 · ⏳2待审批 · ✅5完成 │ ← 底部进度条
|
||||
├──────────────────────────────────┤
|
||||
│ [输入框] │
|
||||
└──────────────────────────────────┘
|
||||
```
|
||||
|
||||
#### 3.8 审批历史面板
|
||||
|
||||
**方案**:独立页面展示 `ai_tool_executions` 表的审计记录。
|
||||
|
||||
**改动范围**:
|
||||
- 后端:新增 `list_tool_executions` IPC 命令(查询审计表)
|
||||
- 前端:新增 `AuditLog.vue` 视图,表格展示历史记录
|
||||
|
||||
**展示字段**:
|
||||
| 时间 | 工具 | 风险 | 状态 | 决策者 | 参数摘要 | 结果摘要 |
|
||||
|------|------|------|------|--------|---------|---------|
|
||||
|
||||
---
|
||||
|
||||
## 四、实施优先级建议
|
||||
|
||||
| 优先级 | 改进项 | 预估工作量 | 用户价值 |
|
||||
|--------|--------|-----------|---------|
|
||||
| **P0** | 3.1 批量审批 | 0.5 天 | 🔥🔥🔥 |
|
||||
| **P0** | 3.2 审批计数器 + 跳转 | 0.5 天 | 🔥🔥🔥 |
|
||||
| **P0** | 3.3 write_file diff 预览 | 1 天 | 🔥🔥 |
|
||||
| **P1** | 3.4 自动审批策略 | 1.5 天 | 🔥🔥🔥 |
|
||||
| **P1** | 3.5 High 二次确认 | 0.5 天 | 🔥 |
|
||||
| **P1** | 3.6 审批超时 | 0.5 天 | 🔥 |
|
||||
| **P2** | 3.7 Agentic 进度条 | 0.5 天 | 🔥🔥 |
|
||||
| **P2** | 3.8 审批历史面板 | 1 天 | 🔥 |
|
||||
|
||||
**建议第一批落地**:P0 三项(批量审批 + 计数器 + diff 预览),总计约 2 天工作量,覆盖最高频的体验痛点。
|
||||
360
docs/02-架构设计/patch_file工具设计-2026-06-15.md
Normal file
360
docs/02-架构设计/patch_file工具设计-2026-06-15.md
Normal file
@@ -0,0 +1,360 @@
|
||||
# Patch File 工具设计
|
||||
|
||||
> 创建: 2026-06-15 | 状态: 设计定稿待实施 | 优先级: P0
|
||||
> 关联 todo: F-260615-06 [P0]
|
||||
|
||||
---
|
||||
|
||||
## 一、问题定义
|
||||
|
||||
### 1.1 现状缺口
|
||||
|
||||
DevFlow AI agent 有 `write_file`(全量覆盖写入)但**无局部编辑能力**。AI 需要修改文件中某几行时只能:
|
||||
|
||||
```
|
||||
read_file → AI 在 context 中拼出完整新内容 → write_file 全量覆盖
|
||||
```
|
||||
|
||||
**问题链**:
|
||||
1. **Token 浪费**:大文件(几百行)只改 3 行却要重发全部内容
|
||||
2. **事故风险**:write_file 全量覆盖已出事故(PROGRESS.md 762 行 → 248 字节,FR-S7 记录)
|
||||
3. **无审计粒度**:无法知道「改了哪里」,只有「整个文件被替换了」
|
||||
4. **并发不安全**:join_all 并行场景下多工具操作同一文件无保护
|
||||
|
||||
### 1.2 目标
|
||||
|
||||
提供 `patch_file` 工具,让 AI 能做**精确的局部文本替换**,形成完整的文件操作闭环:
|
||||
|
||||
```
|
||||
read_file(读) → search_in_file(定位) → patch_file(改) → run_command(验证)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 二、API 设计
|
||||
|
||||
### 2.1 请求结构
|
||||
|
||||
```rust
|
||||
/// 局部文件更新请求
|
||||
struct PatchFileRequest {
|
||||
/// 目标文件路径(必填,走 validate_path 校验 + 黑名单)
|
||||
path: String,
|
||||
|
||||
/// 文件指纹(可选):用于检测外部修改
|
||||
/// 格式: "{unix_timestamp}_{size}" 如 "1718400000_12345"
|
||||
/// 由 read_file 返回的 file_hash 字段携带
|
||||
expected_hash: Option<String>,
|
||||
|
||||
/// 有序补丁列表(从文件末尾往前执行,避免行号偏移)
|
||||
patches: Vec<Patch>,
|
||||
}
|
||||
|
||||
/// 单个补丁
|
||||
struct Patch {
|
||||
/// 必填:要替换的旧文本(精确匹配 = 乐观锁)
|
||||
old_text: String,
|
||||
|
||||
/// 必填:替换后的新文本
|
||||
new_text: String,
|
||||
|
||||
/// 可选:行号辅助定位(快速跳转 + 去歧增强)
|
||||
/// 有值时优先跳到该行检查 old_text;不匹配则降级全文扫描
|
||||
line: Option<u32>,
|
||||
|
||||
/// 可选:old_text 之前的上下文锚(去歧——多匹配时精确锁定)
|
||||
before_text: Option<String>,
|
||||
|
||||
/// 可选:old_text 之后的上下文锚(去歧)
|
||||
after_text: Option<String>,
|
||||
}
|
||||
```
|
||||
|
||||
### 2.2 响应结构
|
||||
|
||||
```rust
|
||||
struct PatchResult {
|
||||
success: bool,
|
||||
patches_applied: usize, // 成功替换的 patch 数
|
||||
total_matches: usize, // 每个 patch 的总命中数(含未替换的)
|
||||
lines_changed: i32, // 总行数变化(正=增加 负=减少)
|
||||
warnings: Vec<String>, // ["匹配到 3 处,仅替换第 1 处"]
|
||||
file_hash: String, // 操作后的新指纹(下次操作用)
|
||||
}
|
||||
```
|
||||
|
||||
### 2.3 read_file 扩展(返回指纹)
|
||||
|
||||
现有 read_file 返回值新增字段:
|
||||
|
||||
```rust
|
||||
struct ReadFileResult {
|
||||
path: String,
|
||||
content: String,
|
||||
size: u64,
|
||||
lines: u32,
|
||||
// 新增:
|
||||
modified: String, // ISO8601 或 unix timestamp
|
||||
file_hash: String, // mtime+size 指纹,如 "1718400000_12345"
|
||||
}
|
||||
```
|
||||
|
||||
### 2.4 LLM 描述(tool_registry 注册用)
|
||||
|
||||
```
|
||||
"局部更新文件内容。用于精确修改文件的特定部分(而非全量覆盖)。
|
||||
每个补丁指定 old_text(要替换的原文)和 new_text(新内容)。
|
||||
可选 line 辅助定位、before/after_text 上下文锚定消除歧义。
|
||||
属 Medium 风险操作(修改已有文件),需人工审批。
|
||||
注意:old_text 必须与文件内容完全匹配(含空格/缩进);若文件已被外部修改,
|
||||
请先重新 read_file 获取最新内容和 file_hash。"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 三、核心决策记录
|
||||
|
||||
### 决策 1:定位方式 — old_text 精确匹配为主,line 为辅
|
||||
|
||||
| 方案 | 示例 | 优点 | 缺点 |
|
||||
|------|------|------|------|
|
||||
| **A: old_text ✅ 选定** | 匹配 "fn main() {" 替换 | =隐式乐观锁;AI零认知负担(复制即用);内容变了自动冲突报错 | 改动大时 old_string 长 |
|
||||
| B: line 行号 | 替换第 42 行 | 短小精悍 | 文件改了行号偏移;AI需先search定位(多一轮IPC) |
|
||||
| C: 正则 regex | 匹配 `/return Err\(.*\)/` | 表达力强 | AI生成regex易出错;转义复杂 |
|
||||
|
||||
**折中**:old_text **必填**(主定位),line **可选**(辅助快速跳转+去歧),before/after_text **可选**(多匹配去歧)。
|
||||
|
||||
**理由**:
|
||||
- AI 做 edit 时天然持有「要改哪段」上下文,复制即用(最小认知路径)
|
||||
- old_text 天然防并发冲突(CAS 语义:Compare-And-Swap)
|
||||
- line 不单独使用(无内容校验=盲替换,并发不安全)
|
||||
|
||||
### 决策 2:多匹配处理 — 替换第 1 处 + warning
|
||||
|
||||
```
|
||||
文件中有 N(N>1) 处相同 old_text:
|
||||
→ 仅替换第 1 处
|
||||
→ 返回 warning: "⚠️ 匹配到 N 处,仅替换第 1 处"
|
||||
→ AI 收到 warning 后可加 before/after_text 缩小范围重试
|
||||
```
|
||||
|
||||
**不选**「全部替换」(太危险,可能批量错改)或「拒绝执行」(太严格,第 1 处往往就是目标)。
|
||||
|
||||
### 决策 3:并发安全 — 文件级 Mutex(不用队列)
|
||||
|
||||
#### 为什么不用队列
|
||||
|
||||
| 维度 | 通用文件锁队列 | DevFlow 实际需要 |
|
||||
|------|-------------|-----------------|
|
||||
| 范围 | 全局、所有会话、所有文件 | 仅 join_all 并发窗口 + 远期多会话 |
|
||||
| 粒度 | 每文件独立 FIFO 队列 | 文件级 Mutex 就够 |
|
||||
| 复杂度 | 高(调度/超时/死锁检测) | **极低(~15行)** |
|
||||
| 场景 | 多用户 / 分布式 | 单进程单用户桌面应用 |
|
||||
|
||||
#### 当前真实并发源
|
||||
|
||||
```
|
||||
唯一并行点: audit.rs:338 join_all — Low 风险工具并行执行
|
||||
典型场景: AI 同时 list_projects + read_file + (未来) patch_file 同一文件
|
||||
```
|
||||
|
||||
#### 实现
|
||||
|
||||
```rust
|
||||
use std::collections::HashMap;
|
||||
use std::path::PathBuf;
|
||||
use std::sync::Mutex;
|
||||
use once_cell::sync::Lazy;
|
||||
|
||||
/// 全局文件锁表:每个路径一把互斥锁
|
||||
static FILE_LOCKS: Lazy<Mutex<HashMap<PathBuf, ()>>> =
|
||||
Lazy::new(|| Mutex::new(HashMap::new()));
|
||||
|
||||
// handler 内使用:
|
||||
let abs_path = validated_path.canonicalize()?;
|
||||
let _guard = FILE_LOCKS
|
||||
.lock()
|
||||
.entry(abs_path)
|
||||
.or_insert_with(|| ());
|
||||
// guard drop 时自动释放
|
||||
```
|
||||
|
||||
**效果**:同一文件读写串行化,不同文件仍并行。Mutex 释放后后续操作继续。
|
||||
|
||||
### 决策 4:外部脏写防御 — expected_hash 指纹校验
|
||||
|
||||
#### 指纹选型
|
||||
|
||||
| 方案 | 精度 | 开销 | 适用 |
|
||||
|------|------|------|------|
|
||||
| **mtime + size ✅ 选定** | 秒级 | ~μs(一次 metadata 调用) | 本地桌面应用 |
|
||||
| blake3/sha256 | 内容级 | 大文件 ms 级 | 需要密码学强度时 |
|
||||
| inode + mtime | Unix 语义 | ~μs | Windows inode 不同 |
|
||||
|
||||
选 **mtime + size**:DevFlow 是本地桌面应用,「外部修改」= 用户切 VS Code 改了几行再回来,时间差 >1s。实现最简单。
|
||||
|
||||
#### 流程
|
||||
|
||||
```
|
||||
read_file(path) → { content, file_hash: "1718400000_23456" }
|
||||
↓
|
||||
AI 基于内容决策 ↓
|
||||
patch_file({ path, expected_hash: "1718400000_23456", ... })
|
||||
↓
|
||||
后端:
|
||||
① current_meta = metadata(path)
|
||||
② current_hash = format!("{}_{}", modified.timestamp(), size)
|
||||
③ current_hash != expected_hash?
|
||||
→ Err("⚠️ 文件已被外部修改(hash 不匹配),请重新读取")
|
||||
含 current_hash 让前端可选自动重读
|
||||
④ hash 通过 → 执行 patch(L2 old_text 校验 + L3 .bak)
|
||||
```
|
||||
|
||||
### 决策 5:截断策略 — 软删除标记(非真删)
|
||||
|
||||
关联 UX-2025-09 编辑消息功能。patch_file 本身不涉及消息截断,但设计原则一致:
|
||||
|
||||
```
|
||||
messages 表: status 列
|
||||
active — 正常显示
|
||||
truncated — 被编辑截断(前端不展示,后端不进 context)
|
||||
保留历史可追溯,与 WF-A soft_delete 模式一致
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 四、三层防御架构
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────┐
|
||||
│ L1: 文件级 Mutex │
|
||||
│ 防时机冲突:同文件读写自动串行化 │
|
||||
│ 实现: HashMap<PathBuf, Mutex<()>> (~15行) │
|
||||
│ │
|
||||
│ ┌───────────────────────────────────────┐ │
|
||||
│ │ L2: old_text 精确匹配 │ │
|
||||
│ │ 防内容错配:= 乐观锁(CAS) │ │
|
||||
│ │ 内容变了 → 匹配不上 → 报错 │ │
|
||||
│ │ 实现: 内容扫描 (~20行) │ │
|
||||
│ │ │ │
|
||||
│ │ ┌─────────────────────────────────┐ │ │
|
||||
│ │ │ L3: expected_hash 指纹校验 │ │ │
|
||||
│ │ │ 防版本漂移: 外部修改检测 │ │ │
|
||||
│ │ │ 实现: metadata() (~10行) │ │ │
|
||||
│ │ └─────────────────────────────────┘ │ │
|
||||
│ └───────────────────────────────────────┘ │
|
||||
│ │
|
||||
│ 底层兜底: FR-S7 .bak 备份(误操作可恢复) │
|
||||
└─────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
各层职责独立、互补:
|
||||
|
||||
| 层 | 管 | 防什么 | 失效后果 |
|
||||
|----|-----|--------|---------|
|
||||
| L1 Mutex | 时机 | 两操作同时碰同一文件 | 数据丢失/半写 |
|
||||
| L2 old_text | 内容 | 操作基于过时内容 | 错改别处 |
|
||||
| L3 hash | 版本 | read→patch之间文件被外部改 | 基于错误版本操作 |
|
||||
| .bak | 恢复 | 以上全失效时的最后防线 | 可回滚 |
|
||||
|
||||
---
|
||||
|
||||
## 五、查找策略(line + old_text 组合)
|
||||
|
||||
```
|
||||
有 line 参数?
|
||||
├─ YES → 跳到该行,检查周围是否包含 old_text
|
||||
│ ├─ 匹配 → 替换(快速路径 ✅)
|
||||
│ └─ 不匹配(行已偏移) → 降级: 全文扫描 old_text
|
||||
└─ NO → 全文扫描 old_text
|
||||
|
||||
全文扫描结果:
|
||||
├─ 0 处匹配 → Err("未找到目标文本,文件可能已被修改")
|
||||
├─ 1 处匹配 → 替换 ✅
|
||||
└─ N 处匹配(N>1) → 替换第 1 处 + warning("匹配到 N 处...")
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 六、边界情况处理
|
||||
|
||||
| 边界情况 | 行为 | 理由 |
|
||||
|---------|------|------|
|
||||
| 文件不存在 | Err("文件不存在") | 安全第一 |
|
||||
| 文件 >1MB | warn + 继续或拒绝(复用 FR-S2 上限) | 防性能问题 |
|
||||
| 二进制文件(含 \0) | Err("不支持二进制文件") | 文本操作不适用 |
|
||||
| old_text 为空串 | Err("old_text 不能为空") | 防全文件匹配 |
|
||||
| new_text == old_text | success + warning("无实际更改") | 不浪费 I/O |
|
||||
| patches 为空 | Err("patches 不能为空") | 无意义调用 |
|
||||
| 单次 patch 文件膨胀 >900% | warn(复用 FR-S7 逻辑) | 异常检测 |
|
||||
| 目标路径是 .bak/.tmp | is_noise_file 过滤拒绝(CR-03) | 不改临时文件 |
|
||||
| 路径含 ".." | validate_path 黑名单拦截 | 路径遍历防护 |
|
||||
| RiskLevel | **Medium**(修改已有文件) | 比 write_file 同级(都是改文件) |
|
||||
|
||||
---
|
||||
|
||||
## 七、性能分析
|
||||
|
||||
| 操作 | 开销 | 对比基准 |
|
||||
|------|------|---------|
|
||||
| Mutex 获取/释放 | ~100ns(非竞争)/ μs 级(等待) | 文件 I/O 是 ms 级,可忽略 |
|
||||
| 全文扫描 old_text | O(n), n=行数(<1MB) | <1ms |
|
||||
| hash 计算 | metadata() 一次系统调用 | ~μs 级 |
|
||||
| .bak 备份 | 文件大小一次 copy | SSD ~100MB/s |
|
||||
| **总开销** | | **<5ms(<< LLM 秒级延迟)** |
|
||||
|
||||
---
|
||||
|
||||
## 八、与现有架构兼容性
|
||||
|
||||
| 维度 | 兼容性 |
|
||||
|------|--------|
|
||||
| tool_registry 注册 | ✅ 完全复用 write_file 模式 |
|
||||
| RiskLevel 分流 | ✅ Medium → 审批(白名单收紧:纯读取外全审) |
|
||||
| audit 审计日志 | ✅ process_tool_calls 自动入库 |
|
||||
| validate_path | ✅ 复用黑名单 + canonicalize |
|
||||
| .bak 备份 | ✅ 复用 FR-S7 已有逻辑 |
|
||||
| ToolCard 前端渲染 | ✅ args 键值对自动适配 |
|
||||
| 数据变更联动 AR-11 | ✅ emit df-data-changed 触发刷新 |
|
||||
| 会话级授权 AE-04 | ✅ write_file 同类操作,可纳入 session_trust |
|
||||
|
||||
---
|
||||
|
||||
## 九、实施步骤
|
||||
|
||||
### 第一批(核心三件套,~50 行后端 + 前端零改动)
|
||||
|
||||
1. **`mod.rs` 或 `tool_registry.rs` 顶层**:加 `FILE_LOCKS` 静态 Mutex(~10 行)
|
||||
2. **`tool_registry.rs`**:注册 `patch_file` handler(~40 行)
|
||||
- 参数解析 + validate_path
|
||||
- expected_hash 校验(可选,第一批可先加框架)
|
||||
- 逐 patch 执行:扫描 old_text → 替换 → 记录结果
|
||||
- .bak 备份(复用 write_file 逻辑)
|
||||
- 截断输出(stdout/stderr 各 10KB)
|
||||
3. **测试**:AI 调用 `patch_file` 改一个已知文件,验证替换正确性
|
||||
|
||||
### 第二批(增强层,按需)
|
||||
|
||||
4. before/after_text 锁定逻辑(~15 行)
|
||||
5. expected_hash 指纹校验完整接入(~10 行)
|
||||
6. read_file 返回值扩展 file_hash 字段(~5 行)
|
||||
|
||||
### 第三批(远期)
|
||||
|
||||
7. replace_all 开关
|
||||
8. regex 支持(old_regex 字段)
|
||||
9. undo stack(会话内 patch 历史)
|
||||
|
||||
---
|
||||
|
||||
## 十、替代方案否决记录
|
||||
|
||||
| 方案 | 否决理由 |
|
||||
|------|---------|
|
||||
| sed/awk via run_command | B-37 stdout 空;修好后也是间接操作,无原子性/备份/审计 |
|
||||
| write_file 全量覆盖 | 已出事故(762→248字节);大文件 token 浪费;无 diff |
|
||||
| git apply (Git-based patch) | 强依赖 git 仓库;非 git 目录不可用 |
|
||||
| JSON Patch (RFC 6902) | 面向 JSON/结构化数据;不适合自由格式文本 |
|
||||
| AST-level (tree-sitter) | 过度工程;需每语言 parser;AI 代码未必能 parse |
|
||||
| 纯 line 号编辑 | 无内容校验=并发不安全;行号漂移易出错 |
|
||||
| 全局文件队列 | 单用户桌面不需要;Mutex 够用且简单 10x |
|
||||
@@ -322,6 +322,12 @@
|
||||
- **治理方向**:① 后端支持阶段流转(planning→in_progress…);② 前端 map 对齐后端真实值(active/deleted/archived);③ 拆双字段(status 生命周期 + stage 开发阶段)。
|
||||
- **状态**:📐 待治理(治标已落地,根本病根未除)
|
||||
|
||||
### i18n import 统一 `@/i18n` 路径别名 [2026-06-15]
|
||||
- **决策**:vite.config.ts 加 `resolve.alias.{ '@': '/src' }` 路径别名;全项目 i18n import 统一为 `import i18n from '@/i18n'`,替代相对路径 `../i18n`/`../../i18n`。
|
||||
- **原因/取舍**:CR-08 i18n 批量改造时,workflow 代理将 stores/ 下层文件 i18n import 从相对路径(`../../i18n`)改为错误多层的 `../../../i18n` 或正确的 `@/i18n`(但项目无别名配置),导致 vite 构建失败(Could not resolve)。相对路径随文件深度变化易断(stores/ 两层 vs composables/ai/ 三层 vs utils/ 一层);`@/i18n` 绝对路径不随文件位置变化,零维护成本;Vue/Vite 生态标准做法(多数 Vue 项目默认配 `@` → src);别名仅影响构建时解析,运行时无开销。
|
||||
- **影响范围**:9 个源文件(import 侧) + 1 个配置文件(vite.config.ts)。
|
||||
- **状态**:✅ 已落地(commit d6eb855 + 6254d06)
|
||||
|
||||
## 状态持久化
|
||||
|
||||
### 窗口位置/大小:用 tauri-plugin-window-state(纯 Rust 层)
|
||||
@@ -481,6 +487,14 @@
|
||||
- **「决策」术语边界**(2026-06-12):devflow 语境「决策/决策需求点」= 日常开发功能细节取舍(为什么这么定),**非** aichat 决策能力升级(B 路线 coordinator/conditions)。后者属架构层,记 Phase2计划/模块文档,不混入本文档。
|
||||
- **代码审查甄别原则**(2026-06-13):审查发现问题时,按「运行时失败/数据损坏 → 简单清理 → 记录不动 → 不做」四档甄别。当前项目规模下,list_all 无 LIMIT、ALLOWED_COLUMNS 不分表、bool→int 重复等属「记录不动」——个人工具表不超千行,加分页/拆白名单是过度设计,维护成本 >> 收益。原则:**真实 bug 修、简单清理做、规模不到位的优化先不动**,保持全局简洁和扩展容易。
|
||||
|
||||
## 文档维护
|
||||
|
||||
### 文档历史项保留原则:标状态不删行 [2026-06-15]
|
||||
- **决策**:所有文档(ARCHITECTURE.md + 模块文档)中的历史设计项**一律保留原文不删除**,仅在行末或旁注标注实现状态:`✅ 已实现` / `❌ 未实现(设计预留)` / `⚠️ 骨架空壳(有文件但无实质逻辑)` / `~~已删~~`(R-PD-X 等重构决策引用)。
|
||||
- **原因/取舍**:全量核对报告(2026-06-15)发现 ARCHITECTURE.md 含多出虚构/过时项(ModelRouter 已删/Docker 等 5 节点未实现)。初版方案为「删虚构行+注」,用户两次明确否决删方案,要求保留全部历史项+标状态。理由:① 保留设计演进痕迹,接手方可理解"曾经考虑过什么、为什么没做";② 删除会导致核对报告等交叉引用断链;③ 标状态列比删行信息量更大。
|
||||
- **影响范围**:DOC-260615-01~14 全部文档修项均遵循此原则。已落地:ARCHITECTURE.md §5.4 ModelRouter 标 `❌ ~~已删~~` + §5.5 8 节点标 `✅` / `❌`(commit be38a44)。
|
||||
- **状态**:✅ 2026-06-15 落地(用户两次否决删方案后定稿,首批标注已 commit)
|
||||
|
||||
**相关文档**:
|
||||
- [Phase 1 架构决策](./Phase1架构决策-2026-06-12.md) — 架构级决策(ADR)
|
||||
- [经验记录](./经验记录-2026-06-14.md) — 踩坑/约定/技巧/bug 排查教训
|
||||
|
||||
85
docs/05-代码审查/定时走查-2026-06-15-P0复核.md
Normal file
85
docs/05-代码审查/定时走查-2026-06-15-P0复核.md
Normal file
@@ -0,0 +1,85 @@
|
||||
# 定时代码走查 — P0 状态复核(2026-06-15)
|
||||
|
||||
> 触发:定时走查 cron(每 30 分钟)。本轮重点 = 自上次走查(HEAD `4b5f096`)起新增内容走查 + 既有 P0 修复状态地面复核。
|
||||
> 方法:2 路后台代理(TaskDetail.vue 新文件 / 后端提交 `2de0c6e` 16 文件大改)+ 主代理地面核对(`project.ts:273` / `workflow.rs:211` / `useAiStream.ts` / `stream_recv.rs` / `todo.md`)。
|
||||
> 性质:核对记录,不改产品代码(本会话 session-role-diagnose-only)。todo 待修项见 `docs/todo.md`。
|
||||
> 关联:[工作区多角度走查-2026-06-15.md](./工作区多角度走查-2026-06-15.md)(上一轮,3 P0 + DRY 发现源头)。
|
||||
|
||||
---
|
||||
|
||||
## 核心结论:纠正过时认知(定时走查首要价值)
|
||||
|
||||
| P0 | 上轮判断 | 本轮地面复核 | 处置 |
|
||||
|---|---|---|---|
|
||||
| **B-260615-34** selectType | 「未修,仍 camelCase」 | **已修 ✅** — `project.ts:273` 现为 `select_type: ... ?? 'single'` + 正确注释「Tauri 2 IPC 不转 camelCase,须 snake_case 对齐后端 workflow.rs:211」;后端 `workflow.rs:211` 同 `select_type: Option<String>`。前后端契约对齐。 | todo 标 `[x] ✅`,删过时描述 |
|
||||
| **B-260615-32** 队列收尾 | 未修 | **仍未修 ❌** — `onStreamTimeout`(useAiStream.ts:24-53)复位 streaming/generatingConvId/currentText + clearStreamWatchdog + 反向扫 completed toolCall 区分文案 + push 错误消息,**仍不清 `state.queue`**(useAiSend:116 有 clearQueue 但收尾路径未调)→ 生成中输入消息丢失 | todo 加复核标注,仍 `[ ]` |
|
||||
| **B-260615-33** 骨架屏 | 未修 | **仍未修 ❌** — `onStreamTimeout` 仍**不回滚 running toolCall**(只读 `status===completed` 做文案判断,不改 running→rejected)→ 审批卡片后端 hang 时永久骨架屏,无重审入口 | todo 加复核标注,仍 `[ ]` |
|
||||
|
||||
> 「B-34 未修」是上一轮压缩前快照,之后提交(`80b9243`「P0 bug 修复」批)已修。后端代理通过后端 IPC 签名推断「前端已对齐」**结论正确**。定时走查纠正过时认知,避免「以为没修催着修」。
|
||||
|
||||
---
|
||||
|
||||
## 新发现 P0(后端代理 ②)
|
||||
|
||||
### B-260615-35 — broadcast `Lagged` 兜底仅 warn,关键终态事件丢失时前端永久卡死
|
||||
|
||||
**位置**:`src-tauri/src/commands/workflow.rs:90-110`(forward 任务循环)
|
||||
|
||||
**问题**:`run_workflow` 的 forward 任务靠 `finished = matches!(event, WorkflowCompleted | WorkflowFailed)` 判定退出。broadcast 滑动窗口 `Lagged(n)` 时丢 n 条最旧事件,代码注释**已识别风险**(「关键终态事件可能永久丢失」),但兜底措施只有 `tracing::warn!`,**没有主动从 DB 查终态或加 watchdog 超时**。若 Lagged 恰好丢了 WorkflowCompleted/Failed,forward 任务永远 `rx.recv().await` 等不到 finished,前端依赖事件关闭审批/完成弹窗的场景**永久卡死**,只能靠 DB 轮询兜底(实时性差)。
|
||||
|
||||
**修法(代理建议)**:`Err(RecvError::Lagged(n))` 分支后加被动超时/主动查 DB — 累计 lag 次数到阈值或单次 n 过大时,查 `WorkflowRepo::get_by_id(&forward_exec_id)` 看是否终态,若是则补 emit `workflow-event` 给前端并 break。需把 `state.db.clone()` 也 move 进 forward 任务(当前仅 executor spawn 持有 db)。
|
||||
|
||||
**优先级**:P0(低频但后果为「永久卡死」无自愈),开发者已知未做兜底。
|
||||
|
||||
---
|
||||
|
||||
## 新内容走查结果
|
||||
|
||||
### 1. TaskDetail.vue(362 行新文件)— DRY 复发,单文件质量良好
|
||||
|
||||
| # | 等级 | 发现 |
|
||||
|---|---|---|
|
||||
| ① | 🔴 | `.ai-md` 样式块**第 5 份重复**(CR-260615-09 复发):`:263-333` ~70 行与 ProjectDetail/Ideas/Knowledge 逐字符同,累计 ~350 行重复 CSS。TaskDetail 是抽公共样式(`src/styles/ai-md.css` 或 `composables/useMarkdownCss.ts`)最佳时机 |
|
||||
| ② | 🔴 | `renderedDescription` computed **第 4 份重复**(CR-260615-10 复发):`:116-119` `void mdReady.value` 技巧四处重复。建议 `useMarkdown` 增 `renderedRef()` 帮手收敛 |
|
||||
| ③-⑦ | 🟡 | 路由 id 空守 / 快切竞态(无序号/AbortController)/ 错误信息缺 `.message` 优先 / `projectApi.list()` 全量拉取仅解名 / 静态文案半 i18n 半硬编码 |
|
||||
| ✅ 亮点 | — | XSS(DOMPurify + escapeFallback 双层)/ `void mdReady` 响应式依赖捕获(注释明确 B-24 修复点)/ Promise.all 并发 / onMounted 不阻塞 load |
|
||||
|
||||
### 2. 后端提交 `2de0c6e`(16 文件 1418+/152-)— 工程化重构扎实,1 真实 P0 + 文档完善项
|
||||
|
||||
| # | 等级 | 位置 | 发现 |
|
||||
|---|---|---|---|
|
||||
| ① | 🔴 | stream_recv.rs:97-108(2de0c6e 版) | 注释虚假承诺「stop_notify 即时打断」,实际该提交无 stop_notify 分支。**working tree 已修** — `80b9243` 补了本地 Notify + 第 3 select 分支(:102-154 现状),无需记 todo |
|
||||
| ② | 🔴 | workflow.rs:90-110 | **见 B-260615-35**(broadcast Lagged 终态丢失) |
|
||||
| ③ | 🟡 | stream_recv.rs:60-69 | `"timed out"` 分支**非死代码**(代理自查纠正初判:`"operation timed out"` 不含子串 `"timeout"`),可合并到首分支提升可读性 |
|
||||
| ④ | 🟡 | commands.rs:265-287 | `ai_chat_stop` 3s 兜底任务用闭包前快照 conv_id,会话切换后 emit 错对话(spawn 内重新读 active_conversation_id 配合 generating 判定) |
|
||||
| ⑤ | 🟡 | commands.rs:367/313/452 | tauri async cmd 内同步调 keyring IO 占 runtime worker,建议 `spawn_blocking`(Linux secret-service D-Bus 慢时影响明显) |
|
||||
| ⑥ | 🟡 | commands.rs:313-322 | `ai_list_providers` mask 后 api_key 字段语义模糊(`""`/`"sk-xxx"`/`"sk-1••••xyz9"` 三语义混一字段),建议拆 `has_api_key: bool` + `api_key_mask` |
|
||||
| ⑦ | 🟡 | secret.rs:51-64 | failcount sidecar 锚 `current_dir()` 不稳定(跨启动 cwd 变化致计数失效),建议改 `app_data_dir`(同 db_path) |
|
||||
| ⑧ | ⚪ | Cargo.toml + secret.rs | keyring noop backend 自检缺失,Linux 漏 feature 时 set 静默成功 get 永空 → 兜底误判「已迁移」清 DB api_key 致密钥永久丢失 |
|
||||
| ⑨ | ⚪ | agentic.rs:317-361 | 达 MAX_AGENT_ITERATIONS 后先 emit AiError 再 emit AiCompleted,前端 UX 处理依赖实现 |
|
||||
| ⑩ | ⚪ | workflow.rs:213 | `let _ = &app;` 显式占位,直接删 app 参数更清晰(Tauri 自动重生成路由) |
|
||||
| ✅ 亮点 | — | B-09 GeneratingGuard RAII 收敛复位(Drop 兜底 panic/异常路径)/ B-11 stale loop 一致性校验(会话切换旧 loop 自动退出)/ stream_recv 错误诊断体系(char 迭代修 UTF-8 边界 panic + 11 单测)/ secret.rs keyring 优先+DB 兜底双源 / tool_registry 双层校验防 symlink 逃逸 + 单次 open 消 TOCTOU |
|
||||
|
||||
---
|
||||
|
||||
## 其他确认
|
||||
|
||||
- **aiShared.ts 破环 ✅ 亮点**(ARC-260615-06 落地):`nextMsgId` 下沉 aiShared,useAiStream 注释明确「本模块不再 import useAiEvents,环消除」。4 composable 共享同一计数器,边界严格(仅下沉「无依赖、被多 composable 共用」成员)。
|
||||
- **stream_recv.rs working tree 已补 stop_notify ✅**(:102-154 三分支 select! 含 stop_notify 即时打断)。2de0c6e 提交自带的注释虚假承诺问题,已被后续 `80b9243` 修代码侧,**当前 working tree OK,无需记 todo**。
|
||||
|
||||
---
|
||||
|
||||
## 📊 本轮摘要
|
||||
|
||||
| 类别 | 数 | 代表 |
|
||||
|---|---|---|
|
||||
| P0 状态复核 | 3 | B-34 已修 ✅(纠正过时)/ B-32 未修 / B-33 未修 |
|
||||
| 新发现 P0 | 1 | B-260615-35 broadcast Lagged 终态丢失 |
|
||||
| 🔴 DRY 复发 | 2 | CR-09(四→五份)/ CR-10(第四份)|
|
||||
| 🟡 建议 | 12 | TaskDetail 5 + 后端 5 + stream_recv 1 + workflow 1 |
|
||||
| ⚪ 可选 | 4 | 后端 3 + TaskDetail 1 |
|
||||
| ✅ 亮点 | 4 | aiShared 破环 / GeneratingGuard RAII / stale loop 校验 / stream_recv 错误诊断 |
|
||||
|
||||
**本轮价值**:纠正 1 个过时 P0 判断(B-34 已修)、坐实 2 个 P0 仍未修(B-32/33)、新增 1 个真实 P0(B-35)、确认 1 个架构亮点落地(aiShared 破环)。无运行时 panic/数据损坏/并发竞争类硬伤,后端重构质量评级**良**。
|
||||
|
||||
todo 映射:B-260615-35(新增);CR-260615-09 描述更新(四→五份);B-260615-34 标 ✅;B-260615-32/33 加复核标注。
|
||||
123
docs/05-代码审查/定时走查-2026-06-15-第2轮.md
Normal file
123
docs/05-代码审查/定时走查-2026-06-15-第2轮.md
Normal file
@@ -0,0 +1,123 @@
|
||||
# 定时代码走查 — 第 2 轮(2026-06-15)
|
||||
|
||||
> 触发:定时走查 cron(每 30 分钟)。本轮范围 = 自上次走查起新增 6 提交(`fddca9d`/`892a642`/`19d64fc`/`d809cf4`/`8710d6c`/`06a2dea`)前端改动 + 工作区未提交(`types.ts`/`project.ts` M)。
|
||||
> 方法:3 路后台代理并行(A:ARC-05 store 拆分 / B:B-22 状态同步含 B-32/33 核对 / C:CR-11 健壮性+工作区未提交)+ 主代理地面核对(`useAiStream.ts`/`useAiSend.ts`/`workflow.rs`/`f93b758` 提交)。
|
||||
> 性质:核对记录,不改产品代码(本会话 session-role-diagnose-only)。todo 待修项见 `docs/todo.md`。
|
||||
> 关联:[定时走查-2026-06-15-P0复核.md](./定时走查-2026-06-15-P0复核.md)(第 1 轮,B-32/33 结论被本轮纠正)。
|
||||
|
||||
---
|
||||
|
||||
## 核心结论:3 前端 P0 全修(第 3 次纠正过时认知)
|
||||
|
||||
| P0 | 第1轮判断 | 第2轮地面复核 | 提交 | 处置 |
|
||||
|---|---|---|---|---|
|
||||
| **B-34** selectType | 已修 ✅ | 已修 ✅(确认) | — | 保持 ✅ |
|
||||
| **B-32** 队列收尾 | 「未修」 | **已修 ✅** | `f93b758` | todo 标 ✅,纠正第1轮 |
|
||||
| **B-33** 骨架屏 | 「未修」 | **已修 ✅** | `f93b758` | todo 标 ✅,纠正第1轮 |
|
||||
|
||||
**B-32/B-33 地面证据**(`useAiStream.ts` 当前 81 行,第1轮 Read 时为 73 行旧版):
|
||||
- `:28` `state.queue = [] // B-32:超时收尾同步清队列` + useAiEvents.ts:225(AiError) + useAiSend.ts:152(stopChat)/124(approveToolCall catch) **四路径全清队列**
|
||||
- `:43-45` onStreamTimeout 单遍反向扫描 running toolCall → rejected(合并探测 completed 为单次 O(n),实现干净)
|
||||
|
||||
**B-32/B-33 修复提交**:`f93b758`「修复: B-32+33 流式收尾清队列+回滚 running toolCall」3 文件 +18/-7。
|
||||
|
||||
> 第1轮「B-32/33 未修」为过时快照(working tree 当时旧版,与 B-34 同根因)。**定时走查连续 3 轮纠正过时 P0 判断**(B-34 → B-32 → B-33),说明走查必须以地面 Read 为准,不可信赖记忆/旧快照。前端 3 P0 至此全部闭环。
|
||||
|
||||
---
|
||||
|
||||
## 新发现 P0:AR-11 前端 listener 永不 attach(功能半接通死代码)
|
||||
|
||||
**位置**:`src/stores/project.ts:31-55`(定义)+ `:109-110`(export)+ `src/App.vue:203-213`(onMounted 未挂载)
|
||||
|
||||
**根因**:工作区未提交改动新增 `startDataChangedListener`/`stopDataChangedListener` 两函数 + export,但**全项目零调用方**(grep 确认)。后端 `emit_data_changed`(audit.rs:259-270)+ 自动执行路径(audit.rs:351)+ 审批路径(commands.rs:175)三处 emit 已就位且逻辑正确,事件 `df-data-changed` 正常发出,但前端 `listen('df-data-changed', …)` 永不执行 — 监听器从未 attach。
|
||||
|
||||
**影响**:AR-11「数据变更联动刷新」功能**完全失效**。用户通过 AI 工具(create/update/delete project/task/idea)改数据后,列表不自动刷新,仍需手动刷新(正是 AR-11 要消除的痛点)。后端 emit 成死事件。
|
||||
|
||||
**修复方向**:`App.vue` onMounted 调 `await projectStore.startDataChangedListener()`,onUnmounted 调 `stopDataChangedListener()`(对齐 `ProjectDetail.vue:463/470` 的 workflow listener 生命周期模式,数据变更可能来自任意页面)。
|
||||
|
||||
**性质**:工作区未提交半成品(非历史遗留,非 ARC-05 拆分收尾)。代理 C 确认 diff 是纯 AR-11 新增。
|
||||
|
||||
> 注:todo:77 AR-11 条目原标「暂缓」,第2轮复核更正为「后端已实施 ✅,前端半接通 🔴」。
|
||||
|
||||
---
|
||||
|
||||
## ARC-05 上帝 store 拆分 — 质量评级:优
|
||||
|
||||
**提交** `8710d6c`:`project.ts` 362→barrel + 5 子 store(state/projects/tasks/ideas/workflow)。
|
||||
|
||||
**3 项硬指标全部兑现**:
|
||||
| 指标 | 证据 |
|
||||
|---|---|
|
||||
| barrel 零改动兼容 | 逐字段比对 8710d6c~1 与 8710d6c 的 `return reactive({...})`,37 key 完全一致(8 getter state + clearError + 9 project + 4 task + 6 idea + 7 workflow actions + pendingApproval/stats computed)。state getter 用 `get projects() { return state.projects }` 保留「避免 loadXxx 重赋值后视图为空」关键设计 |
|
||||
| state 真单例 | state.ts 模块级 `export const state = reactive({...})`,ESM 保证全 app 单实例,4 子 store import 同一引用 |
|
||||
| 依赖图无环 | 4 子 store 互不 import,只依赖叶子 `./state`;barrel 单向依赖子 store,无循环/无初始化顺序陷阱 |
|
||||
|
||||
**🟡 建议 3**:
|
||||
1. `state.ts:32-37` 可变 `let _eventUnlisten` 跨模块共享 + setter,依赖 ESM live-binding(运行时正确但反直觉),建议注释点明或并入 reactive
|
||||
2. **AR-11 监听器已侵入 barrel**(working tree project.ts:31-55),违反「纯 barrel」定位 — 建议抽第 6 子 store `project/dataChange.ts`(与上面 AR-11 P0 同源)
|
||||
3. `projects.ts:105` clearError 夹在 projects return 语义错位(属 state 层),建议移除(barrel 已独立从 state 导入)
|
||||
|
||||
**⚪ 可选 2**:注释「四子 store」实为 5 文件(四领域+共享 state)/ createXxx 入参类型内联可抽 types。
|
||||
|
||||
**✅ 亮点**:越层 invoke 下沉彻底(`approve_human_approval`/`cancel_workflow_node` 下沉 `api/workflow.ts:39-54`,B-34 snake_case 对齐在 api 层统一)/ ProjectStore 类型 + 单例 `_storeInstance` 保留 / view 端 7 处 `useProjectStore` 零改动。
|
||||
|
||||
---
|
||||
|
||||
## B-22 前后端状态同步 — 实现质量良好
|
||||
|
||||
**提交** `d809cf4`:`ai_is_generating` IPC(commands.rs:12)+ sendMessage 预检(useAiSend.ts:27)。
|
||||
|
||||
**设计对症**:双源(前端 streaming / 后端 generating)各自维护确有不同步风险,发送前查后端真值对症。
|
||||
|
||||
**竞态窗口可接受**:`useAiSend.ts:42` 查后端与 `:97` 实际 sendMessage 之间有窗口,但后端 `ai_chat_send:57` 原子检查+占用(`if session.generating { Err }` + `generating=true` 同锁内)是真兜底。前端预检仅优化 UX(提前入队而非发出去被拒),降级路径完备(IPC 失败 catch 退化为原 streaming 预检)。
|
||||
|
||||
**回归安全**:正常事件流(delta/tool/AiAgentRound/AiCompleted/AiError)未改,仅 sendMessage 入口前置预检,不影响 handleEvent。
|
||||
|
||||
**🟡 Issue 1(低)**:`useAiSend.ts:53-57` 入队分支复位 `streaming=true` 但**未启动看门狗**(resetStreamWatchdog 只在 :89 正常发送路径调)。极端卡死场景(后端既不 emit、Drop spawn 又未执行)streaming 可能永真。建议入队分支也调一次 resetStreamWatchdog,代价一行。非 d809cf4 新引入回归(onStreamTimeout 本就依赖正常路径启动的看门狗)。
|
||||
|
||||
**信息 Issue 2**:`useAiSend.ts:60-61` 注释「streaming=true 但后端 false」描述与实际复位方向(onStreamTimeout/AiError 复位 streaming=false)有歧义,建议改注释。
|
||||
|
||||
---
|
||||
|
||||
## CR-11 健壮性批(fddca9d)— ✅5 亮点 + 1 死逻辑副产品
|
||||
|
||||
**子项 ⑦⑧⑨⑩⑪⑫ 全部实施**:
|
||||
|
||||
**✅ 5 亮点**:
|
||||
1. `useAiConversations.ts:77-93` JSON.parse 逐条 try/catch(原单条坏 args 清空整对话 → 单条降级空对象)
|
||||
2. `useAiEvents.ts:130-133` AiHeartbeat 显式 case 防 TS 穷举穿透
|
||||
3. `useAiSend.ts:84-85` 复用 findToolCall(消除内联 flatMap+find 重复 + 反向扫描 O(1) 均)
|
||||
4. `ToolCard.vue:236-242` argString 去 3 处 `as any`,`Record<string,unknown>` 类型收窄
|
||||
5. `workflow.ts:44-92` approveHumanApproval 签名收敛 `(decisions: string[], comment?)`(消除单/多选调用方歧义)
|
||||
|
||||
**⚠️ ⑪ 实施引入死逻辑(CR-260615-18)**:`workflow.ts:67-68` `decision = selectType === 'multiple' ? decisions[0] ?? '' : decisions[0] ?? ''` 两分支返回值完全相同,三元无意义。后端 workflow.rs:291-296 有兜底。修:直接 `const decision = decisions[0] ?? ''`。
|
||||
|
||||
**🟡 其他**:CR-19(action 字段 emit 但前端不消费,契约冗余)/ CR-20(stopDataChangedListener try/catch 过度防御,与 workflow.ts:108 不一致)。
|
||||
|
||||
---
|
||||
|
||||
## P0 汇总(仍未修)
|
||||
|
||||
| ID | 问题 | 状态 |
|
||||
|---|---|---|
|
||||
| B-260615-35 | broadcast Lagged 兜底仅 warn → 终态事件丢失前端永久卡死(后端 workflow.rs:90-110) | **未修**(第1轮发现,本轮确认仍在)|
|
||||
| AR-11 前端 | listener 永不 attach(App.vue 未挂载) | **未修**(本轮新发现,工作区半接通)|
|
||||
|
||||
---
|
||||
|
||||
## 📊 本轮摘要
|
||||
|
||||
| 类别 | 数 | 代表 |
|
||||
|---|---|---|
|
||||
| P0 全修确认 | 3 | B-34(保持✅)/ B-32 / B-33(第1轮过时,本轮纠正)|
|
||||
| 新发现 P0 | 1 | AR-11 前端 listener 永不 attach(功能半接通)|
|
||||
| 仍存 P0 | 2 | B-35 broadcast Lagged(后端)/ AR-11 前端 attach |
|
||||
| ✅ 质量肯定 | 2 | ARC-05 store 拆分(优)/ CR-11 健壮性批(5 亮点)|
|
||||
| 🟡 建议 | 6 | ARC-05 三项 / B-22 看门狗 / CR-18 死逻辑 / CR-19·20 |
|
||||
| ⚪ 可选 | 4 | ARC-05 两项 / ToolCard 兜底 |
|
||||
|
||||
**本轮价值**:第 3 次纠正过时 P0 判断(B-32/33 实已修,f93b758),前端 3 P0 全闭环;新发现 AR-11 前端 listener 永不 attach(工作区半接通死代码,功能未闭环);肯定 ARC-05 拆分(3 硬指标全兑现)与 CR-11 健壮性批质量。
|
||||
|
||||
**方法论警示**:连续 3 轮纠正过时判断(B-34→B-32→B-33),根因是第1轮 Read 到 working tree 旧版(73 行)而 HEAD 已是新版(81 行)。**走查必须每次地面 Read,不可信赖上轮快照**。后续走查代理 prompt 应强调「以当前 working tree + HEAD 为准,git log 看提交,勿用记忆」。
|
||||
|
||||
todo 映射:B-32/33 标 ✅ + 复核更正 / AR-11(todo:77)状态更新 / CR-11(todo:212)标 ✅ / 新增 CR-260615-18/19/20。
|
||||
70
docs/05-代码审查/定时走查-2026-06-15-第3轮.md
Normal file
70
docs/05-代码审查/定时走查-2026-06-15-第3轮.md
Normal file
@@ -0,0 +1,70 @@
|
||||
# 定时代码走查 — 第 3 轮(2026-06-15)
|
||||
|
||||
> 触发:定时走查 cron(每 30 分钟)。范围 = 自第2轮起新增 3 提交(`dc27e79` AR-11 / `b08adcb` B-35 / `f574562` CR-18·19),工作区 src/ 干净。
|
||||
> 方法:主代理地面 git show 3 提交 + Read 关键文件核对修复质量/回归(修复集中、范围明确,无需派代理)。
|
||||
> 性质:核对记录,不改产品代码(本会话 session-role-diagnose-only)。todo 待修项见 `docs/todo.md`。
|
||||
> 关联:[第1轮(P0复核)](./定时走查-2026-06-15-P0复核.md)、[第2轮](./定时走查-2026-06-15-第2轮.md)。
|
||||
|
||||
---
|
||||
|
||||
## 核心结论:走查价值闭环
|
||||
|
||||
接收方(另一会话)据第2轮走查修复了全部发现项 + 核对撤销 1 误判:
|
||||
|
||||
| 项 | 发现轮 | 第3轮核对 | 提交 |
|
||||
|---|---|---|---|
|
||||
| AR-11 listener 永不 attach | 第2轮 🔴 P0 | ✅ **已修**(App.vue:218 attach) | dc27e79 |
|
||||
| B-35 broadcast Lagged 兜底 | 第1轮 🔴 P0 | ✅ **已修**(分支完整健壮) | b08adcb |
|
||||
| CR-18 decision 死逻辑 | 第2轮 🟡 P2 | ✅ **已修**(删三元) | f574562 |
|
||||
| CR-19 action 注释 | 第2轮 🟡 P3 | ✅ **已修**(注释明确,保留预留) | f574562 |
|
||||
| CR-20 stop try/catch 过度防御 | 第2轮 🟡 P3 | ✅ **撤销(走查误判)**:接收方核 workflow.ts:108 一致 | f574562 commit 说明 |
|
||||
|
||||
> **前端 P0 全闭环**:B-32 / B-33 / B-34 / B-35 / AR-11 全部已修。
|
||||
> **走查↔接收方双向校验有效**:接收方不仅修复,还核对出 CR-20 为走查误判(workflow.ts:108 stopEventListener 确有 try/catch,写法一致),主动撤销 — 说明走查质量经得起反向核对。
|
||||
|
||||
---
|
||||
|
||||
## 各修复核对
|
||||
|
||||
### AR-11 listener attach(dc27e79)✅
|
||||
- `App.vue:218` onMounted `await projectStore.startDataChangedListener()` + `:258` onUnmounted `projectStore.stopDataChangedListener?.()`
|
||||
- barrel `listen<DfDataChangedPayload>('df-data-changed')` 按 entity 调 loadProjects/loadTasks/loadIdeas
|
||||
- 后端 audit.rs `data_change_for_tool` 映射(create_project/idea/task→create,update_*→update,delete_*/purge→delete,restore/bind_directory→update)+ emit_data_changed + commands.rs ai_approve 成功路径 emit
|
||||
- listener 单例(`if (_dataChangedUnlisten) return`)防重复 attach ✅
|
||||
- **功能闭环**:AI 工具 create/update/delete 后前端列表自动刷新
|
||||
|
||||
### B-35 broadcast Lagged 兜底(b08adcb)✅
|
||||
- `workflow.rs` `forward_db = state.db.clone()` move 进 forward 任务(第1轮建议)
|
||||
- `lagged_total` 累计达 `LAGGED_PROBE_THRESHOLD=8` 查 `WorkflowRepo::get_by_id` 终态
|
||||
- completed/failed/cancelled 补 emit workflow-event + break;合成事件 total_duration_ms/failed_node 占位(注释说明 broadcast 不暴露原字段,DB 为准)
|
||||
- **分支完整健壮**:Ok(Some) 终态→break / running→重置 lagged_total 继续等 / Ok(None)→warn 重置等 / Err(DB)→warn 重置等 / Closed→break
|
||||
- 第1轮建议方案原样落地
|
||||
|
||||
### CR-18/19/20(f574562)✅
|
||||
- **CR-18**:`workflow.ts:67` decision 三元删除 → `const decision = decisions[0] ?? ''`
|
||||
- **CR-19**:action 注释明确「为未来 delete 本地移除等差异化刷新预留,当前未消费统一 load 全量」
|
||||
- **CR-20 撤销**:提交说明「经核查与 workflow.ts:108 stopEventListener 写法一致(走查误判,不修)」
|
||||
|
||||
---
|
||||
|
||||
## 新发现(1 轻量)
|
||||
|
||||
### CR-260615-21 🟡 P3 — App.vue:218 await 无 try/catch
|
||||
`App.vue:218` `await projectStore.startDataChangedListener()` 无 try/catch。Tauri `listen` 失败(极罕见)会 reject onMounted。虽已是 onMounted 末尾(前面 appSettings/migrate/theme/i18n/keydown/loadCandidates 已跑,app 不崩),但 AR-11 静默失效 + Vue 控制台 warn。修:包 `try { await ... } catch(e){ console.error(...) }`。非阻塞,极低概率。
|
||||
|
||||
---
|
||||
|
||||
## 📊 本轮摘要
|
||||
|
||||
| 类别 | 数 | 代表 |
|
||||
|---|---|---|
|
||||
| 价值闭环(修复确认) | 4 | AR-11 / B-35 / CR-18 / CR-19 全到位 |
|
||||
| 撤销误判 | 1 | CR-20(接收方核对 workflow.ts:108 一致)|
|
||||
| 前端 P0 全闭环 | 5 | B-32/33/34/35/AR-11 全修 |
|
||||
| 新发现 | 1 | CR-21 App.vue try/catch(P3 轻量)|
|
||||
|
||||
**本轮价值**:确认前两轮走查发现被接收方完整修复(含 1 项撤销误判),前端 P0 全闭环。走查↔接收方双向校验机制有效(接收方主动核对出 CR-20 误判并撤销)。本轮仅 1 项 P3 轻量新发现,代码质量趋稳。
|
||||
|
||||
**方法论肯定**:第3轮严格执行「地面 git show + Read 核对」,未重复走查已审内容(前两轮已覆盖的 ARC-05/B-22/CR-11 主体不再重审,只核对修复),符合「跳过已审避免重复」。
|
||||
|
||||
todo 映射:B-35/CR-18/19/20/AR-11 标 ✅(CR-20 标撤销误判)+ CR-21 新增。
|
||||
89
docs/05-代码审查/定时走查-2026-06-15-第4轮.md
Normal file
89
docs/05-代码审查/定时走查-2026-06-15-第4轮.md
Normal file
@@ -0,0 +1,89 @@
|
||||
# 定时代码走查 — 第 4 轮(2026-06-15)
|
||||
|
||||
> 触发:定时走查 cron(每 30 分钟)。范围 = 自第3轮起 1 新提交(`94cdcdc` CR-21 修复)+ **i18n 垂直切片走查**(src/i18n 26 文件此前从未深查,最大未审区域)。
|
||||
> 方法:主代理地面 git show 核 CR-21 + 1 路后台代理走查 i18n 完整性/硬编码遗漏(45 tool_uses 全量扫)。
|
||||
> 性质:核对记录,不改产品代码(本会话 session-role-diagnose-only)。todo 待修项见 `docs/todo.md`。
|
||||
> 关联:[第1轮](./定时走查-2026-06-15-P0复核.md)/[第2轮](./定时走查-2026-06-15-第2轮.md)/[第3轮](./定时走查-2026-06-15-第3轮.md)。
|
||||
|
||||
---
|
||||
|
||||
## 一、CR-21 修复确认(价值第 4 次闭环)
|
||||
|
||||
`94cdcdc` App.vue:218-222 `try { await startDataChangedListener() } catch(e){ console.error(...) }`。提交说明「cron 巡检捕获另一会话走查第3轮 CR-21」。✅ 已修。
|
||||
|
||||
---
|
||||
|
||||
## 二、i18n key 树质量 — 优秀 ✅
|
||||
|
||||
**466 key 全对齐**(12 命名空间 × 2 语言):
|
||||
|
||||
| 维度 | 结果 |
|
||||
|---|---|
|
||||
| zh vs en key 树 | **0 缺 key / 0 多余 / 0 结构不一致** |
|
||||
| 翻译质量 | 0 TODO/占位符,0 复制中文漏译(仅 `aiLanguageZh=🇨🇳 简体中文` 1 处合理) |
|
||||
| 插值参数 | `{var}` 占位符 zh/en 逐一匹配,0 mismatch |
|
||||
| 命名/嵌套 | 顶层=文件名,camelCase,嵌套≤2 层,一致 |
|
||||
|
||||
> i18n 文件本身无需改动。问题全在 **src/ 硬编码遗漏**。
|
||||
|
||||
---
|
||||
|
||||
## 三、硬编码遗漏 — 40+ 处(CR-08 扩展,真实问题)
|
||||
|
||||
CR-260615-08 原记 3 处,i18n 代理全量扫发现 **40+ 处**,按严重度分三档:
|
||||
|
||||
### 🔴 P1 — 直接破坏 i18n(已有 key 却硬编码)
|
||||
|
||||
**P1-1 `useAiEvents.ts:182`(最高优先,改一行)**:`tc.result = '用户拒绝了此操作'` 硬编码,绕过已有 `aiTool.rejectedHint`(en=`User rejected this action`)。ToolCard.vue:54 渲染 `tc.result || $t('aiTool.rejectedHint')`,`||` 短路致 **en locale 拒绝提示恒中文**,已有 en 翻译被一行废掉。修:删该赋值(让 ToolCard 走 t())或改 `t('aiTool.rejectedHint')`。
|
||||
|
||||
**P1-2 `TaskDetail.vue` 整页 13 处未接 i18n**:L6/12/17/24 + 11 个 label span + L144 errorMsg。i18n 无 taskDetail namespace(只有 tasks),需新建。en locale 全中文。
|
||||
|
||||
**P1-3 `ProjectDetail.vue` 审批/导入 5 处**:L12 导入目录 / L201 确认(N) / L220 取消 / L392 confirmDialog / L399 Message.success / L404 Message.error / L381 未知错误 fallback。
|
||||
|
||||
**P1-4 `Dashboard.vue:76`** `暂无项目,去创建一个`。
|
||||
|
||||
**P1-5 `AiChat.vue` 3 处**:L107 title=清空 / L471 confirmDialog / L687 showToast 发送失败。
|
||||
|
||||
### 🟡 P2 — JS 字符串赋 UI/Toast
|
||||
|
||||
**P2-1 `ToolCard.vue:152-164` ARG_LABEL_MAP**:11 个审批参数标签硬编码(ID/项目 ID/名称/标题/描述/字段/新值/路径/优先级/标签/来源),aiTool.ts 无对应 key,需新增 `aiTool.argLabels.*`。
|
||||
|
||||
**P2-2 store error fallback 11 处全中文**:knowledge.ts:53/64/74/105 + ideas.ts:10/20/38 + projects.ts:13/25/37/63/71/81/90 + tasks.ts:10/20/38 + workflow.ts:19。en locale 下所有加载/CRUD 失败 toast 中文。
|
||||
|
||||
**P2-3 `useAiStream.ts:52-53`**(CR-08 行号偏差,原记 39-41):看门狗超时两文案 `⚠ 工具已执行完成...` / `⚠ 响应中断...` 直接 push messages。
|
||||
|
||||
**P2-4 `useAiSend.ts:49/65/130`**:队列满 throw + 审批未送达 tc.result。
|
||||
|
||||
### ⚪ P3 — 边界/低优先
|
||||
|
||||
**P3-1 `utils/time.ts:46-51`(隐蔽)**:`formatRelative` 相对时间硬编码(刚刚/X 分钟前...)。i18n 已有 `common.justNow/minutesAgo/hoursAgo/dayAgo` key 却没用 → en locale 所有时间戳中文。
|
||||
|
||||
**P3-2 `router/index.ts:18-60`**:8 个 `meta.title` 中文,但 **0 消费方**(grep meta.title/document.title 无匹配),死代码/潜伏隐患。
|
||||
|
||||
**P3-4 `i18n/index.ts:20` `fallbackLocale: 'en'`**:zh-CN 用户缺 key 回退英文,反直觉(当前 key 全对齐不触发,设计气味)。
|
||||
|
||||
---
|
||||
|
||||
## 四、亮点
|
||||
|
||||
- **i18n key 树工程化扎实**:466 key 双语全对齐、插值参数一致、命名规范、无占位符 — 国际化基础设施质量高。
|
||||
- **i18n index 初帧渲染处理合理**:appSettings 异步,`getInitialLocale()` 默认 zh-CN,loadAll 后回填,已知短暂态有注释。
|
||||
- **TaskDetail.vue 单文件 XSS/响应式到位**(第1轮已肯定),i18n 是唯一短板。
|
||||
|
||||
---
|
||||
|
||||
## 📊 本轮摘要
|
||||
|
||||
| 类别 | 数 | 代表 |
|
||||
|---|---|---|
|
||||
| 价值闭环 | 1 | CR-21 → 94cdcdc ✅ |
|
||||
| i18n key 树肯定 | 466 | 全对齐 0 缺 key(优秀)|
|
||||
| 硬编码新发现 | 40+ | P1-1 useAiEvents:182(改一行最高 ROI)/ TaskDetail 整页 / ToolCard ARG_LABEL_MAP / store fallback 11 处 / time.ts 隐蔽 |
|
||||
| P1 i18n 破坏 | 5 | useAiEvents:182 / TaskDetail / ProjectDetail / Dashboard / AiChat |
|
||||
| P2/P3 | 6 | ARG_LABEL_MAP / store fallback / useAiSend / time.ts / router meta / fallbackLocale |
|
||||
|
||||
**本轮价值**:i18n 垂直切片暴露真实问题 — key 树优秀但 src/ 硬编码 40+ 处(CR-08 严重低估)。**P1-1 useAiEvents:182 是明确 i18n 破坏 bug**(一行废掉 en 翻译),改一行最高 ROI,应优先。TaskDetail 整页未接 i18n(13 处)是新页面 i18n 债。
|
||||
|
||||
**方法论**:换垂直切片角度(i18n 维度)有效突破前几轮按提交/改动的横切盲区,发现 40+ 处硬编码 — 印证 task2/3「功能/模块垂直走查」的价值(换角度有新发现)。
|
||||
|
||||
todo 映射:CR-260615-08 扩展(3→40+ 子项,标 P1-1 最高优先)+ CR-21 标 ✅。
|
||||
94
docs/05-代码审查/定时走查-2026-06-15-第5轮.md
Normal file
94
docs/05-代码审查/定时走查-2026-06-15-第5轮.md
Normal file
@@ -0,0 +1,94 @@
|
||||
# 定时代码走查 — 第 5 轮(2026-06-15)
|
||||
|
||||
> 触发:定时走查 cron(每 30 分钟)。范围 = **api 层垂直切片**(9 文件,此前从未深查,IPC 契约边界)。无新提交(自第4轮起 src/ 干净)。
|
||||
> 方法:2 路后台代理(A:api invoke 契约层 / B:types 类型对齐层)+ 主代理地面核对 + **WebSearch 权威裁决**。
|
||||
> 性质:核对记录,不改产品代码(本会话 session-role-diagnose-only)。todo 待修项见 `docs/todo.md`。
|
||||
> 关联:[第1-4轮](./)。
|
||||
|
||||
---
|
||||
|
||||
## 🚨 核心发现:Tauri v2 camelCase 行为权威裁决(代理矛盾 → 纠正假阳性)
|
||||
|
||||
### 矛盾
|
||||
- **代理 A**:Tauri v2 **不转** camelCase,6 处 IPC 参数遗漏(ai_approve/ai_save_provider/knowledge_events/ai_set_concurrency_config/ai_set_provider/ai_pending_tool_calls)真 P0(B-34 同类)
|
||||
- **代理 B**:Tauri v2 **默认转** camelCase(宏生成别名),6 处全误判
|
||||
|
||||
### 权威裁决(WebSearch Tauri v2 官方文档 + StackOverflow)
|
||||
|
||||
> **Tauri v2 默认自动转换 snake_case(Rust)↔ camelCase(JavaScript)**。
|
||||
> Rust `#[tauri::command] fn greet(tool_call_id: String)` ↔ JS `invoke('greet', { toolCallId })` 自动匹配。
|
||||
> **除非** `#[tauri::command(rename_all = "snake_case")]` 才需 JS 传 snake_case。
|
||||
|
||||
项目地面核对:`grep rename_all src-tauri/` 仅 `state.rs:30`(`KnowledgeConfig` 的 serde,**非 command 参数**),**无任何 `#[tauri::command]` 用 rename_all** → 全部走默认(camelCase 转换)。
|
||||
|
||||
### 结论
|
||||
|
||||
**代理 B 对,代理 A 6 处全误判(假阳性)**:
|
||||
- `api/ai.ts:15` `{ toolCallId }` → 后端 `tool_call_id` ✅ 自动转换,工作
|
||||
- `api/ai.ts:20` `{ convId }` / `:47-54` `{ providerType/baseUrl/apiKey/defaultModel }` / `:59/64` `{ providerId }` / `:69` `{ globalLimit/perConvLimit }` → 全 ✅
|
||||
- `api/knowledge.ts:80` `{ knowledgeId/eventType }` → ✅
|
||||
|
||||
camelCase 和 snake_case **都工作**(项目混用:ai.ts 用 camelCase,workflow.ts 用 snake_case,均正常)。
|
||||
|
||||
---
|
||||
|
||||
## 🔑 根因:B-34 错误注释传播误导
|
||||
|
||||
**`src/stores/project.ts:273`** B-34 注释「Tauri 2 IPC 不转 camelCase,须 snake_case 对齐后端 workflow.rs:211」**与权威矛盾**(实际转)。
|
||||
|
||||
- 代理 A 据此错误注释判 6 处假阳性 P0(认为 camelCase 失效)
|
||||
- B-34 实际「修复」(selectType→select_type)是 **no-op**:camelCase 本工作,snake_case 也工作,两种都接受。改了无害,但**注释错误传播误导**
|
||||
- 若「多选审批静默失效」曾为真症状,根因**非 selectType 命名**,需复核(camelCase 转换正常)
|
||||
|
||||
> **本轮最高价值**:揭露 B-34 技术债(错误注释),避免后续维护者/走查代理据此做无用「修复」(代理 A 已踩坑)。
|
||||
|
||||
---
|
||||
|
||||
## 代理 B 发现(types 对齐)— 良好,4 项低风险
|
||||
|
||||
**字段名/可选/枚举/响应包装全对齐**(无 P0 undefined 风险):7 Record struct(Idea/Project/Task/Workflow/AiProvider/Knowledge/KnowledgeEvent)字段名全 snake_case 对齐,Option↔可选对齐,AiChatEvent/WorkflowEvent union 变体对齐。
|
||||
|
||||
**4 项低风险**(CR-260615-22):
|
||||
1. `TaskRecord.status`(types.ts:88)注释 `review_ready/merged/abandoned` **错**,后端 TaskStatus(types.rs:165)= `todo/in_progress/in_review/testing/done/blocked/cancelled`
|
||||
2. `IdeaRecord.status`(:11)注释漏 `archived`(IdeaStatus 6 变体)
|
||||
3. `ProjectRecord.status`(:39)注释漏 `testing/releasing`(ProjectStatus 7 变体)
|
||||
4. `AiConversationSummary.models`(:233)冗余(后端返但前端无消费点)
|
||||
|
||||
> status 字段类型是 `string`(非 literal union),注释不阻断运行时,仅误导维护者。
|
||||
|
||||
---
|
||||
|
||||
## ✅ 亮点
|
||||
|
||||
- **api/types.ts 类型对齐质量高**:7 Record struct + union 类型全对齐,无字段漂移
|
||||
- **IPC 命令名零漂移**(代理 A 核 60+ 命令注册):无拼写错/未注册/已删残留
|
||||
- **越层下沉治理**:store 不直碰 invoke(workflow.ts:7-8 注释),api 是唯一 IPC 边界
|
||||
- **workflow.ts 契约自觉**:虽「不转 camelCase」判断有误,但 snake_case 风格统一 + options 透传注释(R-PD-5)是好的防回归实践
|
||||
|
||||
---
|
||||
|
||||
## 📊 本轮摘要
|
||||
|
||||
| 类别 | 数 | 代表 |
|
||||
|---|---|---|
|
||||
| 权威裁决 | 1 | Tauri v2 默认转 camelCase(代理 B 对)|
|
||||
| 假阳性纠正 | 6 | 代理 A IPC camelCase 遗漏全误判 |
|
||||
| 根因澄清 | 1 | B-34 错误注释传播误导(CR-23)|
|
||||
| 低风险新发现 | 4 | types 枚举注释错/漏 + 冗余字段(CR-22)|
|
||||
|
||||
**本轮价值**:多代理交叉验证暴露矛盾 → WebSearch 权威裁决 → 纠正 6 处假阳性(避免后续基于错误注释的无用修复)+ 揭露 B-34 注释技术债。
|
||||
|
||||
**方法论**:
|
||||
1. **走查须以权威行为为准(官方文档/实际运行),非代码注释** — 注释可能错误(如 B-34),代理 A 据错误注释判 6 处假阳性
|
||||
2. **多代理交叉验证有效** — 代理 A/B 结论矛盾触发权威裁决,单代理走查无此纠错机制
|
||||
3. **api 层垂直切片证实类型对齐质量高**(与前几轮横切互补)
|
||||
|
||||
todo 映射:**不记代理 A 6 处**(假阳性)+ CR-260615-22(types 注释/冗余 P3)+ CR-260615-23(B-34 注释修正 P2)。
|
||||
|
||||
---
|
||||
|
||||
## Sources
|
||||
|
||||
- [Calling Rust from the Frontend | Tauri v2](https://v2.tauri.app/develop/calling-rust/)
|
||||
- [Why does Tauri modify the parameter names of invoked functions? — Stack Overflow](https://stackoverflow.com/questions/78432685/why-does-tauri-modify-the-parameter-names-of-invoked-functions)
|
||||
- [Tauri Command Fundamentals — Tauri Tutorials](https://tauritutorials.com/blog/tauri-command-fundamentals)
|
||||
73
docs/05-代码审查/定时走查-2026-06-15-第6轮.md
Normal file
73
docs/05-代码审查/定时走查-2026-06-15-第6轮.md
Normal file
@@ -0,0 +1,73 @@
|
||||
# 定时代码走查 — 第 6 轮(2026-06-15)
|
||||
|
||||
> 触发:定时走查 cron(每 30 分钟)。范围 = **列表页 + Dashboard + AI 面板/窗口垂直切片**(此前从未深查)。1 新提交(CR-22 修复)。
|
||||
> 方法:2 路代理(A 列表页 / B Dashboard+AI窗口)+ 主代理地面核 stopListener。
|
||||
> 性质:核对记录,不改产品代码(本会话 session-role-diagnose-only)。todo 见 `docs/todo.md`。
|
||||
> 关联:[第1-5轮](./)。
|
||||
|
||||
---
|
||||
|
||||
## CR-22 ✅ + CR-23 撤销(价值第 5 次闭环)
|
||||
|
||||
- `16aeacc` CR-22 ✅:types.ts 3 枚举注释对齐后端(Task/Idea/Project status)+ 删 AiConversationSummary.models 冗余
|
||||
- CR-23 撤销(不适用):接收方核对 B-34「Tauri 不转」注释在 ARC-05 拆分时已简化(workflow.ts:81/project.ts:58 仅风格对齐无错误说明)
|
||||
|
||||
---
|
||||
|
||||
## 🔴 分离窗口生命周期(新功能首次深查,P1 核心)
|
||||
|
||||
**代理 B R1-R5**。地面核 `stopListener`(useAiEvents.ts:258) **全仓零调用**(grep App/AiChat/AiDetached onUnmounted/onBeforeUnmount 都不调)确认 R2。
|
||||
|
||||
### CR-260615-24 [P1] listener 双注册 + 永不清理(R1+R2)
|
||||
- **R1 双窗口重复消费 ai-chat-event**:后端 `app.emit`(stream_recv/agentic/commands/audit 多处)是**全局广播**发给所有 webview。主窗口(AiChat.vue:790)+ 分离窗口(AiDetached 渲染 AiChat)各注册 `listen('ai-chat-event')`,两窗口 handleEvent 都触发 → delta 各 `+=` 一次 / AiAgentRound 各 push 空气泡。当前靠 App.vue:94 `v-if="panelOpen && !detached"` detach 时卸载主窗口 AiChat 间接停,但 state 是模块级单例(handleEvent 仍写主窗口 state),**脆弱隐式契约**。
|
||||
- **R2 stopListener 零调用**:useAiEvents.ts:258 定义但全仓无调用点 → listener + watchdog 永久泄漏(startListener 幂等防重复注册,但卸载不释放)。
|
||||
- **边界影响**:reattach 重叠窗口期多一条空 assistant 消息(非崩溃/非数据损坏)。
|
||||
- **修**:AiChat.vue onBeforeUnmount 补 `store.stopListener()` + detach 时主窗口显式 stopListener / reattach startListener,或后端 `emit_to(label)` 定向。
|
||||
|
||||
### CR-260615-25 [P2] 分离窗口状态机缺口(R3/R4/R5)
|
||||
- **R3**:`win.once('tauri://destroyed')` 置 detached=false 但**不恢复 panelOpen=true**(只 reattachPanel 显式设)→ 用户直接 X 关分离窗口后面板消失,需手动 Ctrl+I
|
||||
- **R4**:`resumeInDetached`(useAiWindow.ts:79-95) 强行重建生成态(读 df-ai-gen 置 streaming=true + push 空气泡),与后端 `ai_is_generating` 真值可能失配 → 假气泡 / 130s 后 watchdog 假超时
|
||||
- **R5**:分离窗口 onMounted `switchConversation`(useAiWindow.ts:81) + loadConversations 内自动 switch,后端 **AiSession 单例** activeConversationId 被分离窗口覆盖 → 主窗口期间发消息可能落错会话(B-22 同类延伸)
|
||||
|
||||
---
|
||||
|
||||
## 🟡 列表页(代理 A)
|
||||
|
||||
### CR-260615-26 [P2] AR-11 + B-29 契约交互 + Tasks 空态(R-1/Y-2)
|
||||
- **R-1**:AR-11 监听器(project.ts:38)对 task 调 `loadTasks()` **无参 → 全量加载**,破坏 B-260615-29 建立的「store.tasks 反映当前筛选」契约。filteredGroups 二次过滤**兜住结果正确**,但 store.tasks 变全量(createTask push 到全量数组)+ 性能
|
||||
- **Y-2**:Tasks.vue:39-71 **无 loading/error/empty 兜底**,加载失败或无结果时空白页(store.error/loading 零引用)。对比 Projects.vue:107 至少有空态
|
||||
|
||||
### ✅ 列表页亮点
|
||||
- **Projects/Tasks i18n 100%**(grep 确认中文仅注释/console)— CR-08 范围内这两页**无硬编码遗漏**
|
||||
- Projects.vue useConfirm composable 抽离(DRY,消除 4 视图重复)
|
||||
- B-260615-29 筛选重载注释保留(可追溯)
|
||||
|
||||
---
|
||||
|
||||
## 🟡 Dashboard(代理 B)
|
||||
|
||||
### CR-260615-27 [P2] refresh 兜底 + stage 颜色 + 死代码(M1/M3/L4)
|
||||
- **M1**:refresh(Dashboard.vue:202-204)Promise.all **无 try/catch**,任一接口失败 unhandledrejection + 用户无反馈(onMounted 有 try/catch 但 refresh 无)
|
||||
- **M3**:getProjectStage(:150) status→stage 映射 **planning 归 coding stage(颜色紫)但 label planning**,颜色语义撒谎(cancelled 归 testing 同)
|
||||
- **L4**:决策/批注两 panel(:103-120) 永远空态,CSS(.decision-row/.anno-row :504-572) 完整但模板未用 = **死代码**
|
||||
|
||||
### ✅ Dashboard 亮点
|
||||
- **无图表库**,纯 CSS 进度条 + 数字 stat,GPU 友好动画,**无性能问题**
|
||||
- aiShared 破环正确(ARC-06),nextMsgId 两窗口各自独立不冲突
|
||||
|
||||
---
|
||||
|
||||
## 📊 本轮摘要
|
||||
|
||||
| 类别 | 数 | 代表 |
|
||||
|---|---|---|
|
||||
| 价值闭环 | 1 | CR-22 ✅ + CR-23 撤销 |
|
||||
| P1 新发现 | 1 | 分离窗口 R1+R2 listener 双注册+泄漏(CR-24)|
|
||||
| P2 新发现 | 3 | 分离窗口状态机(CR-25)/ 列表页契约+空态(CR-26)/ Dashboard(CR-27)|
|
||||
| 亮点 | 4 | 列表页 i18n 100% / Dashboard 纯 CSS 无性能问题 / aiShared 破环 / useConfirm DRY |
|
||||
|
||||
**本轮价值**:**分离窗口是新功能,前几轮横切(按提交/改动)完全没覆盖**,垂直切片(Dashboard+AI窗口维度)首次深查即暴露 P1(listener 泄漏)+ 状态机缺口。**再次印证 task2/3 垂直走查价值** — 横切收敛后,垂直切片是新发现来源(i18n/api/列表页/AI窗口 4 个垂直切片均挖出真问题)。
|
||||
|
||||
**方法论**:分离窗口 P1(R1+R2)经地面核 stopListener 零调用坐实(避免第5轮代理 A 假阳性教训),走查发现须地面验证。
|
||||
|
||||
todo 映射:CR-260615-24(P1 分离窗口 listener)/ CR-25(P2 状态机)/ CR-26(P2 列表页)/ CR-27(P2 Dashboard)。
|
||||
81
docs/05-代码审查/定时走查-2026-06-15-第7轮.md
Normal file
81
docs/05-代码审查/定时走查-2026-06-15-第7轮.md
Normal file
@@ -0,0 +1,81 @@
|
||||
# 定时代码走查 — 第 7 轮(2026-06-15)
|
||||
|
||||
> 触发:定时走查 cron(每 30 分钟)。范围 = **stores 独立 store 垂直切片**(ai/knowledge/appSettings/index,未审)+ 核 CR-24/26/27 修复。
|
||||
> 方法:主代理 git show 3 提交 + 1 路代理走查 stores。
|
||||
> 性质:核对记录,不改产品代码(本会话 session-role-diagnose-only)。todo 见 `docs/todo.md`。
|
||||
> 关联:[第1-6轮](./)。
|
||||
|
||||
---
|
||||
|
||||
## CR-24/26/27 ✅(价值第 6 次闭环)
|
||||
|
||||
| 提交 | CR | 修复 |
|
||||
|---|---|---|
|
||||
| ef2d033 | CR-24 ✅ | AiChat onBeforeUnmount 补 `store.stopListener()` + useAiEvents stopListener 补 `clearStreamWatchdog()`(卸载清 130s watchdog 避免写已卸载 state)|
|
||||
| 7637f29 | CR-26 ✅ | project.ts `_activeTaskProject` 三态(undefined 不触发/all 全量/具体 id)+ Tasks.vue 空态兜底(89 行)|
|
||||
| 5e6aa79 | CR-27 ✅ | Dashboard 删决策/批注死代码(-96 行)+ stage 颜色 + refresh try/catch |
|
||||
|
||||
**CR-25(分离窗口状态机 R3/R4/R5)仍存**(无提交提及,接收方未修或在做)。
|
||||
|
||||
---
|
||||
|
||||
## 🔴 knowledge error 通道断(P1,CR-08 深化)
|
||||
|
||||
### CR-260615-28 [P1] state.error 死字段 + create 语义矛盾
|
||||
- **state.error 死字段**:knowledge.ts:19,49,53,64,74,105 写入 `state.error`,但 **grep 全应用零消费**(Knowledge.vue store.error 零命中)→ 用户遭遇加载/检索/配置失败只看空白列表,无错误提示
|
||||
- **CR-08 深化**:CR-08「store error fallback 11 处中文」只记了硬编码,**整个 error 通道前端是断的**(写了从不读)— 比中文硬编码更严重
|
||||
- **create 语义矛盾**:create(:80-85) 成功后调 loadCandidates,若 loadCandidates 失败把错误塞 state.error,但 create 已返回 record 给调用方(认为成功)
|
||||
|
||||
**修**:Knowledge.vue 接 error banner/toast,或删 state.error 改 throw 由调用方 catch + create 内 loadCandidates 失败不污染 error。
|
||||
|
||||
---
|
||||
|
||||
## ✅ ai.ts 状态机完整(亮点,验证修复质量)
|
||||
|
||||
四核心态 `streaming/generatingConvId/currentText/queue` 在 **6 复位点闭环**,无卡死 streaming=true 缺口:
|
||||
- AiCompleted(useAiEvents:189-217)/ AiError(:220-235) + queue=[](B-32)
|
||||
- onStreamTimeout(useAiStream:25-28) + queue=[] + running→rejected(B-33)
|
||||
- sendMessage catch(useAiSend:99-105)/ approveToolCall catch(:121-133) + queue=[]
|
||||
- stopChat(:149-154) 本地先复位再发停止信号(不依赖后端 AiCompleted,防审批态看门狗已 clear 致 AiCompleted 竞态丢失)
|
||||
- 分离窗口 resumeInDetached(useAiWindow:111) 先 invoke('ai_is_generating') 核对(R4 防 df-ai-gen 残留假气泡)
|
||||
|
||||
> 验证:B-32/33/ef2d033 修复质量扎实,状态机无非法组合残留。
|
||||
|
||||
## ✅ appSettings 双源同步(亮点)
|
||||
|
||||
三态 `cache(reactive)↔SQLite↔ref`:
|
||||
- set 乐观写 cache + debounce 300ms 合并 + pendingValues 取最新值(非定时器快照),落库失败仅日志(UI 一致性优先,不回滚致不一致)
|
||||
- useSetting 双 watch `Object.is` 守卫防循环
|
||||
- switchConversation 双 token 竞态防护(useAiConversations:44,48,52,122 `_latestSwitchId`)
|
||||
|
||||
---
|
||||
|
||||
## 🟡 P2/P3 新发现
|
||||
|
||||
### CR-260615-29 [P2] ai.ts messages 无上限 + knowledge 竞态/错误不一致
|
||||
- **ai.ts messages 单对话无上限**:useAiSend push user+ai / useAiEvents push err/审批 / useAiStream push delta,单对话线性增长(切换对话整体替换是减负点,但同对话不轮换),长会话+大工具结果内存可观
|
||||
- **knowledge 异步无竞态**:loadList/search/loadConfig 无请求序号(messages 有 switch token),快速连点后返回覆盖先
|
||||
- **knowledge 错误处理不一致**:load*/search 有 catch 兜底,create/updateStatus/archive/saveConfig/extractNow 直抛(调用方无 catch 致 unhandled)
|
||||
|
||||
### CR-260615-30 [P3] appSettings useSetting 对象值不落库(边界)
|
||||
useSetting 双 watch `Object.is` 守卫对值类型完美防循环,但**对象/数组值深 mutate**(`r.value.x=1`)时 `Object.is(v,cache[key])` 同引用恒 true → 不触发 set 落库,静默不持久化。当前无 bug(useSetting 多绑值类型,df-ai-ui 走 set 整对象替换),隐式契约。
|
||||
|
||||
---
|
||||
|
||||
## 📊 本轮摘要
|
||||
|
||||
| 类别 | 数 | 代表 |
|
||||
|---|---|---|
|
||||
| 价值闭环 | 3 | CR-24/26/27 全修(修复质量好)|
|
||||
| P1 新发现 | 1 | knowledge error 通道断(CR-28,CR-08 深化)|
|
||||
| P2/P3 新发现 | 2 | ai.ts messages 无上限 + knowledge 竞态(CR-29)/ useSetting 对象值边界(CR-30)|
|
||||
| 亮点 | 2 | ai.ts 状态机完整(6 复位点,验证修复质量)/ appSettings 双源同步设计扎实 |
|
||||
|
||||
**本轮价值**:
|
||||
1. **knowledge error 通道断**是新维度发现 — CR-08 只记中文硬编码,实际整个 error 通道前端断的(写了从不读),用户看不到知识库错误
|
||||
2. **ai.ts 状态机完整肯定前几轮修复质量**(B-32/33/ef2d033 闭环无卡死缺口)— 走查不仅找问题也验证修复
|
||||
3. CR-24/26/27 三项 P1/P2 全修,走查↔接收方闭环高效(第 6 次闭环)
|
||||
|
||||
**方法论**:第 5 个垂直切片(stores),knowledge error 通道断是新发现维度(超越 i18n 的功能缺陷)。ai.ts 状态机完整是「走查验证修复」价值(非只找 bug)。
|
||||
|
||||
todo 映射:CR-260615-28(P1 knowledge error)/ CR-29(P2 messages+竞态)/ CR-30(P3 useSetting 边界)。
|
||||
64
docs/05-代码审查/定时走查-2026-06-15-第8轮.md
Normal file
64
docs/05-代码审查/定时走查-2026-06-15-第8轮.md
Normal file
@@ -0,0 +1,64 @@
|
||||
# 定时代码走查 — 第 8 轮(前端收尾)
|
||||
|
||||
> 触发:定时走查 cron(每 30 分钟)。范围 = **剩余小区域收尾**(useConfirm/useMarkdown/ConfirmDialog/ToolCardList/constants/utils)+ 核 CR-25/28/30 修复。
|
||||
> 方法:主代理 git show 3 提交 + 1 路代理收尾走查。
|
||||
> 性质:核对记录,不改产品代码(本会话 session-role-diagnose-only)。todo 见 `docs/todo.md`。
|
||||
> 关联:[第1-7轮](./)。
|
||||
|
||||
---
|
||||
|
||||
## CR-25/28/30 ✅(价值第 7 次闭环)
|
||||
|
||||
| 提交 | CR | 修复 |
|
||||
|---|---|---|
|
||||
| ac8dab3 | CR-25 ✅ | 分离窗口状态机:destroyed 恢复 panelOpen(R3)+ resumeInDetached 核 ai_is_generating(R4)+ 删 switchConversation 自动 switch(R5)|
|
||||
| 40461b2 | CR-28 ✅ | knowledge create 内 loadCandidates 改非阻塞(不污染成功语义)+ Knowledge.vue error banner 消费 store.error(**通道接通**)|
|
||||
| b300bb7 | CR-30 ✅ | useSetting 值类型契约文档 + changed helper JSON 深比较 |
|
||||
|
||||
**CR-29(ai.ts messages 无上限 + knowledge 竞态)仍存**。
|
||||
|
||||
## 数据完整性修复
|
||||
|
||||
todo:227 CR-30 行末**被污染**(上轮 Edit 锚错位接 CR-23 的 B-34 注释残留内容),本轮修复(删除 CR-23 残留段)。走查记录数据完整性已恢复。
|
||||
|
||||
---
|
||||
|
||||
## 收尾小区域(代理)— 无必修,质量过关
|
||||
|
||||
### CR-260615-31 [P2] 小区域健壮性(Y-1/Y-2)
|
||||
- **Y-1 useConfirm 并发 confirmDialog 覆盖 resolve**(useConfirm.ts:35-47):连点两删除/confirm 交错时,第二个覆盖 `resolve`,前一个 Promise **永挂**(静默中断 + 轻微内存泄漏)。修:进入时 `resolve?.(false)` 打断前一个视为取消
|
||||
- **Y-2 ToolCardList collapseInactive 漏清 expandedTools**(:47-51):只清 expandedCards 未动 expandedTools,已完成卡片折叠后内容展开态残留 Set。修:对称过滤或注释明确有意保留
|
||||
|
||||
### ✅ 5 亮点(收尾区域是项目较干净部分)
|
||||
- **G-1 useConfirm 抽取消除 5 处重复**(Projects/ProjectDetail/Ideas/Settings/AiChat)— DRY 到位
|
||||
- **G-2 useMarkdown 单例 + AiChat 流式 getter 复用** + **escapeHtml 全局单份** + **useRendered 已抽**(CR-10 无残留重复)
|
||||
- **G-3 XSS 防护链完整**:renderMd `_purify.sanitize(_marked.parse(...))` + escapeFallback 纯文本转义双保险(B-24 覆盖)
|
||||
- **G-4 project 常量统一映射**(label/badge/stage 三套 + accessor 兜底 + parseStack 防崩)— 根治三套矛盾
|
||||
- **G-5 ConfirmDialog 自包含**(样式内联不依赖外部 .btn-*,CSS 变量主题化)
|
||||
|
||||
> 硬编码中文文案(AiChat:471/ProjectDetail:392)均带「i18n 不在白名单」注释,属已知取舍,CR-08 范围内不重复记。
|
||||
|
||||
---
|
||||
|
||||
## 📊 8 轮 cron 走查总结(前端全覆盖)
|
||||
|
||||
| 维度 | 成果 |
|
||||
|---|---|
|
||||
| 轮次 | 8 轮 cron + 5 垂直切片(i18n/api/列表页/AI窗口/stores)+ 收尾 |
|
||||
| 价值闭环 | **7 次**(B-32/33/34/35 + AR-11 + CR-18/19/21/22/24/25/26/27/28/30 全被接收方修复)|
|
||||
| 假阳性纠正 | 1 次(Tauri camelCase,代理 A 6 处误判经权威裁决纠正)|
|
||||
| 数据完整性 | 1 次污染修复(CR-30 行末 CR-23 残留)|
|
||||
| 方法论 | 走查须地面 Read/权威为准(非注释);多代理交叉验证有效;垂直切片是横切收敛后的新发现来源 |
|
||||
|
||||
**前端剩余未修**:CR-29(ai.ts messages 无上限 + knowledge 竞态)/ CR-31(useConfirm/ToolCardList 健壮性)/ CR-08(i18n 硬编码 40+)/ CR-09/10(DRY .ai-md/renderedRef)/ CR-12(可选一组)。
|
||||
|
||||
---
|
||||
|
||||
## 💡 建议
|
||||
|
||||
前端走查已**充分覆盖**(8 轮 + 5 垂直切片,收尾无必修,接收方修复速度跟上发现速度)。建议:
|
||||
|
||||
1. **停止 cron**(前端边际收益递减)— 用户可 `/loop` 停止或让我 CronDelete
|
||||
2. **转 task2/3 后端 workflow 功能/模块垂直走查**(新维度:后端 src-tauri + 8 crate 全链路,此前前端走查未覆盖后端)
|
||||
|
||||
todo 映射:CR-260615-31(P2 收尾健壮性)。
|
||||
@@ -177,7 +177,7 @@
|
||||
|
||||
| # | 等级 | 文件:行 | 修改内容 | 对应 todo |
|
||||
|---|------|---------|----------|-----------|
|
||||
| ① | 🔴P0 | project.ts:273 | selectType→select_type,多选审批静默失效 | B-260615-31 |
|
||||
| ① | 🔴P0 | project.ts:273 | selectType→select_type,多选审批静默失效 | B-260615-34 |
|
||||
| ② | 🔴P0 | useAiSend/Stream/Events | timeout/error/stop 收尾清队列,防消息丢失 | B-260615-32 |
|
||||
| ③ | 🔴P0 | useAiStream onStreamTimeout | 回滚 running toolCall,防骨架屏卡死(B-07 残留) | B-260615-33 |
|
||||
| ④ | 🔴P1 | ProjectDetail:201 + useAiStream:39 + AiChat:675 | i18n 一组(多选按钮🔴+错误文案🟡+toast⚪) | CR-260615-08 |
|
||||
|
||||
141
docs/05-代码审查/文档全量核对报告-2026-06-15.md
Normal file
141
docs/05-代码审查/文档全量核对报告-2026-06-15.md
Normal file
@@ -0,0 +1,141 @@
|
||||
# 文档全量核对报告(2026-06-15)
|
||||
|
||||
> 方法:4 路并行代理核对(A 根目录核心 / B 架构+模块 vs 代码 / C 审查+迭代+管理 / D 交叉引用+规范+重复)
|
||||
> 范围:docs/ 全量 48 .md + 根目录 ARCHITECTURE.md / PROGRESS.md / URGENT.md
|
||||
> 性质:核对记录,不改代码(本会话 session-role)。todo 待修项见 docs/todo.md「文档核对待修项」DOC-260615-01~14。
|
||||
> **悬空链接:0**(全量 markdown 链接核对,目标文件全部存在)。
|
||||
|
||||
---
|
||||
|
||||
## P0 文档硬伤(按文档找代码会落空)
|
||||
|
||||
### 1. ARCHITECTURE.md 数据模型整段漂移(B-1 + A)
|
||||
:262-475 列 14 表,**6 虚构 7 遗漏**:`artifacts`/`connections`/`features`/`test_cases`/`test_runs`/`annotations`/`decisions` 7 张不存在(df-traceability 删后未清);`workflow_defs`/`workflow_runs` 应合并为 `workflow_executions`;实际 `node_executions`/`branches`/`ai_conversations`/`knowledges`/`ai_tool_executions`/`knowledge_events`/`app_settings` 7 张未列。projects 表字段(repo_path/priority/current_stage)多数从未加。**建议**:整段重写对齐 migrations.rs,或加「⚠️ 初稿,实际 schema 见 migrations.rs」横幅。
|
||||
|
||||
### 2. df-nodes 模块文档 8 节点全骨架 → 实际 3 节点 2 已实现(A-1/C-1/D-3.2)
|
||||
:9,13,17-26 列 8 节点(AI/Script/Docker/Git/Human/Notify/HTTP/Subflow)全标骨架 execute() 空。实际 `crates/df-nodes/src/` 仅 ai_node/script_node/human_node 3 文件,**均完整实现**(script 接 shell、ai 调 provider、human subscribe→send→select!)。Docker/Git/Notify/HTTP/Subflow 5 节点不存在。**建议**:重写,节点缩 3 个状态改 ✅。
|
||||
|
||||
### 3. df-ai 模块文档列不存在的文件 + 已删 trait(A-3)
|
||||
:30-40 文件结构列 `router.rs`(ModelRouter)+ `stream.rs`(StreamCollector),实际均无(R-PD-7 删)。:50-72 LlmProvider trait 含 `supported_features()`(已删,新增 `endpoint()`)。:22/174 工具数 12 → 实际 13。**建议**:删两虚构文件行,trait 段对齐,12→13。
|
||||
|
||||
### 4. 使用手册 7 处与实际不符(C-4.4)
|
||||
运行命令 `bun`→实际 `npm`;任务状态 4 态→实际 7 态(todo/in_progress/in_review/testing/done/blocked/cancelled);优先级 P0-P3 方向歧义(前后端枚举反向);节点类型 Script/Condition/Parallel→实际 Script/Ai/Human(无独立 Condition/Parallel,且 Script 已下线);监控「WebSocket」→实际 Tauri EventBus;「单 Provider」→实际多 Provider;知识库「静态收集」→实际 Tier1 AI 提炼+向量检索。**建议**:全面重写。
|
||||
|
||||
---
|
||||
|
||||
## P1 审查状态断层(报告说修了/todo 看不到)
|
||||
|
||||
### 5. 全局 review R-P1-1~6 / R-P2-1~13 已实施 todo 零回写(C-1.2/1.3)
|
||||
全局代码review-2026-06-15.md 推进状态标「P1×6 + P2×13 全闭环,126 test pass」,代码核对坐实 R-P1-2/3/5/6、R-P2-2/3/8/9/10/13 均落地。**todo.md 完全无条目**(既不在待办也不在已完成)。**建议**:已完成区补汇总回写。
|
||||
|
||||
### 6. 架构复核报告 §5 新发现 11 项零入 todo(C-1.1)
|
||||
架构与缺陷复核报告 §5 新①~⑪(5 项🟠),todo 零覆盖。复核-新②/⑨/⑩ 后被全局 review 取代,**复核-新①/③/④/⑪ 待补**:①useAiConversations pendingToolCalls 二次 token 比对 / ③executor NodeCancelled 事件语义双标 / ④cancel_workflow_node 终态守卫 / ⑪主窗口幽灵 watchdog。**建议**:4 项补 todo。
|
||||
|
||||
### 7. aichat §8 + 工作流审批 TL;DR 已修项未勾(B-3/B-4)
|
||||
aichat审查报告 §8 优先级表 11 行仅 AR-3 标 ✅,实际 H1/H2/create_project/AR-5/6/7/9/10 均 ✅(todo 对齐)。工作流审批审查报告 TL;DR + §0 头号 P0(human_node.rs:41 缺 await)B-03b-R6 ✅ 已修但报告仍「待修」语气。**建议**:补 ✅ + commit 标注。
|
||||
|
||||
### 8. 近期改动 CR-03 未做子项埋 [x] 备注(C-1.4)
|
||||
CR-260615-03 标 [x] 但备注含「未做:audit.rs Err 误报/openai source 丢失/ToolCard projectNameById find」4 子项,代码核对坐实未改。**建议**:拆独立 P2 todo(或合并 CR-260615-11)。
|
||||
|
||||
---
|
||||
|
||||
## P1 架构/模块文档过期
|
||||
|
||||
### 9. df-workflow 4 处接口过期(B-A-2)
|
||||
:81 NodeRegistry Default(已删,registry.rs:76 注「不实现」);:180 ConditionEngine 默认 true→实际 false(B-260614-02);:138 try_recv_human_approval(已删);:155-156 set_waiting/set_skipped(已删,仅留 set_cancelled)。**建议**:4 处对齐。
|
||||
|
||||
### 10. df-storage V8→V13(B-A-4/C-4.1)
|
||||
:3,9,26-36,114,132 反复 V1-V8/MIGRATION_VERSION=8,实际 V1-V13(V9 ai_providers/V10 knowledges.reasoning+knowledge_events/V11 projects.deleted_at/V12 path+stack/V13 app_settings)。表数 11→18,Repo 11→17。**建议**:版本/表数/Repo 数全改。
|
||||
|
||||
### 11. 对抗评估 idea_evaluations 设计稿未标未实施(B-A-5)
|
||||
想法探索-对抗式评估 :220-310 设计 6 struct + idea_evaluations 表,全仓零命中。实际 adversarial.rs 纯启发式(AdversarialEval/Argument/AnalystAnalysis),功能决策记录已标「📐 设计未实施」但模块文档未标。**建议**:加「⚠️ 当前纯启发式,三路 LLM + 表为设计稿」横幅。
|
||||
|
||||
### 12. df-execute 文档 Shell/Docker/SSH/Git vs 仅 Shell(D-3.3)
|
||||
ARCHITECTURE:84/PROGRESS:29/df-nodes 文档描述 df-execute Shell/Docker/SSH/Git,实际仅 shell.rs+lib.rs(docker/git_ops/ssh Sprint 19 已删)。**建议**:删 Docker/SSH/Git 描述。
|
||||
|
||||
---
|
||||
|
||||
## P1 索引/计数矛盾
|
||||
|
||||
### 13. 13→8 crate 多处(A/D-3.1)
|
||||
实际 workspace 8 crate。PROGRESS:13/INDEX:4,107,119/Phase1决策 ADR-003/对抗裁决报告/想法探索 多处写 13。**建议**:统一「原 13,2026-06-14 删 5 零引用,现 8」(ADR 加「已修正」标注保留决策史)。
|
||||
|
||||
### 14. INDEX 漏 9 文档 + 09-问题排查目录(A/D-2.3)— **本会话已补正**
|
||||
F-07 trait下沉/密钥迁移/工作流边界/条件引擎/generating状态机(02 区 5)/DAG引擎详解/AI对话引擎(03 区 2)/全局代码review(05 区 1)/aichat-apikey-401排查(09 目录)。**已在 INDEX 补**。
|
||||
|
||||
### 15. commands 数 47/57/68 三处冲突(C-5.1)
|
||||
PROGRESS:72 写 47、:48 写 57、实际 68 个 #[tauri::command]。**建议**:统一 68。
|
||||
|
||||
### 16. URGENT.md 过时 + 未挂索引(D-5.1/2.4)
|
||||
5 个 P0(AC3/FR-S7/FR-S8/B-03b-R8/FR-S1)在 todo 全 ✅,URGENT 无停用标注 + 不在任何索引。**建议**:顶部加「⚠️ 2026-06-14 快照,P0 全迁 todo 已完成,本文件停用」或删除。
|
||||
|
||||
---
|
||||
|
||||
## P2 迭代/Phase 过期
|
||||
|
||||
### 17. DEVFLOW-1~4 全标待实施(C-2.1)
|
||||
4 文档全「状态:待实施/实施记录待填写」,实际 Phase1 全完成(DEVFLOW-1 宏替代 trait 设计转向/DEVFLOW-2 68 commands/3-4 落地)。**建议**:标已完成+实施摘要,或归档。
|
||||
|
||||
### 18. Phase1 任务清单严重滞后(C-3.1)
|
||||
标「进行中」,实际全完成。待实施表 #7~#11 全 [ ](均已做)。代码规模 71 文件 4108 行 vs 实际 53/7400。已知问题 4 项 3 项已解决。**建议**:标完成或归档。
|
||||
|
||||
### 19. Phase2 计划部分超前/滞后(C-3.2)
|
||||
Phase2.2/2.3 基础已完成(启发式)未回写;Phase2.4 并行 ✅/编辑器+条件分支未做;B 路线 conditions 默认值已改未同步。**建议**:标注实际状态。
|
||||
|
||||
---
|
||||
|
||||
## P2 三层规范
|
||||
|
||||
### 20. todo 肥大违反分层(D-2.1)
|
||||
todo:88-101/251-267/293-296 块是 mini 审查报告塞看板(单条 10-30 行),应压成指针进 docs。**建议**:每条压到「ID — [P级] 一句话 — 详见<文档>」。
|
||||
|
||||
### 21. 重复收口(D-4.1-4.3)
|
||||
HumanNode 审批三处(功能决策记录未瘦身为指针,B-03 文档标真相源但功能决策记录仍全文重复);密钥迁移两处(全局review 应仅留指针);FR-S1 四处(todo 与 URGENT 既重复又矛盾)。**建议**:真相源收口,余改指针。
|
||||
|
||||
### 22. 文档记录规范未在 INDEX 突出(D-5.3)
|
||||
文档记录规范-2026-06-14.md 是三层规范权威定义,INDEX 仅普通条目,README 未提。**建议**:INDEX/README 加「规范基准」标注。
|
||||
|
||||
---
|
||||
|
||||
## P3 技术/工具文档
|
||||
|
||||
### 23. Tauri-IPC 全篇过时(C-4.2)
|
||||
:8 「仅 greet 示例」→实际 68 commands;:16-23 待实施 5 项全落地;:35 `frontend/src/`→实际 `src/`。**建议**:重写或归档。
|
||||
|
||||
### 24. View 改造指南全篇过时(C-4.3)
|
||||
:7-13 当前问题 4 项全解决;:80-86 页面 ProjectsView 等(实际无 View 后缀,无 WorkflowView)。**建议**:归档(与 DEVFLOW-3 重复)。
|
||||
|
||||
### 25. SQLite 表数 V1-V9→V1-V13(C-4.1,与 §10 同源)
|
||||
|
||||
### 26. 锚点顿号 + 路径风格(A/D)
|
||||
INDEX:107-109 `#四crate-结构` 锚点顿号渲染器差异(低危);功能创意池:150 链接省 `./` 风格不一。
|
||||
|
||||
---
|
||||
|
||||
## ✅ 一致性良好
|
||||
|
||||
- **df-knowledge 模块文档**:与代码吻合
|
||||
- **规格契约自检机制**:活契约机制描述准确
|
||||
- **功能决策记录**:状态标注详尽且与 todo 对齐(决策反转有留痕)
|
||||
- **经验记录**:落地项均标 ✅ + commit
|
||||
- **todo.md**:项目内最准真相源,审查报告交叉对照基本可靠
|
||||
- **悬空链接 0**:全量 markdown 链接目标全部存在
|
||||
|
||||
---
|
||||
|
||||
## 📊 摘要
|
||||
|
||||
| 类别 | 数 | 代表 |
|
||||
|---|---|---|
|
||||
| P0 文档硬伤 | 4 | ARCHITECTURE 数据模型 / df-nodes / df-ai / 使用手册 |
|
||||
| P1 审查断层 | 4 | R-P1/P2 未回写 / 复核新①③④⑪ / §8 表 / CR-03 子项 |
|
||||
| P1 模块过期 | 4 | df-workflow / df-storage / 对抗评估 / df-execute |
|
||||
| P1 索引计数 | 4 | 13→8 / INDEX漏 / commands冲突 / URGENT |
|
||||
| P2 迭代规范 | 6 | DEVFLOW / Phase1 / Phase2 / todo肥大 / 重复 / 规范未突出 |
|
||||
| P3 技术文档 | 4 | Tauri-IPC / View指南 / SQLite / 锚点 |
|
||||
|
||||
**最优先**:P0 四项(ARCHITECTURE 数据模型重写 / df-nodes 重写 / df-ai 删虚构 / 使用手册重写)—— 按文档找代码会落空,误导接手方最强。
|
||||
|
||||
**已本会话补正**:INDEX 漏列 9 文档 + 09 目录(DOC-14 部分);ID 碰撞 B-260615-31→34。
|
||||
|
||||
todo 映射:DOC-260615-01~14(见 docs/todo.md「文档核对待修项」)。
|
||||
@@ -34,14 +34,21 @@ docs/
|
||||
│ ├── aichat信息密度构想-2026-06-14.md
|
||||
│ ├── 任务推进构想-2026-06-14.md
|
||||
│ ├── aichat流式Markdown渲染调研-2026-06-15.md # 流式渲染优化方案(rAF节流/块级diff/换库)
|
||||
│ └── 工作流审批审查报告-2026-06-14.md
|
||||
│ ├── 工作流审批审查报告-2026-06-14.md
|
||||
│ ├── F-07-df-ai-core-trait下沉设计-2026-06-14.md
|
||||
│ ├── 密钥迁移健壮性-2026-06-15.md
|
||||
│ ├── 工作流脚本执行边界-2026-06-15.md
|
||||
│ ├── 条件表达式引擎-2026-06-15.md
|
||||
│ └── generating状态机加固-2026-06-15.md
|
||||
├── 03-模块文档/
|
||||
│ ├── df-storage-存储层-2026-06-12.md
|
||||
│ ├── df-workflow-工作流引擎-2026-06-12.md
|
||||
│ ├── df-nodes-节点集合-2026-06-12.md
|
||||
│ ├── df-ai-AI集成模块-2026-06-12.md
|
||||
│ ├── df-knowledge-知识库-2026-06-14.md
|
||||
│ └── 想法探索-对抗式评估-2026-06-12.md
|
||||
│ ├── 想法探索-对抗式评估-2026-06-12.md
|
||||
│ ├── DAG引擎详解-2026-06-14.md
|
||||
│ └── AI对话引擎-2026-06-14.md
|
||||
├── 04-功能迭代/
|
||||
│ ├── DEVFLOW-1.CRUD层实施-2026-06-12.md
|
||||
│ ├── DEVFLOW-2.IPC桥接实施-2026-06-12.md
|
||||
@@ -53,14 +60,26 @@ docs/
|
||||
│ ├── 近期改动代码审查-2026-06-15.md # 工作区 FR-S1/S7/S8 + 近 5 提交(3 路并行)
|
||||
│ ├── 架构审查-2026-06-15.md # 纯架构层(边界/依赖/抽象/扩展性),8 crate + 前端(2 路并行)
|
||||
│ ├── 自研块级memo流式渲染审查-2026-06-15.md # ARC-260615-08 实施走查(splitBlocks/parseBlock/rAF)
|
||||
│ └── 工作区多角度走查-2026-06-15.md # 工作区22文件547行4路并行(selectType/队列收尾/骨架屏/i18n/DRY)
|
||||
│ ├── 工作区多角度走查-2026-06-15.md # 工作区22文件547行4路并行(selectType/队列收尾/骨架屏/i18n/DRY)
|
||||
│ ├── 定时走查-2026-06-15-P0复核.md # 定时走查第1轮:B-34已修(B-32·33结论后被第2轮纠正为已修)/B-35 broadcast Lagged新P0/aiShared破环亮点
|
||||
│ ├── 定时走查-2026-06-15-第2轮.md # 定时走查第2轮:B-32·33·34全修确认(第3次纠正过时)/AR-11前端listener永不attach新P0/ARC-05 store拆分优/CR-11健壮性✅
|
||||
│ ├── 定时走查-2026-06-15-第3轮.md # 定时走查第3轮:走查价值闭环(AR-11/B-35/CR-18·19全修确认+CR-20撤销误判)/前端P0全闭环/CR-21 App.vue try/catch轻量
|
||||
│ ├── 定时走查-2026-06-15-第4轮.md # 定时走查第4轮:i18n垂直切片(key树466全对齐优秀)/硬编码40+处(CR-08扩展)/P1-1 useAiEvents:182 i18n破坏bug改一行最高ROI
|
||||
│ ├── 定时走查-2026-06-15-第5轮.md # 定时走查第5轮:api垂直切片/Tauri v2默认转camelCase权威裁决(代理A 6处IPC遗漏假阳性纠正)/B-34错误注释传播误导(CR-23)/types对齐良好
|
||||
│ ├── 定时走查-2026-06-15-第6轮.md # 定时走查第6轮:列表页+Dashboard+AI窗口垂直切片/分离窗口生命周期P1(listener双注册+永不清理CR-24)/状态机缺口(CR-25)/列表页契约(CR-26)/Dashboard(CR-27)
|
||||
│ ├── 定时走查-2026-06-15-第7轮.md # 定时走查第7轮:stores垂直切片/CR-24·26·27三修确认(价值第6闭环)/knowledge error通道断P1(CR-28,CR-08深化)/ai.ts状态机完整亮点(验证修复质量)
|
||||
│ ├── 定时走查-2026-06-15-第8轮.md # 定时走查第8轮(前端收尾):CR-25·28·30三修确认(价值第7闭环)/CR-30污染修复/收尾小区域无必修(CR-31 useConfirm/ToolCardList)+5亮点(XSS防护链/useConfirm DRY)/8轮总结+停止cron建议
|
||||
│ ├── 全局代码review-2026-06-15.md # 7维度并行(DRY/架构/bug/安全/AI可靠/工作流),P1×6+P2×13全闭环
|
||||
│ └── 文档全量核对报告-2026-06-15.md # docs全量+根目录4路核对(ARCHITECTURE漂移/模块文档过期/审查状态断层)
|
||||
├── 06-前端开发/
|
||||
│ └── View改造指南-2026-06-12.md
|
||||
├── 07-项目管理/
|
||||
│ ├── Phase1任务清单-2026-06-12.md
|
||||
│ └── Phase2计划-2026-06-12.md
|
||||
└── 08-用户指南/
|
||||
└── 使用手册-2026-06-12.md
|
||||
├── 08-用户指南/
|
||||
│ └── 使用手册-2026-06-12.md
|
||||
└── 09-问题排查/
|
||||
└── aichat-apikey-401排查-2026-06-15.md
|
||||
```
|
||||
|
||||
---
|
||||
@@ -80,6 +99,7 @@ docs/
|
||||
| 🎨 前端开发 | [06-前端开发/](./06-前端开发/) | Vue 3 前端分析、优化、迁移指南 |
|
||||
| 📊 项目管理 | [07-项目管理/](./07-项目管理/) | 任务清单、开发计划、进度跟踪 |
|
||||
| 📚 用户指南 | [08-用户指南/](./08-用户指南/) | 快速上手、配置指南、FAQ |
|
||||
| 🐛 问题排查 | [09-问题排查/](./09-问题排查/) | 排查记录、根因分析 |
|
||||
|
||||
---
|
||||
|
||||
|
||||
189
docs/ai-file-ops-manual.md
Normal file
189
docs/ai-file-ops-manual.md
Normal file
@@ -0,0 +1,189 @@
|
||||
# AI 文件操作工具能力手册
|
||||
|
||||
> 最后更新:2026-06-15 | 基于全量测试验证
|
||||
|
||||
---
|
||||
|
||||
## 工具总览
|
||||
|
||||
| 工具 | 用途 | 可靠性 |
|
||||
|------|------|--------|
|
||||
| `read_file` | 读取文件内容(全文/分页) | ✅ 高 |
|
||||
| `write_file` | 创建/覆盖写入文件(纯文本) | ✅ 高 |
|
||||
| `list_directory` | 列出目录内容(含递归/深度控制) | ✅ 高 |
|
||||
| `run_command` | 执行 shell 命令 | ⚠️ 可执行,但 stdout 不可用 |
|
||||
|
||||
---
|
||||
|
||||
## read_file
|
||||
|
||||
读取文件内容,支持分页。
|
||||
|
||||
### 参数
|
||||
|
||||
| 参数 | 类型 | 必填 | 说明 |
|
||||
|------|------|------|------|
|
||||
| `path` | string | ✅ | 文件路径,支持 `/` 和 `\` |
|
||||
| `offset` | number | ❌ | 起始行(0 基索引),默认 0 |
|
||||
| `limit` | number | ❌ | 读取行数,默认全部 |
|
||||
|
||||
### 行为规则
|
||||
|
||||
- `offset` 是 **0 基索引**:offset=0 → 第 1 行,offset=10 → 第 11 行
|
||||
- `offset` 超出总行数 → 返回空内容,**不报错**
|
||||
- `limit` 超出剩余行数 → 返回到末尾,**不报错**
|
||||
- 空文件 → 返回 `{ content: "", lines: 0, size: 0 }`
|
||||
- 不存在的文件 → 报错 `os error 2`
|
||||
- 二进制文件 → 返回 `{ binary: true, content: null, error: "文件非 UTF-8 文本" }`
|
||||
- 路径越界(项目目录外)→ 被拦截
|
||||
|
||||
### 返回结构
|
||||
|
||||
```json
|
||||
{
|
||||
"content": "文件文本内容",
|
||||
"lines": 30, // 总行数(始终是全量值,非当前页行数)
|
||||
"size": 145, // 总字节数(始终是全量值)
|
||||
"path": "..."
|
||||
}
|
||||
```
|
||||
|
||||
> ⚠️ `lines` 和 `size` 始终返回**全量值**,不受 offset/limit 影响。
|
||||
|
||||
---
|
||||
|
||||
## write_file
|
||||
|
||||
创建或覆盖写入文件,纯文本模式。
|
||||
|
||||
### 参数
|
||||
|
||||
| 参数 | 类型 | 必填 | 说明 |
|
||||
|------|------|------|------|
|
||||
| `path` | string | ✅ | 文件路径 |
|
||||
| `content` | string | ✅ | 文本内容(UTF-8) |
|
||||
|
||||
### 行为规则
|
||||
|
||||
- **自动创建多级父目录**:路径中不存在的目录会自动创建
|
||||
- **覆盖已有文件**:直接覆盖,无确认机制(⚠️ 注意安全)
|
||||
- 返回 `old_size`(覆盖前大小,新建时为 null)和 `bytes_written`(写入大小)
|
||||
- 支持 emoji、引号、反斜杠、中文等特殊字符
|
||||
- 路径分隔符 `/` 和 `\` 均可
|
||||
- 中文路径/文件名含空格 → 正常工作
|
||||
- 空内容(`content=""`)→ 创建 0 字节文件
|
||||
- **无法写入二进制数据**(NULL 字节等会被当文本处理)
|
||||
- 路径越界(项目目录外)→ 被拦截
|
||||
|
||||
### 局部修改的正确做法
|
||||
|
||||
write_file 是全量覆盖,局部修改需三步:
|
||||
|
||||
```
|
||||
1. read_file 获取完整内容
|
||||
2. AI 在内存中替换目标行/文本
|
||||
3. write_file 写回完整内容
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## list_directory
|
||||
|
||||
列出目录内容,支持递归和深度控制。
|
||||
|
||||
### 参数
|
||||
|
||||
| 参数 | 类型 | 必填 | 说明 |
|
||||
|------|------|------|------|
|
||||
| `path` | string | ✅ | 目录路径 |
|
||||
| `recursive` | boolean | ❌ | 是否递归,默认 false |
|
||||
| `max_depth` | number | ❌ | 递归最大深度(配合 recursive) |
|
||||
| `skip_noise_dirs` | boolean | ❌ | 跳过 node_modules/.git 等,默认 false |
|
||||
|
||||
### 返回结构
|
||||
|
||||
```json
|
||||
{
|
||||
"entries": [
|
||||
{ "name": "src", "type": "directory", "size": 0, "depth": 0 },
|
||||
{ "name": "main.ts", "type": "file", "size": 466, "depth": 0 }
|
||||
],
|
||||
"truncated": false
|
||||
}
|
||||
```
|
||||
|
||||
### 行为规则
|
||||
|
||||
- `type` 取值:`"directory"` | `"file"`
|
||||
- `depth`:0 = 根层级,1 = 一级子目录,以此类推
|
||||
- 不存在的目录 → 报错 `os error 3`
|
||||
- `truncated: false` 表示结果完整未截断
|
||||
|
||||
---
|
||||
|
||||
## run_command
|
||||
|
||||
执行 shell 命令。
|
||||
|
||||
### 参数
|
||||
|
||||
| 参数 | 类型 | 必填 | 说明 |
|
||||
|------|------|------|------|
|
||||
| `command` | string | ✅ | shell 命令(非交互式) |
|
||||
| `working_dir` | string | ❌ | 工作目录 |
|
||||
| `timeout_secs` | number | ❌ | 超时秒数,默认 60 |
|
||||
|
||||
### ⚠️ 已知问题
|
||||
|
||||
**stdout 始终返回空字符串**(已确认是普遍问题):
|
||||
- `echo`、`Write-Output` 等输出无法被捕获
|
||||
- 命令本身**能正常执行**(cp/mv/rm/重定向写文件均验证通过)
|
||||
- stderr 大部分场景也为空
|
||||
|
||||
### 可靠使用的场景
|
||||
|
||||
| 场景 | 可靠性 | 示例 |
|
||||
|------|--------|------|
|
||||
| 删除文件 `rm` | ✅ | `rm -f path/to/file` |
|
||||
| 复制文件 `cp` | ✅ | `cp src.txt dst.txt` |
|
||||
| 移动/重命名 `mv` | ✅ | `mv old.txt new.txt` |
|
||||
| 创建目录 `mkdir` | ✅ | `mkdir -p deep/nested/dir` |
|
||||
| 写入文件(重定向) | ✅ | `echo content > file.txt` |
|
||||
| 二进制写入 `printf` | ✅ | `printf '\x89PNG' > file.png` |
|
||||
| 超时控制 | ✅ | `timeout_secs=3` 正确中断 |
|
||||
| 工作目录切换 | ✅ | `working_dir` 生效,相对路径可用 |
|
||||
| 获取命令输出 | ❌ | stdout/stderr 不可用 |
|
||||
|
||||
---
|
||||
|
||||
## 快速参考:常见操作怎么做
|
||||
|
||||
| 我想要... | 工具调用 |
|
||||
|-----------|---------|
|
||||
| 读文件全文 | `read_file(path)` |
|
||||
| 读第 10~15 行 | `read_file(path, offset=9, limit=6)` |
|
||||
| 创建新文件 | `write_file(path, content)` |
|
||||
| 修改文件中某几行 | `read_file` → AI 替换 → `write_file` |
|
||||
| 追加内容到文件末尾 | `read_file` → 拼接 → `write_file` |
|
||||
| 删除文件 | `run_command("rm -f path")` |
|
||||
| 重命名文件 | `run_command("mv old new")` |
|
||||
| 列出项目结构 | `list_directory(path, recursive=true, max_depth=3)` |
|
||||
| 搜索文件内容 | 分页 `read_file` 逐段扫描(无原生搜索) |
|
||||
| 搜索文件名 | `list_directory` 递归 + AI 过滤(无原生 glob) |
|
||||
| 写入二进制文件 | `run_command("printf '\x89...' > file")` |
|
||||
| 获取文件大小/行数 | `read_file`(必须读内容,无轻量元信息工具) |
|
||||
|
||||
---
|
||||
|
||||
## 待实现能力(已建任务)
|
||||
|
||||
| 能力 | 任务 ID | 优先级 |
|
||||
|------|---------|--------|
|
||||
| 文件内容搜索 `search_in_file` | F-FILE-01 | P1 |
|
||||
| 文件局部更新 `patch_file` | F-FILE-02 | P1 |
|
||||
| 文件元信息 `file_info` | F-FILE-03 | P1 |
|
||||
| run_command stdout 修复 | F-FILE-07 | P1 |
|
||||
| 追加写入 `append_file` | F-FILE-04 | P2 |
|
||||
| 文件名搜索 `search_files` | F-FILE-05 | P2 |
|
||||
| Base64 二进制写入 | F-FILE-06 | P2 |
|
||||
| 文件差异对比 `diff_file` | F-FILE-08 | P3 |
|
||||
207
docs/todo.md
207
docs/todo.md
@@ -74,7 +74,8 @@
|
||||
- [ ] AR-8 delta 节流+滚动 — **重评(2026-06-15)**:前端 rAF 节流已被 ARC-08 覆盖(每帧 ≤1 parse);剩后端 50ms 合批(B-260615-02 心跳已动 stream_recv.rs,合批可并入同文件)+ 滚动跟随。降优先级 — stream_recv.rs + AiChat.vue 滚动
|
||||
- [x] AR-9 ~~friendlyError 硬编码中文~~ ✅ Wave3 完成(friendlyError 全走 i18n.global.t + zh/en 双语补 4 key;TS2589 用 as any 规避 vue-i18n 深度泛型)(commit 9e2aeff)
|
||||
- [x] AR-10 ~~想法→灵感迁移残留~~ ✅ 已统一(13 文件批量:i18n zh-CN + 后端错误 + LLM 描述/提示词 + store toast;en 待定 Ideas/Idea、docs 注释低优先略)(commit 65c475b)
|
||||
- [ ] AR-11 数据变更联动刷新(推荐方案A 后端 emit + store 监听)— 跨层 — 详见 [审查第五章](./02-架构设计/aichat审查报告-2026-06-14.md) — **勘察完成(2026-06-15,wxflofhf2)**:feasible/risk 中/跨 8 文件(audit.rs/commands.rs/tool_registry.rs/stores/project.ts/useAiEvents.ts/Projects/Tasks/ProjectDetail.vue)。方案A 方向合理(emit df-data-changed+entity/action 分类+store listen)但勘察 implPlan 含伪代码错误(std::env::var/.match Rust 不存在=agent 幻觉)+碰 7 近期活跃文件含未提交 P0 改动的 commands.rs。**暂缓**:等本批提交后主代理重设计 emit 点(audit.rs 签名是否有 app_handle 待核查)再派
|
||||
- [x] AR-11 ✅(定时走查第3轮,2026-06-15,dc27e79) — ~~数据变更联动刷新~~(推荐方案A 后端 emit + store 监听)— 跨层 — 详见 [审查第五章](./02-架构设计/aichat审查报告-2026-06-14.md) — **勘察完成(2026-06-15,wxflofhf2)**:feasible/risk 中/跨 8 文件(audit.rs/commands.rs/tool_registry.rs/stores/project.ts/useAiEvents.ts/Projects/Tasks/ProjectDetail.vue)。方案A 方向合理(emit df-data-changed+entity/action 分类+store listen)但勘察 implPlan 含伪代码错误(std::env::var/.match Rust 不存在=agent 幻觉)+碰 7 近期活跃文件含未提交 P0 改动的 commands.rs。**暂缓(原)**:等本批提交后主代理重设计 emit 点 — **第2轮定时走查复核(2026-06-15):后端已实施 ✅,前端半接通 🔴** — 后端 `emit_data_changed`(audit.rs:259-270)+ 自动执行路径(audit.rs:351)+ 审批路径(commands.rs:175)三处 emit 就位且逻辑正确;前端工作区(project.ts:31-55,109-110)定义 `startDataChangedListener`/`stopDataChangedListener` + export,**但 App.vue onMounted 未挂载 → listener 永不 attach → 数据变更后列表不刷新(AR-11 功能失效,后端 emit 成死事件)**。修:App.vue onMounted 调 `startDataChangedListener()` + onUnmounted stop(对齐 workflow listener 生命周期)。详见 [定时走查-2026-06-15-第2轮.md](./05-代码审查/定时走查-2026-06-15-第2轮.md)
|
||||
> **第3轮(2026-06-15):listener 已 attach ✅** — dc27e79 `App.vue:218` onMounted `await startDataChangedListener()` + :258 onUnmounted stop。AR-11 功能闭环(后端 emit df-data-changed + 前端 listen 按 entity 调 load)。详见 [定时走查-2026-06-15-第3轮.md](./05-代码审查/定时走查-2026-06-15-第3轮.md)
|
||||
|
||||
### 🔴 aichat API Key 401 排查(2026-06-15)
|
||||
|
||||
@@ -196,18 +197,65 @@
|
||||
> 4 路并行代理走查工作区 22 文件 547 行(AiChat/View/stores/composables+ToolCard),详见 [工作区多角度走查-2026-06-15.md](./05-代码审查/工作区多角度走查-2026-06-15.md)。3 P0 功能 bug + 1 P1 i18n + DRY/健壮性一组。
|
||||
|
||||
**P0 — 功能 bug**
|
||||
- [ ] B-260615-31 — **[P0]** selectType 字段名与后端 IPC 不匹配 → 多选审批静默失效。`project.ts:273` 传 selectType(camelCase),后端 `workflow.rs:211` 签名 select_type(snake_case),Tauri 2 默认不转换 → 后端收 None 归一化 Single → 多选提交被拒「单选只能一个决策」。单选偶然兼容掩盖。铁证:同 invoke 的 execution_id/node_id 已 snake_case 唯独此项破坏 + grep selectType src-tauri/ 零命中。修:`selectType:`→`select_type:` + 删错误注释 — stores/project.ts:273 — 详见走查 ①
|
||||
- [ ] B-260615-32 — **[P0]** 流式 timeout/error/stop 收尾不清队列 → 队列消息静默丢失。drainQueue 仅 AiCompleted 触发(useAiEvents:218),onStreamTimeout/AiError/stopChat 三路径不清 state.queue → 生成中输入的消息丢失无提示。approveToolCall catch 同漏。修:三路径 + approve catch 补 `state.queue=[]` + 提示。关联 B-260615-22(状态不同步,不同角度) — useAiSend.ts:36-42 + useAiStream.ts onStreamTimeout + useAiEvents.ts AiError case + useAiSend.ts:82-107 — 详见走查 ②
|
||||
- [ ] B-260615-33 — **[P0]** onStreamTimeout 不回滚 running toolCall → 审批后卡片永久骨架屏。approveToolCall 乐观置 running,后端 hang 时看门狗触发 onStreamTimeout 只复位 streaming 不碰 toolCalls[].status → running 态只渲染骨架(ToolCard:24) 审批按钮不显 → 无重审入口。**B-260615-07 残留**(B-07 加了 watchdog 但回调没回滚 status)。修:onStreamTimeout 扫一遍 running toolCall→rejected — useAiStream.ts:21-49 — 详见走查 ③
|
||||
- [x] B-260615-34 ✅(定时走查复核,2026-06-15) — ~~selectType snake_case 对齐~~ `project.ts:273` 已改 `select_type: ... ?? 'single'` + 正确注释「Tauri 2 IPC 不转 camelCase,须 snake_case 对齐后端 workflow.rs:211」;后端 `workflow.rs:211` 同 `select_type: Option<String>`,前后端契约对齐。**纠正上轮过时判断**(上轮压缩前快照仍 camelCase,之后 `80b9243`「P0 bug 修复」批已修;后端代理经 IPC 签名推断「前端已对齐」结论正确) — stores/project.ts:273 — 详见定时走查 P0复核.md
|
||||
- [x] B-260615-32 ✅(定时走查第2轮复核,2026-06-15,f93b758) — ~~流式收尾清队列~~ **[原 P0]**drainQueue 仅 AiCompleted 触发(useAiEvents:218),onStreamTimeout/AiError/stopChat 三路径不清 state.queue → 生成中输入的消息丢失无提示。approveToolCall catch 同漏。修:三路径 + approve catch 补 `state.queue=[]` + 提示。关联 B-260615-22(状态不同步,不同角度) — useAiSend.ts:36-42 + useAiStream.ts onStreamTimeout + useAiEvents.ts AiError case + useAiSend.ts:82-107 — 详见走查 ②
|
||||
> ⚠️ **第2轮复核更正(2026-06-15):已修 ✅** — f93b758「修复: B-32+33 流式收尾」:`useAiStream.ts:28` `state.queue=[]`(onStreamTimeout) + `useAiEvents.ts:225`(AiError) + `useAiSend.ts:152`(stopChat)/124(approveToolCall catch)四路径全清队列。上轮「仍未修」为过时快照(working tree 当时旧版,与 B-34 同)。详见 [定时走查-2026-06-15-第2轮.md](./05-代码审查/定时走查-2026-06-15-第2轮.md)
|
||||
- [x] B-260615-33 ✅(定时走查第2轮复核,2026-06-15,f93b758) — ~~回滚 running toolCall~~ **[原 P0]**approveToolCall 乐观置 running,后端 hang 时看门狗触发 onStreamTimeout 只复位 streaming 不碰 toolCalls[].status → running 态只渲染骨架(ToolCard:24) 审批按钮不显 → 无重审入口。**B-260615-07 残留**(B-07 加了 watchdog 但回调没回滚 status)。修:onStreamTimeout 扫一遍 running toolCall→rejected — useAiStream.ts:21-49 — 详见走查 ③
|
||||
> ⚠️ **第2轮复核更正(2026-06-15):已修 ✅** — f93b758:`useAiStream.ts:43-45` onStreamTimeout 单遍反向扫描 running toolCall→rejected(合并探测 completed 为单次 O(n))。上轮「仍未修」为过时快照。详见 [定时走查-2026-06-15-第2轮.md](./05-代码审查/定时走查-2026-06-15-第2轮.md)
|
||||
- [x] B-260615-35 ✅(定时走查第3轮复核,2026-06-15,b08adcb) — **[原 P0]** broadcast `Lagged` 兜底仅 warn → 关键终态事件丢失时前端永久卡死。`workflow.rs:90-110` forward 任务靠 `finished = matches!(event, WorkflowCompleted|WorkflowFailed)` 退出,broadcast 滑动窗口 Lagged(n) 丢 n 条最旧事件,**代码注释已识别风险但仅 warn 无兜底**;若 Lagged 恰丢终态事件,forward 永远 `rx.recv().await` 等不到 finished → 前端审批/完成弹窗永久卡死,仅靠 DB 轮询兜底(实时性差)。修:Lagged 分支后累计次数/单次 n 过大时查 `WorkflowRepo::get_by_id` 看终态,补 emit workflow-event + break;需把 `state.db.clone()` move 进 forward 任务(当前仅 executor spawn 持有)。低频但无自愈 — src-tauri/src/commands/workflow.rs:90-110 — 详见定时走查 P0复核.md(后端代理 ②)
|
||||
|
||||
**P1 — i18n**
|
||||
- [ ] CR-260615-08 — **[P1]** i18n 硬编码一组:ProjectDetail:201 多选按钮「确认(N)」🔴 + useAiStream:39-41 onStreamTimeout 两错误文案🟡 + AiChat:675 handleSend toast⚪。英文 locale 中英混杂。修:补 `projectDetail.approvalConfirm/Count` + `ai.streamInterrupted(AfterTool)` + `aiChat.toastSendFail` 中英 key 改 `t()`。confirmClearChat 硬编码接 B-260615-20 已记技术债一并清 — ProjectDetail/useAiStream/AiChat + i18n — 详见走查 ④
|
||||
- [ ] CR-260615-08 — **[P1]** i18n 硬编码一组(定时走查第4轮扩展:原记 3 处,实际 **40+ 处**;i18n key 树本身优秀 466 key zh/en 全对齐 0 缺,问题全在 src/ 硬编码)。**P1 直接破坏 i18n(最高优先)**:①useAiEvents:182 `tc.result='用户拒绝了此操作'` 绕过已有 aiTool.rejectedHint → en locale 拒绝提示恒中文(已有 en 翻译被一行废掉),**改一行删/换 t() 最高 ROI**;②TaskDetail.vue 整页 13 处未接 i18n(无 taskDetail namespace,需新建);③ProjectDetail 审批/导入 5 处(导入目录/确认N/取消/confirmDialog/Message);④Dashboard:76 暂无项目;⑤AiChat:107/471/687(title清空/confirmDialog/showToast)。**P2**:⑥ToolCard:152-164 ARG_LABEL_MAP 11 审批参数标签硬编码(aiTool 无 key,需新增 argLabels.*);⑦store error fallback 11 处全中文(knowledge/ideas/projects/tasks/workflow);⑧useAiSend:49/65/130(队列满/审批未送达);⑨useAiStream:52-53 看门狗两文案(原 CR-08 行号 39-41 偏差)。**P3**:⑩time.ts:46-51 相对时间中文(已有 common.justNow/minutesAgo 等 key 却没用,en locale 时间恒中文,隐蔽);⑪router meta.title 8 处(0 消费方死代码);⑫fallbackLocale:'en' 设计气味。修:按视图分批接入,**P1-1 改一行先做** — 全 src/ + i18n — 详见定时走查第4轮.md
|
||||
|
||||
**P1/P2 — DRY + 健壮性**
|
||||
- [ ] CR-260615-09 — **[P1]** 四份 .ai-md 样式逐字重复 ~180 行(ProjectDetail/Ideas/Knowledge/TaskDetail) → 抽全局 `src/styles/ai-md.css` 或 `<AiMarkdown>` 组件。B-24/25 复制粘贴源头 — 详见走查 ⑤
|
||||
- [ ] CR-260615-09 — **[P1]** 五份 .ai-md 样式逐字重复 ~350 行(AiChat/ProjectDetail/Ideas/Knowledge/TaskDetail) → 抽全局 `src/styles/ai-md.css` 或 `<AiMarkdown>` 组件。TaskDetail.vue:263-333 新文件继续抄(CR-09 复发,定时走查 06-15 确认第 5 份)。B-24/25 复制粘贴源头 — 详见走查 ⑤ + 定时走查 P0复核.md
|
||||
- [ ] CR-260615-10 — **[P2]** useMarkdown 加 `useRendered(getText)` 辅助,消除 renderedDesc computed + onMounted(loadMarkdown) 三处重复(与 CR-09 同源) — 详见走查 ⑥
|
||||
- [ ] CR-260615-11 — **[P2]** composables+ToolCard 健壮性一组:⑦switchConversation JSON.parse 无逐条容错(单条坏 args 清空整对话 useAiConversations:81) ⑧args 消费 `as any` 类型逃逸(ToolCard:241,328) ⑨approveToolCall 重复查找应复用 findToolCall(useAiSend:82) ⑩useAiEvents switch 缺 AiHeartbeat case(:118) ⑪approveHumanApproval 签名 decision/decisions 歧义(project.ts:255) ⑫ToolCard formatBytes/命名判定/key 兜底 — 详见走查 ⑦~⑫
|
||||
- [x] CR-260615-11 ✅(定时走查第2轮复核,2026-06-15,fddca9d) — ~~composables+ToolCard 健壮性一组~~ **[原 P2]** ⑦⑧⑨⑩⑪⑫ fddca9d 已实施(JSON.parse 逐条容错/argString 去 as any/复用 findToolCall/AiHeartbeat 显式 case/approveHumanApproval 签名收敛/formatToolName 兜底)。原问题:⑦switchConversation JSON.parse 无逐条容错(useAiConversations:81) ⑧args 消费 `as any` 类型逃逸(ToolCard) ⑨approveToolCall 重复查找(findToolCall) ⑩useAiEvents switch 缺 AiHeartbeat case ⑪approveHumanApproval 签名歧义 ⑫ToolCard 兜底。**⚠️⑪ 实施引入死逻辑**:workflow.ts:67-68 decision 三元两分支相同(见 CR-260615-18)— 详见走查 ⑦~⑫ + 定时走查第2轮.md
|
||||
- [ ] CR-260615-12 — **[P3]** 可选一组:_toastTimer 卸载未清(AiChat:436) + markdown 外层 span→div + SVG 常量散落 + appSettings 注释 + index 空行 + 路由 icon 语义 + options shape 校验 + projectNameById 共享 + useConfirm dev 警告 + i18n `t` as any — 详见走查 ⚪ 区
|
||||
- [x] CR-260615-18 ✅(定时走查第3轮,2026-06-15,f574562) — **[原 P2]** decision 三元两分支相同死逻辑(CR-11⑪ 实施引入):`workflow.ts:67-68` `selectType === 'multiple' ? decisions[0] ?? '' : decisions[0] ?? ''` 两分支返回值完全一致,三元判断无意义,误导维护者以为单/多选差异化。后端 workflow.rs:291-296 兜底(空 picked+非空 decision push)。修:直接 `const decision = decisions[0] ?? ''` 删三元 — src/stores/project/workflow.ts:67-68 — 详见定时走查第2轮.md
|
||||
- [x] CR-260615-19 ✅(定时走查第3轮,2026-06-15,f574562) — **[原 P3]** action 字段(保留为未来差异化刷新预留,注释已明确)定义 emit 但前端不消费:DfDataChangedPayload.action(api/types.ts:157) 后端 emit_data_changed(audit.rs:243-256) 每次计算,前端 listener(project.ts:33) 只解构 entity,action 忽略,刷新统一全量 load。契约冗余(白算白传)。修:删 action 或做差异化刷新(delete 本地移除)— src/api/types.ts:157 + project.ts:33 — 详见定时走查第2轮.md
|
||||
- [x] CR-260615-20 ✅撤销(定时走查第3轮,2026-06-15) — 走查误判:接收方核 workflow.ts:108 stopEventListener 写法一致(f574562 commit 说明),不修 — stopDataChangedListener try/catch 过度防御:project.ts:47-55 unlisten 同步数组操作正常不抛,与 workflow.ts:108 stopEventListener 写法不一致。修:核 workflow.ts:108 对齐统一去/留 — src/stores/project.ts:47-55 — 详见定时走查第2轮.md
|
||||
- [x] CR-260615-21 ✅(定时走查第4轮,2026-06-15,94cdcdc) — **[原 P3]** App.vue:218 `startDataChangedListener()` await 包 try/catch console.error(防 Tauri listen 失败 reject onMounted 致 AR-11 静默失效)。原问题:await 无 try/catch — src/App.vue:218 — 详见定时走查第3轮.md
|
||||
- [x] CR-260615-22 ✅(定时走查第6轮,2026-06-15,16aeacc) — **[原 P3]** types.ts 枚举注释对齐(Task/Idea/Project status)+ 删 AiConversationSummary.models 冗余 枚举注释与后端不符(api 垂直切片,定时走查第5轮代理 B):①TaskRecord.status(types.ts:88)注释 review_ready/merged/abandoned 错,后端 TaskStatus(types.rs:165)= todo/in_progress/in_review/testing/done/blocked/cancelled;②IdeaRecord.status(:11)漏 archived;③ProjectRecord.status(:39)漏 testing/releasing。status 字段是 string 非 literal union 不阻断,但注释误导。④AiConversationSummary.models(:233)冗余(后端返但前端无消费点)。修:注释对齐后端枚举/删冗余字段 — src/api/types.ts — 详见定时走查第5轮.md
|
||||
- [x] CR-260615-23 ✅撤销(定时走查第6轮,2026-06-15,不适用) — B-34「Tauri 不转」注释在 ARC-05 拆分时已简化(workflow.ts:81/project.ts:58 仅风格对齐无错误说明,16aeacc 接收方核对) — project.ts:273
|
||||
- [x] CR-260615-24 ✅(定时走查第7轮,2026-06-15,ef2d033) — **[原 P1]** 分离窗口 listener 双注册+永不清理(Dashboard+AI窗口垂直切片,定时走查第6轮代理 B R1+R2,新功能首次深查):①R1 双窗口重复消费 ai-chat-event:后端 app.emit 全局广播(stream_recv/agentic/commands/audit 多处)发给所有 webview,主窗口(AiChat.vue:790)+分离窗口(AiDetached 渲染 AiChat)各注册 listen,两窗口 handleEvent 都触发 → delta 各 += 一次/AiAgentRound 各 push 空气泡,靠 App.vue:94 v-if detach 时卸载主窗口 AiChat 间接停但 state 模块级单例 handleEvent 仍写(脆弱隐式契约);②R2 stopListener(useAiEvents.ts:258)定义但**全仓零调用**(地面 grep App/AiChat/AiDetached onUnmounted/onBeforeUnmount 都不调)→ listener+watchdog 永久泄漏。边界影响:reattach 重叠窗口期多一条空 assistant 消息。修:AiChat.vue onBeforeUnmount 补 store.stopListener()+detach 主窗口显式 stop/reattach start,或后端 emit_to(label) 定向 — src/composables/ai/useAiEvents.ts:258 + useAiWindow.ts + components/AiChat.vue:747 — 详见定时走查第6轮.md
|
||||
- [x] CR-260615-25 ✅(定时走查第8轮,2026-06-15,ac8dab3) — **[原 P2]** 分离窗口状态机缺口(代理 B R3/R4/R5):③R3 win.once('tauri://destroyed')置 detached=false 但不恢复 panelOpen=true(只 reattachPanel 显式设)→ X 关分离窗口后面板消失需手动 Ctrl+I;④R4 resumeInDetached(useAiWindow.ts:79-95)强行重建生成态(读 df-ai-gen 置 streaming=true push 空气泡)与后端 ai_is_generating 真值可能失配 → 假气泡/130s 后 watchdog 假超时;⑤R5 分离窗口 switchConversation(useAiWindow.ts:81)+loadConversations 内自动 switch,后端 AiSession 单例 activeConversationId 被覆盖,主窗口期间发消息可能落错会话(B-22 同类延伸)。修:destroyed 补 panelOpen=true/resumeInDetached 先 await ai_is_generating 核对/分离窗口只读不切 — src/composables/ai/useAiWindow.ts — 详见定时走查第6轮.md
|
||||
- [x] CR-260615-26 ✅(定时走查第7轮,2026-06-15,7637f29) — **[原 P2]** AR-11 listener 与 B-29 筛选契约交互 + Tasks 空态(列表页垂直切片,代理 A R-1/Y-2):①R-1 AR-11 监听器(project.ts:38)对 task 调 loadTasks() 无参 → 全量加载,破坏 B-260615-29「store.tasks 反映当前筛选」契约(filteredGroups 二次过滤兜住结果正确,但 store.tasks 变全量+性能);②Y-2 Tasks.vue:39-71 无 loading/error/empty 兜底,加载失败或无结果时空白页(store.error/loading 零引用,对比 Projects.vue:107 至少有空态)。修:listener 对 task 按 activeProject 筛选拉/Tasks 加 v-if loading/error/empty — src/stores/project.ts:38 + src/views/Tasks.vue — 详见定时走查第6轮.md
|
||||
- [x] CR-260615-27 ✅(定时走查第7轮,2026-06-15,5e6aa79) — **[原 P2]** Dashboard 一组(代理 B M1/M3/L4):①M1 refresh(Dashboard.vue:202-204)Promise.all 无 try/catch,任一接口失败 unhandledrejection+用户无反馈(onMounted 有 try/catch 但 refresh 无);②M3 getProjectStage(:150)status→stage 映射 planning 归 coding stage(颜色紫)但 label planning,颜色语义撒谎(cancelled 归 testing 同);③L4 决策/批注两 panel(:103-120)永远空态,CSS(:504-572)完整但模板未用=死代码。修:refresh 包 try/catch+toast/补 planning CSS 类/删死代码或接数据 — src/views/Dashboard.vue — 详见定时走查第6轮.md
|
||||
- [x] CR-260615-28 ✅(定时走查第8轮,2026-06-15,40461b2) — **[原 P1]** knowledge store state.error 死字段(error 通道前端断)+create 语义矛盾(stores 垂直切片,定时走查第7轮):①state.error(knowledge.ts:19,49,53,64,74,105)写入但**全应用零消费**(grep Knowledge.vue store.error 零命中)→ 用户遭遇加载/检索/配置失败只看到空白列表无错误提示,**CR-08「store error fallback 11 处中文」深化**:不仅中文硬编码,整个 error 通道前端断的;②create(:80-85)成功后 loadCandidates 失败把错误塞 state.error,create 已返回 record 给调用方认为成功,语义自相矛盾。修:Knowledge.vue 接 error banner/toast 或删 state.error 改 throw 由调用方 catch + create 内 loadCandidates 失败不污染 error — src/stores/knowledge.ts — 详见定时走查第7轮.md
|
||||
- [x] CR-260615-29 ✅(定时走查第9轮,2026-06-15,6116c9b) — **[原 P2]** ai.ts messages 单对话无上限 + knowledge 异步竞态/错误不一致(定时走查第7轮):①ai.ts state.messages(useAiSend push user+ai/useAiEvents push err 审批/useAiStream push delta)单对话内线性增长,长会话+大工具结果内存可观(切换对话整体替换是减负点,同对话不轮换),建议软上限/滚动淘汰;②knowledge.ts 加载/检索/配置异步无竞态保护(messages 有 switch token,items/config 无),快速连点后返回覆盖先;③knowledge 错误处理不一致(load*/search 有 catch 兜底,create/updateStatus/archive/saveConfig/extractNow 直抛)。修:messages 软上限/items+config 加请求序号/错误处理统一 — src/stores/ai.ts + knowledge.ts — 详见定时走查第7轮.md
|
||||
- [x] CR-260615-30 ✅(定时走查第8轮,2026-06-15,b300bb7) — **[原 P3]** appSettings useSetting 对象/数组值深 mutate 不落库(定时走查第7轮边界):useSetting 双 watch 用 Object.is 守卫防循环,但对对象/数组值深 mutate(r.value.x=1)时 Object.is(v,cache[key]) 同引用恒 true → 不触发 set 落库,静默不持久化。当前无 bug(useSetting 多绑值类型,df-ai-ui 走 set 整对象替换非 mutate),但隐式契约。修:文档注明 useSetting 仅值类型,或对象值用 JSON 序列化比较 — src/stores/appSettings.ts:88-113 — 详见定时走查第7轮.md
|
||||
- [x] CR-260615-31 ✅(定时走查第9轮,2026-06-15,2ddd294) — **[原 P2]** 小区域收尾健壮性一组(定时走查第8轮,无必修):①useConfirm 并发 confirmDialog 覆盖 resolve(useConfirm.ts:35-47),前一个 Promise 永挂(连点两删除/confirm 交错时)→ 静默中断+轻微内存泄漏,修:进入时 resolve?.(false) 打断前一个视为取消;②ToolCardList collapseInactive(:47-51)只清 expandedCards 漏清 expandedTools,已完成卡片折叠后内容展开态残留 Set,修:对称过滤或注释明确有意保留 — src/composables/useConfirm.ts + components/ToolCardList.vue — 详见定时走查第8轮.md
|
||||
|
||||
### 📄 文档核对待修项(2026-06-15)
|
||||
|
||||
> 4 路并行核对 docs 全量 + 根目录文档,详见 [文档全量核对报告-2026-06-15.md](./05-代码审查/文档全量核对报告-2026-06-15.md)。悬空链接 0。
|
||||
|
||||
**P0 — 文档硬伤(按文档找代码落空)**
|
||||
- [ ] DOC-260615-01 — **[P0]** ARCHITECTURE.md 数据模型整段重写(14 表 6 虚构 artifacts/connections/features/test_cases/test_runs/annotations/decisions + 7 遗漏 node_executions/branches/ai_conversations 等;workflow_defs/runs 合并 workflow_executions)— ARCHITECTURE.md:262-475 — 详见核对报告 §1
|
||||
- [ ] DOC-260615-02 — **[P0]** df-nodes 模块文档重写(8 节点全骨架 → 实际 3 节点 ai/script/human 均完整实现;删 Docker/Git/Notify/HTTP/Subflow 5 虚构)— 03-模块文档/df-nodes — 详见 §2
|
||||
- [ ] DOC-260615-03 — **[P0]** df-ai 模块文档删虚构 router.rs/stream.rs + 已删 supported_features trait + 12→13 工具 — 03-模块文档/df-ai — 详见 §3
|
||||
- [ ] DOC-260615-04 — **[P0]** 使用手册全面重写 7 处(运行命令 bun→npm / 任务状态 4→7 态 / 优先级方向 / 节点类型 Script/Ai/Human / EventBus 非 WebSocket / 多 Provider / 知识库 Tier1)— 08-用户指南/使用手册 — 详见 §4
|
||||
|
||||
**P1 — 审查状态断层**
|
||||
- [ ] DOC-260615-05 — todo 补全局 review R-P1-1~6/R-P2-1~13 已完成回写(报告说全闭环 126 test pass,todo 零条目)— 已完成区补汇总 — 详见 §5
|
||||
- [ ] DOC-260615-06 — 架构复核报告 §5 新发现 11 项补 todo(复核-新①③④⑪ 待补:pendingToolCalls 二次 token / NodeCancelled 事件语义 / cancel 终态守卫 / 主窗口幽灵 watchdog;余已被全局 review 取代)— 详见 §6
|
||||
- [ ] DOC-260615-07 — aichat §8 表 9 行 + 工作流审批 TL;DR 已修项补 ✅ 勾选(H1/H2/create_project/AR-5~10 + human_node.rs:41 await B-03b-R6)— 详见 §7
|
||||
- [ ] DOC-260615-08 — 近期改动 CR-03 未做子项拆独立 todo(audit.rs 三臂 / openai Error::from / ToolCard projectNameById find)— 详见 §8
|
||||
|
||||
**P1 — 架构/模块文档过期**
|
||||
- [ ] DOC-260615-09 — df-workflow 模块文档 4 处过期(NodeRegistry 删 Default / conditions 默认 true→false / try_recv_human_approval 已删 / set_waiting·set_skipped 已删)— 详见 §9
|
||||
- [ ] DOC-260615-10 — df-storage V8→V13(迁移版本+表数 11→18+Repo 11→17)+ 对抗评估 idea_evaluations 设计稿标未实施 + df-execute 描述删 Docker/SSH/Git — 详见 §10-12
|
||||
|
||||
**P1 — 索引/计数矛盾**
|
||||
- [ ] DOC-260615-11 — 13→8 crate 多处统一(PROGRESS/INDEX/Phase1决策/对抗裁决/想法探索)+ commands 数 47/57/68 统一 68 + URGENT.md 停用标注或删 — 详见 §13/15/16(INDEX 漏列部分本会话已补,见 DOC-14)
|
||||
|
||||
**P2 — 迭代/Phase/规范**
|
||||
- [ ] DOC-260615-12 — DEVFLOW-1~4 + Phase1/Phase2 标完成或归档(Phase1 已收尾文档仍全待实施)— 详见 §17-19
|
||||
- [ ] DOC-260615-13 — todo 肥大瘦身(大段详情移 docs 子目录)+ 重复收口(HumanNode 审批/密钥迁移/FR-S1)+ 文档记录规范在 INDEX 突出 — 详见 §20-22
|
||||
|
||||
**P3 — 技术/工具文档**
|
||||
- [ ] DOC-260615-14 — Tauri-IPC/View 改造指南全篇过时重写或归档 + SQLite 表数 V1-V9→V1-V13 + 锚点顿号 — 详见 §23-26
|
||||
|
||||
### 🟡 B-03b 复核后续(独立深度复核,2026-06-14)
|
||||
|
||||
@@ -278,7 +326,18 @@
|
||||
- [x] B-260615-21 ✅(P0批,2026-06-15,待commit) — ~~sendMessage catch 回滚 user message~~ push user msg 前捕获 const userMsgId(原内联未生成)+catch 块 filter 改 m.id!==aiMsgId && m.id!==userMsgId 一并回滚;机制:user/ai msg 均 nextMsgId() 唯一 id+push 进 state.messages(无索引依赖,按 id 过滤最稳) — useAiSend.ts
|
||||
|
||||
**P2 — 前后端状态同步**
|
||||
- [ ] B-260615-22 — [P2] 前后端状态不同步:前端 state.streaming 与后端 generating 各自维护,后端卡死/异常退出时前端不知情(streaming 可能已被 AiError 复位 false,预检 useAiSend.ts:36 放行撞后端 :45 拦截)。评估:发送前 IPC 查后端真实 generating,或卡死主动 emit 状态同步事件。
|
||||
- [ ] B-260615-22 — [P2] ~~前后端状态不同步~~ → **由 B-260616-01(L0 握手) + B-260616-02(L2 强制发送) 解决**,本条保留为问题记录不再独立实施
|
||||
- [ ] CR-260615-12 — **[P3]** 可选一组:_toastTimer 卸载未清(AiChat:436) + markdown 外层 span→div + SVG 常量散落 + appSettings 注释 + index 空行 + 路由 icon 语义 + options shape 校验 + projectNameById 共享 + useConfirm dev 警告 + i18n `t` as any — 详见走查 ⚪ 区
|
||||
- [ ] CR-260615-09 — **[P1]** 五份 .ai-md 样式逐字重复 ~350 行(AiChat/ProjectDetail/Ideas/Knowledge/TaskDetail) → 抽全局 `src/styles/ai-md.css` 或 `<AiMarkdown>` 组件。TaskDetail.vue:263-333 新文件继续抄(CR-09 复发,定时走查 06-15 确认第 5 份)。B-24/25 复制粘贴源头 — 详见走查 ⑤ + 定时走查 P0复核.md
|
||||
- [ ] CR-260615-10 — **[P2]** useMarkdown 加 `useRendered(getText)` 辅助,消除 renderedDesc computed + onMounted(loadMarkdown) 三处重复(与 CR-09 同源) — 详见走查 ⑥
|
||||
- [ ] CR-260615-08 — **[P1]** i18n 硬编码一组(定时走查第4轮扩展:原记 3 处,实际 **40+ 处**;i18n key 树本身优秀 466 key zh/en 全对齐 0 缺,问题全在 src/ 硬编码)。**P1 直接破坏 i18n(最高优先)**:①useAiEvents:182 `tc.result='用户拒绝了此操作'` 绕过已有 aiTool.rejectedHint → en locale 拒绝提示恒中文(已有 en 翻译被一行废掉),**改一行删/换 t() 最高 ROI**;②TaskDetail.vue 整页 13 处未接 i18n(无 taskDetail namespace,需新建);③ProjectDetail 审批/导入 5 处(导入目录/确认N/取消/confirmDialog/Message);④Dashboard:76 暂无项目;⑤AiChat:107/471/687(title清空/confirmDialog/showToast)。**P2**:⑥ToolCard:152-164 ARG_LABEL_MAP 11 审批参数标签硬编码(aiTool 无 key,需新增 argLabels.*);⑦store error fallback 11 处全中文(knowledge/ideas/projects/tasks/workflow);⑧useAiSend:49/65/130(队列满/审批未送达);⑨useAiStream:52-53 看门狗两文案(原 CR-08 行号 39-41 偏差)。**P3**:⑩time.ts:46-51 相对时间中文(已有 common.justNow/minutesAgo 等 key 却没用,en locale 时间恒中文,隐蔽);⑪router meta.title 8 处(0 消费方死代码);⑫fallbackLocale:'en' 设计气味。修:按视图分批接入,**P1-1 改一行先做** — 全 src/ + i18n — 详见定时走查第4轮.md
|
||||
|
||||
### 🔴 前后端状态分裂根治(HMR/刷新/切窗口自愈)
|
||||
|
||||
> 用户实测(06-15):DEV 运行中改代码触发 Vite HMR → 前端重载但后端 Tauri 进程未重启 → `generating=true` 残留 → 所有新消息被拦(`ai_chat_send` 同步检查)→ 入队死锁 10+ 分钟不恢复。**根因**:前后端独立生命周期无 reconciliation 协议。**否决超时方案**(5-10min 等死体验差)。选 L0 重接握手(零延迟自愈) + L2 发送韧性(用户可控)。
|
||||
|
||||
- [ ] B-260616-01 [P0] — **L0 前端重连握手(状态分裂根治)**。前端每次 startListener 完成后 emit `ai-client-ready` 事件(携带 client_id + last_conv_id);后端注册 listener 收到后:①`generating=true`? → 强制 reset + emit AiCompleted(补偿事件让前端收尾清 watchdog/续发队列)②`pending_approvals` 有残留? → 清除(审批态已无前端 UI 可操作)③记录 `last_heartbeat = now`。覆盖场景:Vite HMR / 页面 F5 / 分离窗口重建 / 切换标签页后台回收。**改动**:前端 `useAiEvents.ts:startListener()` 末尾加 emit(~3 行);后端 `mod.rs` 或新建 `commands/ai/handshake.rs` 注册 listener + 状态清洗(~30 行)。**用户体验**:HMR 后零延迟自动恢复,无需用户干预 — src/composables/ai/useAiEvents.ts + src-tauri/src/commands/ai/mod.rs
|
||||
- [ ] B-260616-02 [P1] — **L2 发送时强制覆盖(用户可控自救)**。当前行为:`generating=true` → `ai_chat_send` 返回 Err → 前端 catch 静默入队 → 死锁无出口。改进三级降级:①正常(generating=false)→ 直接发送 ②排队(<30s, generating=true)→ 入队 + UI 显示「⚳ 排队中 N 条」+ 可取消按钮 ③异常(>30s 或 用户主动)→ 弹确认「检测到生成状态异常,是否强制发送?」→ 调用新 IPC `ai_chat_force_send` 绕过 generating 检查(后端先 reset generating 再正常走 send 流程)。**关键设计**:30s 后给用户选择权而非永远静默等。**改动**:前端 `useAiSend.ts` sendMessage 加超时分支 + confirm 弹窗;后端 `commands.rs` 新增 `ai_chat_force_send`(复用 ai_chat_send 逻辑,前置 reset generating)。关联 B-10(软复位已做 newConversation, force_send 同模式) — src/composables/ai/useAiSend.ts + src/components/AiChat.vue + src-tauri/src/commands/ai/commands.rs
|
||||
|
||||
### 🔴 详情描述字段 Markdown 未渲染(2026-06-15 用户报障)
|
||||
|
||||
@@ -308,6 +367,80 @@
|
||||
|
||||
- [x] B-260615-31 ✅(2026-06-15,待commit) — ~~TaskDetail + ProjectDetail 字段同行布局~~ `.info-item` flex-direction column→row + align-items baseline + gap 12px + `.label` min-width 88px flex-shrink:0(TaskDetail `.value` 加 flex:1 min-width:0 占余);描述字段 info-item 加 `info-block` class + CSS `.info-item.info-block{flex-direction:column}` 保块状(长文本独占整行);ProjectDetail `.path-row`/`.info-tags` 已 flex-wrap 无溢出风险。vue-tsc 0 err — src/views/TaskDetail.vue + src/views/ProjectDetail.vue
|
||||
|
||||
### 🔴 run_command stdout/stderr 恒空(2026-06-15 用户实测)
|
||||
|
||||
> 用户实测 F-05 run_command:命令能跑(副作用如写文件发生)+ exit_code 正常,但 **stdout 始终返回空字符串**,echo/Write-Output 等均如此 → "写→跑→**看**→改"闭环断在「看」,跑测试/构建无法获取输出。**根因(铁证)**:`crates/df-execute/src/shell.rs:37-58` 构造 `tokio::process::Command` 时**漏设 `cmd.stdout(Stdio::piped()).stderr(Stdio::piped())`**。tokio 默认 `Stdio::inherit()`(输出继承父进程 Tauri app → 控制台/devnull),`wait_with_output()` 读不到 pipe → `output.stdout/stderr` 空 Vec → `from_utf8_lossy` → 空串。F-05 plan doc 也未提 Stdio 设置,实现遗漏。**PS 静默失败(.Replace()/-replace/Set-Content exit_code=0 文件未变)疑非独立 bug**:修了 piped 后 stderr/stdout 可观测,方能定位是「PS Replace 源不匹配」(AI 用法)还是「cmd /C 调 PS 引号问题」(shell.rs:38 Windows 用 cmd /C,PS 语法引号嵌套易错)。**B-38 待 B-37 修复后据观测重评**。
|
||||
|
||||
- [ ] B-260615-37 [P0] — **shell.rs execute 补 Stdio::piped()**。`shell.rs:58` spawn 前 加 `cmd.stdout(Stdio::piped()).stderr(Stdio::piped())`(需 `use tokio::process::Stdio;`)。修后 stdout/stderr 正常捕获,run_command 闭环恢复。最小改动 1 行 + import — crates/df-execute/src/shell.rs:37-58 — source:用户实测(06-15)
|
||||
- [ ] B-260615-38 [P1] — **PS 静默失败根因**(待 B-37 修后据 stderr 观测重评)。候选:a) PS Replace/-replace 源字符串不匹配(AI 用法,非 bug);b) shell.rs:38 Windows `cmd /C` 不适配 PS 语法(引号嵌套/管道),需支持 shell 选择(powershell -NoProfile -Command / cmd /C / sh -c)。B-37 修复前无法定位 — crates/df-execute/src/shell.rs:37-39 — source:用户实测(06-15)
|
||||
|
||||
### 🔴 查看灵感来源跳空白页(2026-06-15 用户报障)
|
||||
|
||||
> 用户报障:项目详情点「来源灵感」(i18n key `projectDetail.sourceIdea`) 跳转后页面空白。**根因双锁**:① `ProjectDetail.vue:71` `router-link :to="/ideas/${idea_id}"` 指向 `/ideas/:id`,但**路由表(router/index.ts:21-25)只有 `/ideas` 精确路由,无 `:id` 子路由** → hash 路由无匹配 → 渲染空白(路由表无 404 catch-all 兜底)。② 即便补路由,`Ideas.vue` 不读 route params——`:209` import 无 `useRoute` / `:221` 只 `useRouter` / `:448-450` onMounted 只 `store.loadIdeas()` 不接 params → `selectedId`(:230) 纯内部 ref,无法 deep link 选中灵感。**数据层 OK**:`Project.idea_id` 字段(models.rs:38)+晋升写入(idea.rs:124),ProjectDetail 拿得到 idea_id。**对照**:`/projects/:id` `/tasks/:id` 路由已建(独立详情页),Ideas 是双栏列表+详情同页故未建 `:id`,但 ProjectDetail 反向溯源需 deep link。
|
||||
|
||||
- [ ] B-260615-36 [P1] — **查看灵感来源跳空白页**。修复两层:① `router/index.ts` 加 `/ideas/:id` 路由(name `IdeasDetail` 或复用 Ideas 组件);② `Ideas.vue` 加 `useRoute` + `onMounted` 读 `route.params.id`→`selectedId.value=id`(loadIdeas 后校验 id 在 `store.ideas` 中,不在则提示「灵感不存在/已删」+清 selectedId);可选 watch `route.params.id` 支持从详情再点别的灵感切换。— src/router/index.ts:21-25 + src/views/Ideas.vue:209,221,448-450 — source:用户报障(06-15)
|
||||
|
||||
### 🟡 AiChat 气泡拷贝能力(2026-06-15 用户需求)
|
||||
|
||||
> 用户需求:AiChat 消息气泡支持一键拷贝——**文本拷贝**(content 原文→剪贴板) + **截图拷贝**(气泡 DOM 渲染→图片→剪贴板,粘贴出图片)。**现状**:全项目零 clipboard 实现(grep `navigator.clipboard`/`copy`/`clipboard`/`拷贝`/`复制` 零命中)。气泡结构:用户消息 `.ai-msg-bubble--user`(纯文本 `{{msg.content}}`, :185);AI 消息 `.ai-msg-bubble--ai.ai-md`(v-html 渲染 MD, :197)。**Tauri WebView2(Edge)**:Clipboard API 完整支持(`writeText`/`write([ClipboardItem])`),无需额外 plugin(做减法,不用 `@tauri-apps/plugin-clipboard-manager`)。**安全约束**:必须在用户点击事件(手势)内调用剪贴板 API。
|
||||
|
||||
- [ ] F-260615-13 [P2] — **AiChat 气泡文本拷贝**。每条消息气泡右上角加拷贝按钮(📋 icon,hover 显示)。点击 → `navigator.clipboard.writeText(msg.content)` → 短暂 toast「已复制」。AI 气泡拷原始 content(非 v-html 渲染后 HTML,防 HTML 标签污染);用户气泡同。按钮位置:`.ai-msg-bubble` 内右下角或 hover 时右上角浮现(不占空间,信息密度优先)。i18n 加 `aiChat.copied` / `aiChat.copyMsg`。— src/components/AiChat.vue(:185 用户/:197 AI) + src/i18n
|
||||
- [ ] F-260615-14 [P2] — **AiChat 气泡截图拷贝(图片)**。同位置第二按钮(🖼️ 或与文本拷贝合并为下拉)。技术链:`html2canvas(bubbleElement)` → canvas → `canvas.toBlob('image/png')` → `new ClipboardItem({ 'image/png': blob })` → `navigator.clipboard.write([item])`。需加依赖 `html2canvas`(~90KB,或轻量替代 `dom-to-image-more` ~3KB,待选型)。**注意**:html2canvas 对 CSS 支持有限(阴影/渐变/CSS 变量可能不全),但气泡样式简单(ai-md 基本色+圆框),风险低。粘贴目标:Word/微信/钉钉等富文本编辑器可接图片;纯文本框不行(正常行为)。— src/components/AiChat.vue + 新依赖(html2canvas/dom-to-image-more 待选)
|
||||
|
||||
### 🟡 AI Chat 交互体验改进(2025-07-15 方案 → 待办化)
|
||||
|
||||
> 来源:[aichat交互体验改进方案-2025-07-15.md](./02-架构设计/aichat交互体验改进方案-2025-07-15.md)。7 大类 20 个改进项,按原文优先级分 4 批。**去重**:§1.3 复制 ⊂ F-260615-13/14(已写)、§3.4 新建中断 ⊂ B-260615-10(已做软复位,本文档加 confirm 增强)、§6.2 分离窗口同步 ⊂ CR-24/25(已写)。**状态: 待讨论→看板跟踪**。
|
||||
|
||||
**第一批 — 最高频痛点(每次对话都遇到)**
|
||||
|
||||
- [ ] UX-2025-01 [P1] — **流式渲染中选中文字保持**(§2.1)。现象:AI 流式输出时选文字被 delta 打断丢失选区。方案 A(推荐):`selectionchange` + 选区在消息容器内 → 暂停 rAF 流式 parse → 选区结束恢复;方案 B:已完成块独立 DOM 节点不参与 v-html 替换。关联 ARC-08 块级 memo(splitBlocks 已按块切分,末块单独更新天然支持方案 B)— src/components/AiChat.vue + src/composables/ai/useAiEvents.ts
|
||||
- [ ] UX-2025-02 [P1] — **消息操作栏(复制 / 重新生成)**(§1.2+§1.3)。AI 气泡 hover 显示操作栏:「📋 复制」+「🔄 重新生成」。复制 = F-260615-13 合并实现;重新生成:后端删最后一条 AI 消息 + 用倒数第二条 user 消息重跑 agentic loop(新增 `ai_regenerate` IPC)。代码块额外 hover 浮出「📋 复制代码」按钮(§2.2 子项)。— src/components/AiChat.vue + src-tauri/src/commands/ai/commands.rs
|
||||
- [ ] UX-2025-03 [P1] — **错误气泡操作入口**(§4.1)。错误气泡底部增加按钮区:「🔁 重试」(取上一条 user 消息重发)+「⚙ 去设置」(仅 auth/provider 错误时显示,跳 Settings→AI Tab)。后端 `AiError` 加 `error_type: Option<ErrorType>` 枚举(auth/network/timeout/provider_config/unknown),前端据此显隐按钮。— src-tauri/src/commands/ai/mod.rs(AiChatEvent) + src/api/types.ts + src/components/AiChat.vue
|
||||
- [ ] UX-2025-04 [P1] — **断线保文**(§4.2)。流式中断时已接收文本不丢弃:`stream_llm` mid-stream error 改返回 `Some(partial_text, ...)` + `incomplete: true` 标志;agentic 收到 incomplete → 正常入库 + emit AiCompleted(非 AiError)+ 追加系统提示「⚠ 响应因网络中断不完整」;前端 AI 气泡底显示「继续生成」按钮。关联 B-260615-05(流尽空内容报错)同改 stream_recv.rs。— crates/df-ai/src/stream_recv.rs + src-tauri/src/commands/ai/agentic.rs + src/components/AiChat.vue
|
||||
|
||||
**第二批 — 显著提升日常效率**
|
||||
|
||||
- [ ] UX-2025-05 [P2] — **代码块语法高亮 + 复制按钮**(§2.2)。集成 highlight.js(按需加载不阻塞首屏),marked renderer code 回调调 `hljs.highlightAuto`;代码块右上角 hover 浮出「📋 复制代码」。依赖 ~30KB(highlight.js core + 常用语言包)。— src/composables/useMarkdown.ts + 新依赖 highlight.js
|
||||
- [ ] UX-2025-06 [P2] — **对话搜索**(§3.1)。侧栏 header 加搜索框(实时过滤 conversations 匹配 title);搜索时取消分组平铺展示;支持 `Ctrl+K` 快捷键聚焦。— src/views/(AiChat|Sidebar).vue
|
||||
- [ ] UX-2025-07 [P2] — **键盘快捷键**(§7.1)。Ctrl+N 新建 / Ctrl+K 搜索对话 / Ctrl+L 清空 / Ctrl+Shift+C 复制 AI 消息 / Ctrl+R 重新生成 / Esc 关闭面板 / Ctrl+B 切侧栏。全局监听 AiChat 聚焦时生效。— src/components/AiChat.vue
|
||||
- [ ] UX-2025-08 [P2] — **新建对话 confirm 弹窗**(§3.4 增强)。生成中点新建对话时弹 ConfirmDialog(「当前正在生成,确定中断并新建?」),确认后才执行 B-260615-10 的软复位逻辑。防误触中断。— src/components/AiChat.vue + src-tauri/src/commands/ai/commands.rs
|
||||
|
||||
**第三批 — 锦上添花**
|
||||
|
||||
- [ ] UX-2025-09 [P2] — **编辑已发送消息并重新生成**(§1.1)。用户气泡 hover 显示"✏️ 编辑",点击回填输入框;发送时后端:①替换目标 message content ②后续所有消息标记 `status='truncated'`(软删除,保留历史可追溯,与 WF-A soft_delete 模式一致)③用 status='active' 的历史重跑 agentic loop。约束:只允许编辑最后一条 user 消息(中间编辑语义复杂+易乱上下文);generating 中拦住。前端:截断消息从视图移除,loading 等新回复。后端新增 `ai_chat_edit(message_id, new_content)` IPC。— src/components/AiChat.vue + src-tauri/src/commands/ai/(commands.rs|conversation.rs) + df-storage(messages 表加 status 列)
|
||||
- [ ] UX-2025-10 [P2] — **@ 实体引用**(§1.4)。输入框支持 `@` 触发实体联想浮层(复用 `/` 技能联想 popover 架构);联想源:项目列表/任务列表/最近文件;选中展开为 `[项目: xxx]` 标记文本,后端 system prompt 注入上下文摘要。关联 F-260614-02(技能联想)。— src/components/AiChat.vue + src-tauri/src/commands/ai/prompt.rs
|
||||
- [ ] UX-2025-11 [P2] — **输入框高度提升**(§1.5)。textarea 最大高度 120px→200px(~10 行),超过后再内部滚动。或可拖拽 resize handle。— src/components/AiChat.vue
|
||||
- [ ] UX-2025-12 [P2] — **历史消息分页懒加载**(§2.3)。⏸️ **暂缓**:当前典型对话 <100 条消息,v-for 全量渲染无卡顿;等用户反馈长对话卡顿再做。switchConversation 首次加载最近 50 条,滚顶加载更多(需后端 offset/limit 支持 messages 查询)。或前端虚拟滚动替代。关联 B-260629(messages 无上限)。— src/stores/ai.ts + src/composables/ai/useAiConversations.ts + 后端 conversation.rs
|
||||
- [ ] UX-2025-13 [P2] — **消息时间戳**(§2.4)。气泡下方极小字号(dim 色)展示相对时间(复用 time.ts 已有 common.justNow 等 key);hover tooltip 展示完整时间。— src/components/AiChat.vue
|
||||
- [ ] UX-2025-14 [P2] — **Provider 切换反馈**(§5.1)。cycleProvider 切换后 toast 提示 + 0.15s 淡入动画 + bar 展示 model 名称小字。— src/stores/ai.ts + src/components/AiChat.vue
|
||||
- [ ] UX-2025-15 [P2] — **技能参数用法展示**(§5.2)。联想浮层每项增加 argument_hint 等宽小字提示;选中技能后输入框上方 chip 展示完整参数格式说明。关联 F-260614-02(技能联想)。— src/components/AiChat.vue
|
||||
- [ ] UX-2025-16 [P2] — **侧栏宽度可拖拽**(§6.1)。侧栏右边缘 2px 拖拽条(cursor: col-resize),范围 120~280px,宽度持久化 df-ai-ui 设置。— src/components/AiChat.vue (sidebar CSS)
|
||||
- [ ] UX-2025-17 [P3] — **对话置顶**(§3.2)。ai_conversations 表加 pinned 字段;排序改为 pinned DESC, updated_at DESC;侧栏 hover 图钉按钮。— df-storage + src/views/Sidebar.vue + src/stores/ai.ts
|
||||
- [ ] UX-2025-18 [P3] — **对话导出**(§3.3)。后端新增 ai_conversation_export(conv_id, format) 支持 markdown/json/txt;前端侧栏 hover 导出按钮。Markdown 格式:## 用户 / ## 助手交替 + 代码块保留围栏。— src-tauri/src/commands/ai/conversation.rs + src/views/Sidebar.vue
|
||||
- [ ] UX-2025-19 [P3] — **消息列表虚拟滚动**(§7.2)。⏸️ **暂缓**:同 UX-12,当前数据量无需虚拟滚动;与 UX-12 合并为一条观察项,等用户反馈长对话卡顿再启动。长对话几百条消息全量渲染卡顿;集成 vue-virtual-scroller 或自研 IntersectionObserver 懒渲染;流式最后一条始终保持挂载。— src/components/AiChat.vue + 新依赖
|
||||
- [ ] UX-2025-20 [P3] — **空状态引导 + 标题过渡**(§7.3+§7.4)。空态展示 3-4 示例问题卡片(点击自动填入发送);无 provider 时引导配置;标题生成后 0.3s 淡入动画 + AI 生成标识图标。— src/components/AiChat.vue
|
||||
|
||||
### 🟡 AI Chat 授权体验改进(2025-07-15 方案 → 待办化)
|
||||
|
||||
> 来源:[aichat授权体验改进方案-2025-07-15.md](./02-架构设计/aichat授权体验改进方案-2025-07-15.md)。6 痛点 + 8 改进项,按原文优先级转待办。**状态: 待讨论→看板跟踪**。
|
||||
|
||||
**P0 — 快速改善体感(第一批建议落地,~2 天工作量)**
|
||||
|
||||
- [ ] AE-2025-01 [P0] — **批量审批**。同一轮多 pending 时,ToolCardList 顶部显示「全部批准(N) / 全部拒绝」按钮。改动:①`ToolCardList.vue` 加批量操作栏(监听 pending_approval 数量)②`useAiSend.ts` 新增 `approveAll(rejectAll)` 循环调 `ai_approve`。预估 0.5 天 — src/components/ToolCardList.vue + src/composables/ai/useAiSend.ts
|
||||
- [ ] AE-2025-02 [P0] — **审批计数器 + 跳转**。输入框上方/header 显示 `⏳ N 项待审批` 徽标,点击跳转到第一个 pending 卡片。改动:①`AiChat.vue` header 区域加审批徽标 ②`ToolCardList.vue` 暴露 `scrollToFirstPending` 方法。预估 0.5 天 — src/components/AiChat.vue + src/components/ToolCardList.vue
|
||||
- [ ] AE-2025-03 [P0] — **write_file diff 预览**。write_file 审批时若文件已存在,展示前后对比 diff 而非裸 content(content 可能几百行代码截断到 300 字符)。改动:①后端 `tool_registry.rs` write_file handler 执行前读旧文件返回 diff ②`ToolCard.vue` pending_approval + name=write_file 时渲染 diff 视图(红删绿增)。关联 F-260615-10(diff 工具)可复用。预估 1 天 — src-tauri/src/commands/ai/tool_registry.rs + src/components/ToolCard.vue
|
||||
|
||||
**P1 — 增强控制力**
|
||||
|
||||
- [ ] AE-2025-04 [P1] — **会话级授权(Session Trust)**。替代原"全局宽松模式"。核心:信任是上下文相关的——同一会话内用户批准过某类操作后,后续同类操作自动放行;换会话清空重审。模型:AiSession 加 `session_trust: HashSet<TrustKey>`(内存态,随会话销毁);TrustKey 按操作+目标范围粒度(如 `Write { dir: "src/" }`、`Execute { dir: workspace }`)。流程:首次操作→pending_approval 用户批准→记录 TrustKey → 同会话后续同类操作命中则自动放行(消息流显示轻量提示 `🔓 自动放行: write_file(xxx)`)。**白名单收紧决策**:纯读取(list_*/read_*/list_directory)始终自动放行;创建/修改/绑定/覆盖写入/高危全部需审批或会话级授权放行(bind_directory 归类为修改操作;write_file 覆盖写入已出事故不放行)。**首批实现**:write_file + run_command(写→跑→看→改闭环最高频连续操作);create/update 后续按需加。改动:①`mod.rs` AiSession 加 session_trust 字段 + TrustKey enum ②`audit.rs` process_tool_calls 审批前查 session_trust 命中则跳过 pending ③审批通过时写 session_trust ④前端自动放行操作显示轻量 toast 提示。关联 AE-05(High 二次确认: 会话级授权的操作是否也需二次确认? 建议: 首次审批走二次确认,后续自动放行跳过)。预估 1.5 天 — src-tauri/src/commands/ai/(mod.rs|audit.rs) + src/components/AiChat.vue — **设计决策(2026-06-15)**: 目录级粒度(非文件级)、仅写+执行首批、Webhook 未来走独立 execution_token 链路不影响本设计
|
||||
- [ ] AE-2025-05 [P1] — **High 二次确认**。delete/purge/run_command 等 High 风险操作,批准后弹 inline 确认("确定要永久删除?此操作不可恢复"),二次确认才真正执行 ai_approve。可用现有 ConfirmDialog 组件。预估 0.5 天 — src/components/ToolCard.vue
|
||||
- [ ] AE-2025-06 [P1] — **审批超时(人工审批场景)**。pending_approval 超时自动 reject(默认 5 分钟),防对话永久卡死。**决策:纯前端定时器**(审批依赖页面交互,页面关闭 auto-reject 合理;不改后端)。超时后 toast 提示「⚰ 审批超时,已自动拒绝: {tool_name}({reason})」+ 调 ai_approve(id, false)。Settings 可配超时常量(3/5/10min)。**范围限定**:仅覆盖人工审批场景。未来 Webhook/外部集成走独立 `execution_token` 预鉴权链路(不经过 pending_approvals),不影响本设计。预估 0.5 天 — src/composables/ai/useAiSend.ts + src/views/Settings.vue
|
||||
|
||||
**P2 — 信息透明度**
|
||||
|
||||
- [ ] AE-2025-07 [P2] — **Agentic 进度条**。消息区域底部显示循环进度(`🔄 循环 3/10 · ⏳2待审批 · ✅5完成`)。改动:①`AiChat.vue` 底部加进度指示条 ②`useAiEvents.ts` 处理 AiAgentRound 时更新进度。预估 0.5 天 — src/components/AiChat.vue + src/composables/ai/useAiEvents.ts
|
||||
- [ ] AE-2025-08 [P2] — **审批历史面板**。独立页面展示 `ai_tool_executions` 表审计记录(时间/工具/风险/状态/决策者/参数摘要/结果摘要)。改动:①后端新增 `list_tool_executions` IPC ②前端新建 AuditLog.vue 表格视图。预估 1 天 — src-tauri/src/commands/ai/(commands|audit).rs + 新建 src/views/AuditLog.vue
|
||||
|
||||
### 待澄清 / A-B 待定
|
||||
|
||||
- [ ] S-260614-01 — 「显示多开」需求待澄清 — 用户报"设置勾选显示多开但 AiChat 未显示",全 src grep 零命中,疑似旧版本/指分离窗口/想新增开关,待用户截图确认 (06-14)
|
||||
@@ -333,8 +466,64 @@
|
||||
- [x] T-260614-12 — ~~df-ideas 死代码~~ ✅ WF-E 部分完成(capture.rs 删 CaptureInput/IdeaCapture 死码,保留 Idea/IdeaScores 共享实体;promotion/scoring/adversarial 内"两套 Recommendation/PromotionPolicy 死枚举"嫌疑 agent 未确认存在/保留为对外契约,本次未动,待复查)(06-14, commit 89da9fa)
|
||||
- [ ] F-260615-01 — **[P1 功能增强]** HumanNode 审批节点支持自定义选项 + 单选/多选类型。现状:config `options: Vec<String>` 已支持任意数量(2/3/…数量扩展已通),但 `decision` 是单 `String` 仅单选语义,`options` 空=自由文本。增强目标:不止「同意/拒绝」二选一,可配置 N 个候选项 + 单选(single)/多选(multiple)两种类型。**改动面**:①`df-core/events.rs` `WorkflowEvent::HumanApprovalRequest` 加 `select_type`、`HumanApprovalResponse` decision 单值→多值(`decisions: Vec<String>` 或保留 decision 兼容 + 加 decisions)②`human_node.rs` config 解析 `select_type` + 校验(多选时每项 ∈ options,可加 min/max 选中数约束)③IPC `approve_human_approval` 签名 ④前端 `stores/project.ts` approve + `api/types.ts` 事件类型 + 审批弹窗 UI(单选 radio / 多选 checkbox)⑤单测改断言 + 新增多选/超限测。**注意**:向后兼容现 single 调用方,`select_type` 缺省 = single — source:用户需求(06-15),crates/df-nodes/src/human_node.rs + src-tauri/src/commands/workflow.rs + src/stores/project.ts — **✅已实施(a3cccb070fe9c8821,2026-06-15,待commit)**:6 文件契约向后兼容(events.rs SelectType 枚举 Single/Multiple 缺省 Single+HumanApprovalResponse decision+decisions 双字段/human_node.rs 校验 single len==1·multiple len≥1·∈options/workflow.rs IPC 加 decisions+select_type Option 缺省兼容/types.ts/project.ts approve/ProjectDetail.vue checkbox 多选 UI)。cargo check 0 err/df-nodes 21 test(含 4 新增多选)/vue-tsc 0 err。主代理核查契约向后兼容 + 6 文件边界
|
||||
- [x] F-260615-02 ✅(2026-06-15,待commit) — **[P1 功能]** task 详情查看。✅已实施:get_task_by_id IPC(复用 TaskRepo::get_by_id ok_or_else 转 Result)+lib.rs 注册+taskApi.get+/tasks/:id 路由+TaskDetail.vue(11 字段:标题/描述/状态/优先级/关联项目 router-link 解析名/分支标签/负责人/基础分支/工作流定义/创建更新时间;复用 constants/project 标签+formatDate+watch route.params.id 重载)+Tasks.vue 列表项 @click router.push;TaskRecord TS 类型已存在无需新增;主代理核查 cargo check 0 err/vue-tsc 0 err/git diff 6 文件边界干净。现状:`src/views/Tasks.vue` 仅列表,无独立 TaskDetail 视图/路由(grep 仅 `ProjectDetail.vue` 嵌套任务命中,无独立详情页)。需求:点击 task 查看详情。**数据模型已就绪**(`df-storage/src/models.rs:53` `TaskRecord` 12 字段:id / project_id / title / description / status / priority / branch_name / assignee / workflow_def_id / base_branch / created_at / updated_at)。**改动面**:①新建 `src/views/TaskDetail.vue` 视图 + 路由(`router/index.ts` `/tasks/:id`)②task 详情 IPC(`get_task_by_id`,核对 `commands/task.rs` 现有 IPC 是否已有,无则补)③`Tasks.vue` 列表项点击 → 跳详情 ④详情页字段展示(title/description 渲染、status/priority 标签、关联项目名解析 project_id→name、branch/assignee 信息、时间戳)⑤可选:详情页内编辑(`update_task` IPC 已存在,FR-D6)。— source:用户需求(06-15),src/views/Tasks.vue + src/router/index.ts + src-tauri/src/commands/task.rs
|
||||
- [ ] F-260615-03 — **[P1 功能]** list 工具分页能力(list_projects / list_tasks / list_ideas + list_deleted 回收站)。现状:4 工具 `truncate(50)` 硬截断,无 offset/limit 参数,返回纯数组无 total/has_more 提示——超 50 条数据 AI 看不到**且不知被截断**。**已核对**(tool_registry.rs:134 / :151 / :164 / :378)。**后果**:80 项目找第 50+ 名后的 X → AI 回复「不存在」;100 任务总结只覆盖前 50;60 灵感评估静默漏 10。**方案(用户定)**:①schema 加 `offset`(默认 0)/ `limit`(默认 50,最大 100)参数 ②返回改 `{ items, total, returned, has_more }` ③AI 见 `has_more: true` 可再调 `offset=50` 翻页。**改动面**:`tool_registry.rs` 4 工具 handler(list_projects:127 / list_tasks:139 / list_ideas:156 / list_deleted:377)schema 加参数 + 截断改 offset/limit slice + 返回结构包对象;total 用截断前 `items.len()`(无需 repo 加 count)。**⚠️ breaking change**:返回 Array→Object,4 工具的 tool 描述(register 第 2 参)须同步更新告知新结构,否则 AI 仍按数组解析。— source:用户需求(06-15),src-tauri/src/commands/ai/tool_registry.rs:127-165,377-379
|
||||
- [ ] F-260615-03 — **[P2 最小修]** list 工具截断标志(原完整分页方案,对抗分析降级)。现状:4 工具 `truncate(50)` 硬截断,AI 不知被截断会回复「不存在」。**对抗分析裁决**:数据量未到 50 条线(projects=5, tasks=61),完整分页 breaking change 风险 > 收益 → **降级为最小修**:不改返回结构(non breaking),只在 truncate 时加 `truncated: true` 标志 + 工具描述注明"最多返回 N 条"。未来数据量到时再升完整分页(F-03-full)。**改动面**:tool_registry.rs 4 handler 截断处加 truncated bool 返回 — src-tauri/src/commands/ai/tool_registry.rs:127-165,377-379 — source:用户需求(06-15)+对抗分析裁决(06-15)
|
||||
- [ ] F-260615-04 — **[P1 UX]** read_dir / read_file 工具卡片连续时折叠/收起,提高信息密度。现状:AI 探查目录常连续调多个 read_dir + read_file(先列目录再读多个文件),每个结果独立卡片平铺,长列表/大文件内容占满屏幕,信息密度低。需求:相邻同类读取卡片支持折叠——默认收起只显摘要(如「read_dir: 12 项」「read_file: src/main.rs (234 行)」),点击展开看详情;或连续 N 个同类卡片归组折叠。**改动面**:①`ToolCard.vue` 加折叠态(`collapsed` ref + 摘要/详情双视图 + chevron 图标 + 高度过渡)②连续同类检测/归组(`ToolCardList.vue` 按 `tool.name` 分组,已有列表容器适合放分组逻辑)③摘要提取(read_dir 数项数 / read_file 文件名 + 行数,解析 result)④折叠态持久化(可选,localStorage 按 conv)。关联信息密度构想(memory: devflow-info-density-concept,卡片折叠是其中一环)。— source:用户需求(06-15),src/components/ToolCard.vue + src/components/ToolCardList.vue — **勘察完成(2026-06-15,wxflofhf2)**:feasible/plan 11 步跨 5 文件(ToolCardList/ToolCard/useAiSend/stores/ai/global.css)。risk 标低但实为 UX 新行为+改核心 ai 状态文件(useAiSend.ts/stores/ai.ts)+plan 细节有误(useAiSend composable 无 emit 方法)。**拆小或留待**:先做 ToolCardList 分组+单卡折叠摘要(限定不碰 useAiSend/stores),完整折叠交互归信息密度构想单独立项
|
||||
- [ ] F-260615-07 — **[P2 功能]** read_file 加 search 参数(单文件内关键词定位)。现状:read_file 仅 offset/limit 分页(tool_registry.rs:458-501),无原生搜索——AI 找关键词需多次分页扫描定位,token 浪费 + 多次 IPC。用户实测确认 offset 0基 + limit=1 单行精确读取可用,但定位靠 AI 多轮分页。**方案**:① schema 加 `search`(string,可选)参数 ② handler 内若有 search:读全文(1MB 限内)→ 按行过滤含 search 的 → 返回 `matches: [{line, content}]`(line 为 1基行号,须在描述注明与 offset 0基的差异)③ 匹配超量:默认返前 50 匹配 + total + has_more。**改动面**:tool_registry.rs read_file handler(:458-501)schema 加参数 + 返回结构扩展(有 search 返 matches,无 search 走原 content 路径,两路径并存)。**权衡**:vs 独立 grep 工具——read_file search 满足单文件定位主场景(最小改动,复用现有工具);跨文件搜索待 B-260615-37(run_command stdout)修后用 grep/findstr 验证够不够用,不够再立 grep 工具(对标 Claude Code Grep)。**大小写**:默认区分(确定性优先),case_sensitive 参数先不加(做减法)。— source:用户建议(06-15),src-tauri/src/commands/ai/tool_registry.rs:458-501
|
||||
|
||||
### 🟡 文件操作工具集补全(2026-06-15 能力梳理·设计定稿 V2)
|
||||
|
||||
> 用户给出 5 个工具具体 API 设计。**核心原则不变**:专用工具补 run_command 做不好/不安全的;系统命令类交给 run_command(B-37 修后)。**修正**:search_files 从"run_command 覆盖"升级为"值得做"(结构化 JSON > parse find 输出)。
|
||||
|
||||
**5 工具设计(用户方案)**:
|
||||
|
||||
- [ ] F-260615-06 [P0] **patch_file(edit_file)** — → 见下方对抗分析区 :503 完整条目(含设计文档指针)
|
||||
- [ ] F-260615-07 — **[P1] search_in_file**(内容搜索)— `path, keyword, (可选)regex, case_sensitive` → `[{line, content, column}]`。纯子串搜索先做(regex/case 后加,做减法)。替代 AI 多轮分页扫描 — tool_registry.rs read_file handler 扩展
|
||||
- [ ] F-260615-08 [P1] **file_info**(元信息)— `path` → `{exists, size, lines?, modified, is_binary, is_dir}`。**不读内容**,快速元数据。lines 用 `\n` 计数(不全文读);二进制不报 lines — tool_registry.rs 新增
|
||||
- [ ] F-260615-09 [P1] **append_file**(追加写入)— `path, content` → `{bytes_written, new_size}`。日志追加/增量写入,避 read-merge-write 竞态 — tool_registry.rs 新增
|
||||
- [ ] F-260615-12 [P1] **search_files**(文件名/glob 搜索)— `path, pattern: "*.ts", recursive?` → `[{path, size}]`。结构化 JSON > parse find 输出(AI token 效率+可靠)。从"run_command 覆盖"升级(原归类为不做的系统命令类) — tool_registry.rs 新增
|
||||
|
||||
**后续(P3/合并)**:
|
||||
|
||||
- [ ] F-260615-10 [P2] **diff**(修改前对比,防误覆盖)— 并入 patch_file 增强(edit 前 return diff 给审批卡看清改动),或独立工具 — 关联 F-06
|
||||
- [ ] F-260615-11 [P3] **write_file base64**(二进制写入)— 图片/PDF/Excel,场景窄 — tool_registry.rs:519
|
||||
|
||||
**run_command 覆盖(不单独做)**:压缩/权限/软链/校验/批量
|
||||
**不做**:文件监听(app 层)/文件锁定(并入 F-06 Mutex)
|
||||
|
||||
— source:用户 API 设计 V2(06-15),F-06 设计已定稿见 [patch_file工具设计-2026-06-15.md](./02-架构设计/patch_file工具设计-2026-06-15.md)
|
||||
|
||||
### 🟡 对抗分析裁决(2026-06-15 十域十八项交叉验证)
|
||||
|
||||
> 来源:用户对抗分析报告(10 域聚类 + 正反双向验证 + 依赖链提取)。**核心价值:6 个否决/暂缓裁决**("不做"比"做什么"更值钱,避免浪费投入)。完整报告见 docs/02-架构设计/对抗分析裁决-2026-06-15.md(若已落盘)。
|
||||
|
||||
**✅ 推进(第一批独立可并行)**:
|
||||
|
||||
- [ ] F-07 trait 下沉 df-ai-core — 设计完备 4 项决策全定稿,退路可放 df-core,解锁 F-03/F-01
|
||||
- [ ] R-PD-2 ScriptNode 不注册 script — 3 行删除封死攻击面,工作流当前纯演示无真实脚本需求
|
||||
- [ ] F-260615-06 [P0] **patch_file(edit_file)** — 局部文件更新工具,补齐 AI 文件操作闭环。完整设计见 [patch_file工具设计-2026-06-15.md](./02-架构设计/patch_file工具设计-2026-06-15.md)(API/三层防御/边界情况/替代方案否决/实施步骤)。**核心**: old_text 精确匹配为主+line 辅助+Mutex 并发安全+expected_hash 指纹防脏写。第一批实现核心三件套(~50行)。— src-tauri/src/commands/ai/tool_registry.rs 新增 handler
|
||||
- [ ] ARC-06 composable 循环依赖 — 提 aiShared.ts 破环最小改动(或合回 stores/ai.ts 备选)
|
||||
- [ ] CR-09 .ai-md 样式 5 份→全局 css 抽取 — 零风险减法
|
||||
- [ ] F-09 status 对齐前后端 — 删从未产生的 planning/in_progress 等,前端 map 改 {active/archived}
|
||||
- [ ] F-260615-03 最小修(截断标志)— **从完整分页降级**:不改返回结构(non breaking),truncate 时加 `truncated: true`(→ 见上方 :469 完整条目,本条为对抗分析区引用,勿重复实施)
|
||||
- [ ] R-PD-9 display_hint 轻量抽取 — audit 工具名→文案映射作 display_hint,消除双份映射
|
||||
|
||||
**🟡 暂缓 / 需重设计**:
|
||||
|
||||
- [x] T-11 条件引擎 Phase1+2 🟡 **暂缓** — 引擎设计好但**无前端 UI 消费方**(demoDag edges 无 condition 字段,用户无法配置条件)。等工作流有真实使用场景(BuildNode 落地后跑构建→测试→部署)再推进。决策点 A/B/C(手写求值器/复活 set_skipped/default false+warn)均认可
|
||||
- [x] F-260615-03 完整分页 🟡 **暂缓** → 降为最小修(truncated 标志),见上方
|
||||
- [x] ARC-05 project.ts 上帝 store 拆分 🟡 **需重设计拆分策略** — 不能简单按领域拆(会重蹈 ai.ts 循环依赖覆辙),需先把越层 invoke 沉到 api/workflow.ts。ARC-06 先做
|
||||
|
||||
**❌ 否决(人定"不做",不进路线)**:
|
||||
|
||||
- [x] ~~R-PD-9 agentic loop 下沉 df-ai~~ ❌ **否决** — 与 ai.rs 子 module 拆分决策冲突("下沉需动 crate 依赖图引入循环依赖")。保留 display_hint 轻量部分
|
||||
- [x] ~~R-PD-8 DTO 映射层~~ ❌ **否决** — 过度设计,5 个 IPC 手动对齐可接受。替代:types.ts 头部文档约定"后端改 schema 必须同步改此文件"
|
||||
- [x] ~~F-09 拆双字段(status 生命周期 + stage 开发阶段)~~ ❌ **否决** — 阶段概念已被弱化(df-stages 砍了阶段插件),DB 实际只有 active/archived/deleted,双字段过度设计
|
||||
- [x] ~~R-PD-12 做实 run_workflow AI 工具~~ ❌ **否决(当前)** — 工作流是内部基础设施,无真实 CI/CD 需求。删假能力(no-op 桩返 note)更诚实
|
||||
|
||||
**⚡ 推荐推进顺序**:
|
||||
1. 第一批(独立并行): F-07 / R-PD-2 / F-15-06 edit_file / ARC-06 / CR-09 / F-09对齐 / F-03最小修 / R-PD-9 display_hint
|
||||
2. 第二批(依赖第一批): F-03 对抗评估接 LLM(F-07后) / F-01 模型能力Phase1(F-07后) / R-PD-12 删假能力(R-PD-2后)
|
||||
3. 第三批(需用户输入): S-01 API Key 401 / 显示多开 / 审批可见性
|
||||
|
||||
## 已完成
|
||||
|
||||
|
||||
Reference in New Issue
Block a user