Files
DevFlow/docs/05-代码审查/工作区多角度走查-2026-06-15.md
绝尘 04032a2a8d 重构: 文档汇总+进度看板+孤儿任务清理脚本+gitignore 噪音排除
- docs/02 架构设计: 新增 aichat审查/异步审批构想/流式渲染调研/generating状态机/密钥迁移健壮性/工作流脚本执行边界/条件表达式引擎/F-07 trait下沉/Agent架构说明/任务推进构想/功能创意池;更新功能决策记录+归档/对抗论证/文档记录规范/经验记录
- docs/03 模块文档: 新增 AI对话引擎/DAG引擎详解;更新 df-knowledge/df-nodes/df-storage/df-workflow/df-ai
- docs/05 代码审查: 新增 全栈审查/全局review/架构审查/近期改动审查/工作区多角度走查/自研memo流式渲染审查
- docs/09 问题排查: 新增 aichat-apikey-401
- docs/INDEX+README 索引同步;docs/todo 待办看板(2026-06-15 汇总)
- PROGRESS.md Sprint 22-25;URGENT.md 加急清单快照(5 项 P0 已全修)
- scripts/cleanup_orphan_tasks.{py,sh} 孤儿任务清理工具
- .gitignore 补 *.broken.bak + tmp/ 噪音排除
2026-06-15 05:14:21 +08:00

16 KiB
Raw Blame History

工作区多角度代码走查2026-06-15

范围:工作区未提交改动 22 文件 547 行 + 近 6 提交前端改动。4 路并行代理按文件域切分走查AiChat.vue / View 层 / stores 重构 / composables+ToolCard。 方法:每路代理独立 git diff + 多维度走查(正确性/健壮性/边界/性能/DRY/安全/异步时序/i18n/Vue3+Pinia+TS 规范),主代理整合 + 去重 + 关联已有 todo。 性质:dry — 仅走查 + 文档,不改代码(本会话职责:走查/整理/建待办,见 memory session-role-diagnose-only)。 关联:近期改动代码审查-2026-06-15.mdFR-S1/S7/S8+近5提交自研块级memo流式渲染审查-2026-06-15.md(块级 memoCR-04~07本走查跳过不重复


🔴 必须修复4 独立根因)

① [src/stores/project.ts:273] selectType 字段名与后端 IPC 不匹配 → 多选审批静默失效P0 功能)

来源stores 代理。

改什么invoke('approve_human_approval', {...})selectTypecamelCase后端 src-tauri/src/commands/workflow.rs:211 签名 select_type: Option<String>snake_case。Tauri 2 #[tauri::command] 默认不做 camelCase→snake_case 转换(全项目仅 state.rs:30rename_all,与 IPC 命令无关)。

影响:后端收到 select_type = None → 归一化为 Singleworkflow.rs:214-217→ 多选 UI 勾多项提交被后端拒「单选审批只能提交一个决策」。单选偶然兼容(缺省=Single掩盖了问题。F-260615-01 多选审批功能完全不可用。

证据:同 invoke 对象 execution_id/node_id 已是 snake_case唯独 selectType 破坏约定;grep selectTypesrc-tauri/ 零命中。

怎么改

-    selectType: state.pendingApproval.select_type ?? 'single', // Tauri camelCase→snake_case 自动转 select_type
+    select_type: state.pendingApproval.select_type ?? 'single',

删误导性注释。单行修复。修后须手动验证一次多选审批全链路


② [useAiSend/useAiStream/useAiEvents] 流式 timeout/error/stop 收尾不清队列 → 队列消息静默丢失P0 功能)

来源composables 代理。

改什么sendMessageuseAiSend.ts:36-42state.streaming === true 时入队 state.queuedrainQueueuseAiSend.ts:25-29仅在 AiCompleted 事件中调用useAiEvents.ts:218。若流式因 onStreamTimeout/AiError/stopChat 收尾(非走 AiCompleted队列消息永远不被发出,用户输入"消失"无提示。

影响:用户生成中输入消息 → 流式超时/stopChat/后端崩溃 → 队列消息静默丢失,用户以为已发送但无回复。

关联去重:与 B-260615-22前后端状态不同步streaming 复位撞后端拦截相关但不同角度——B-22 讲状态不同步致发送撞拦截,本项讲收尾路径不清队列致消息丢失。两者独立。

怎么改onStreamTimeoutuseAiStream.ts:21-49/ AiError caseuseAiEvents.ts:222-237/ stopChat 三处收尾路径都应清空队列并提示(或回填输入框):

// onStreamTimeout() 末尾clearStreamWatchdog() 之后
+ if (state.queue.length) {
+   state.queue = []
+   content += '\n(待发送队列中的消息已取消,请重新发送)'
+ }

approveToolCalluseAiSend.ts:82-107IPC 失败 catch throw e 路径同样漏清队列,归并本项一起修。


③ [useAiStream.ts onStreamTimeout] 超时不回滚 running toolCall → 审批后卡片永久骨架屏P0 功能B-07 残留)

来源composables 代理。

改什么approveToolCalluseAiSend.ts:82-98乐观置 tc.status = 'running' 后,若后端既不回 AiToolCallCompleted 也不回 AiApprovalResultIPC 成功但后端 hang看门狗超时回调 onStreamTimeoutuseAiStream.ts:21-49只复位 state.streaming/generatingConvId/currentText + push 错误气泡,不碰 messages[].toolCalls[].status。卡片 status 仍是 running → 渲染骨架屏ToolCard.vue:24审批按钮在 pending_approval 分支ToolCard.vue:31才显示 → 用户看到永久骨架屏,无重审入口。

关联去重B-260615-07 已实施approveToolCall 乐观置 running 加 watchdog 兜底),但 B-07 只加了 watchdog 重启,onStreamTimeout 回调本身没回滚 toolCall.status——这是 B-07 的残留遗漏点。本项是 B-07 的补全。

怎么改onStreamTimeout 中扫一遍把 running 的 toolCall 回滚:

// onStreamTimeout() 中clearStreamWatchdog() 之后、push 消息之前
+ for (let i = state.messages.length - 1; i >= 0; i--) {
+   const tcs = state.messages[i].toolCalls
+   if (!tcs) continue
+   for (const tc of tcs) {
+     if (tc.status === 'running') { tc.status = 'rejected'; tc.result = '审批/执行超时未响应' }
+   }
+ }

④ i18n 硬编码一组 → 英文 locale 中英混杂P1 文案)

来源View 代理ProjectDetail 多选按钮 🔴+ composables 代理onStreamTimeout 文案 🟡)。

改什么

  • ProjectDetail.vue:201🔴):多选审批按钮 确认({{ multiDecisions.length }}) 硬编码,同文件其他文案全走 $t('projectDetail.xxx')i18n 体系完整。英文 locale 下多选弹层中英混杂。
  • useAiStream.ts:39-41🟡onStreamTimeout 两条错误文案硬编码B-260615-03 改了文案但没接 i18n
  • AiChat.vue:675handleSend toast 发送失败:${msg} 硬编码。

关联去重AiChat.vue:459 confirmClearChat 硬编码 = B-260615-20 已记技术债(「文案硬编码未接 i18n同 confirmClearChat 先例」),不重复记,但本组修复时一并处理。

怎么改:补 i18n key中英

  • projectDetail.approvalConfirm / approvalConfirmCount({count})
  • ai.streamInterruptedAfterTool / ai.streamInterrupted
  • aiChat.toastSendFail 模板/composable 改用 t('...')

🟡 建议改进

⑤ 四份 .ai-md 样式逐字重复 ~180 行 → 抽全局样式或 <AiMarkdown> 组件(最大 DRY 债)

来源View 代理。位置ProjectDetail.vue:549-607 / Ideas.vue:815-873 / Knowledge.vue:610-668 / TaskDetail.vue。

:deep(p/ul/li/code/pre/blockquote/h1-h3/a/strong/hr/table/th/td) 样式规则逐字符相同仅外层选择器不同。B-260615-24TaskDetail首创B-260615-25 复制粘贴到三视图。composable 抽了渲染逻辑useMarkdown样式没跟着抽。

怎么改:方案 A推荐轻量src/styles/ai-md.css 全局规则 + main.ts import 一次,四视图删 scoped 重复块。方案 B彻底— 抽 <AiMarkdown :text> 组件含 v-html+scoped 样式。先 A第 5 处需求再升 B。

⑥ useMarkdown 加 useRendered(getText) 辅助 → 消除 computed+onMounted 三处重复(与 ⑤ 同源)

来源View 代理。ProjectDetail:251 / Ideas:225 / Knowledge:249 三处 renderedDesc = computed(() => { void mdReady.value; return renderMd(...) }) + onMounted(loadMarkdown) 模式逐字一致。

怎么改useMarkdown 加 useMarkdownRender(getText) 返回 { html, loadMarkdown },调用点一行。做 ⑤ 方案 B 组件则此项自动消失。

⑦ switchConversation JSON.parse(arguments) 无逐条容错 → 单条坏参数清空整对话

来源composables 代理。useAiConversations.ts:81 JSON.parse(tc.function?.arguments || '{}') 在 map 内,外层 try-catch:87粒度过粗——单条 tool_call arguments 损坏 → state.messages = [] 整对话空白。

怎么改:包成安全函数逐条容错(返回 {} 兜底)。

⑧ args 消费方全用 as any → 类型逃逸

来源composables 代理。ToolCard.vue:241,328 + useAiConversations.ts:71。AiToolCallInfo.args 类型 unknowntypes.ts:205但消费方 as any 绕过,拼写错无编译期检查。

怎么改:定义 type ToolArgs = Record<string, unknown>,出口收窄 + 消费方类型守卫。

⑨ approveToolCall 重复查找 → 复用 findToolCall

来源composables 代理。useAiSend.ts:82-86 state.messages.flatMap(m => m.toolCalls || []).find(...),而 findToolCalluseAiEvents:75已是反向遍历统一查找。同一语义两套查找DRY 违反。

怎么改approveToolCall 改用 findToolCall(toolCallId)useAiSend 已 import useAiEvents不新增耦合方向

⑩ useAiEvents switch 缺 AiHeartbeat case

来源composables 代理。useAiEvents.ts:115 看门狗重置白名单 ['AiApprovalRequired','AiCompleted','AiError']AiHeartbeat 不在排除表 → 会 resetStreamWatchdog行为正确但 switch:118无 AiHeartbeat case 落到末尾。功能对但可读性差。

怎么改:补 case 'AiHeartbeat': break(显式 no-op

⑪ approveHumanApproval 签名 decision/decisions 歧义

来源stores 代理。project.ts:255 (decision, comment?, decisions?) 三参,多选传首项 decision 凑数 + 全量 decisions依赖后端归一化。前端注释「decision 传首项(向后兼容)」冗余。

怎么改:改 options 对象签名 { decisions: string[], comment? },或不改则在 JSDoc 明确「decisions 优先decision 占位」契约。

⑫ ToolCard 杂项

来源composables 代理。formatBytes(undefined) 误显「0 B」:227应区分 undefined 与 0shouldKeepOpen 与 toolCategory 命名判定风格不一(:131 精确 vs :206 includesv-for :key="arg.key" 无 index 兜底(:33LLM 畸形 args 极低概率)。


可选优化

  • _toastTimer 卸载未 clearTimeoutAiChat.vue:436,728— onBeforeUnmount 清了 rafId 漏了 _toastTimer同组件不对称。补一行。
  • markdown 外层容器 span/p 包 blockProjectDetail:119 span / Ideas:66 p— 改 div 避免 inline 包 block浏览器容错实际不崩。
  • 内联 SVG 常量散落重复ToolCard.vue:127-128,213-218— 抽 SVG 常量表。
  • appSettings.ts:8 注释「store 层」指代模糊settings.ts 已删)— 改指 stores/ai.ts
  • stores/index.ts 空行不统一 — 格式。
  • router TaskDetail 路由 icon 语义:44-49— 核对是否有 menu 遍历逻辑,详情页防误入菜单。
  • options 透传缺 shape 校验project.ts:271— 后端兜底已校验,记录。
  • projectNameById computed 每实例重建ToolCard.vue:288-293— 可选提升 store/inject 共享。
  • useConfirm answerConfirm resolve null 静默:43-47— dev 警告。
  • useAiEvents.ts:25 tas any — 已知权衡(绕 vue-i18n TS2589记录。

亮点

  1. settings.ts mock 删除彻底零残留stores 代理):git log -p 确认是初始提交纯 mock98 行硬编码 provider+连接),全 src 零 useSettingsStore/AIProvider/GeneralSettings importgrep 验证)。api/settingsHTTP 层)同名不同模块不构成残留。真实 provider CRUD 走 api/ai.ts+stores/ai.ts,偏好走 appSettings.ts,删除干净。
  2. useConfirm 抽取消除重复彻底composables 代理Projects/ProjectDetail/Ideas/Settings/AiChat 五处全迁移,无残留 window.confirm、无旧 confirmState 定义。Settings reactive→ref 模板自动解包正确。
  3. useMarkdown 单例+DOMPurify+缓存+兜底四件套View 代理):模块级 _marked/_purify/mdReady/_mdCache 单例避免多视图重复加载 markedrenderMd 强制 sanitize 杜绝 XSS未就绪 escapeFallback 降级。三视图复用零成本。
  4. void mdReady.value 显式响应式依赖View 代理):修了 B-25 漏响应式致首次纯文本后不重算的坑,三处一致 + 注释到位。Vue3 computed 追踪非同步读的正确用法。
  5. findToolCall 反向遍历 + 不建索引的决策有据composables 代理):注释详述 state.messages 多处整体替换致独立 Map 索引易失配反向扫描零额外状态。onStreamTimeout 复制扫描逻辑而非 import 是为避循环依赖,判断准确。
  6. switchConversation 二次 token 比对composables 代理):第二个 await 后再 if (mySwitchId !== _latestSwitchId) return,防 A→B 切换期间 A 的 pending 覆写 B补了首个异步点后的竞态漏。
  7. sendMessage IPC 失败回滚完善composables 代理):复位 streaming+清 watchdog+移除 user 消息和空气泡,让 handleSend 回填输入框重试。
  8. 多选审批状态清空双保险View 代理watch pendingApproval 在 showDialog 前清 multiDecisionshandleApprovalMulti 结束也清immediate:true 覆盖首次挂载。
  9. demoDag 下线决策有据View 代理R-PD-2 删除根因明确script 节点不再注册,构造含 script 的 DagDef 会 build_dag 失败),删得干净无死代码。
  10. i18n 中英 key 完整对齐composables 代理aiTool.ts 两份逐行比对11 个 CRUD key 中英都有,无缺 key。

📊 摘要

# 等级 文件:行 修改内容 对应 todo
🔴P0 project.ts:273 selectType→select_type多选审批静默失效 B-260615-31
🔴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
🟡P1 ProjectDetail/Ideas/Knowledge/TaskDetail .ai-md 180 行抽全局样式/组件 CR-260615-09
🟡P2 ProjectDetail:251/Ideas:225/Knowledge:249 useMarkdown useRendered 辅助(同 ⑤ 源) CR-260615-10
🟡P2 useAiConversations.ts:81 JSON.parse 逐条容错 CR-260615-11
🟡P2 ToolCard:241,328 + useAiConversations:71 args 去 as any 收窄类型 CR-260615-11
🟡P2 useAiSend.ts:82-86 approveToolCall 复用 findToolCall CR-260615-11
🟡P2 useAiEvents.ts:118 switch 补 AiHeartbeat case CR-260615-11
🟡P2 project.ts:255 approveHumanApproval 签名改 options 对象 CR-260615-11
🟡P2 ToolCard.vue 多处 formatBytes/命名判定/key 兜底 CR-260615-11
P3 AiChat:436 等 10 项 CR-260615-12

总计🔴43 个 P0 功能 + 1 个 P1 文案)🟡8 10

总体评价工作区改动主干质量高settings.ts 删除彻底/useConfirm 收敛/useMarkdown 四件套/响应式修复正确/竞态防护到位)。3 个 P0 功能 bug 集中在 AI 交互异步收尾路径(队列丢失/骨架屏卡死)+ 1 个 IPC 字段名笔误selectType—— 均非设计缺陷,是收尾路径不完整 + 命名笔误,修复成本低。最大技术债是 ⑤ .ai-md 样式 180 行重复B-24/25 复制粘贴源头),建议抽全局样式根治。

质量评级:良(改 ①②③④ 达优;⑤⑥ 顺带清最大 DRY 债)


来源

  • 4 路并行代理走查AiChat.vue / View 层 / stores 重构 / composables+ToolCard2026-06-15本会话内 spawn。
  • 关联已有 todoB-260615-07approveToolCall watchdog③ 残留点、B-260615-20handleSend toast i18n 技术债,④ 同源、B-260615-22前后端状态不同步② 相关、B-260615-24/25.ai-md 样式首创/复制,⑤ 源头)。