Files
DevFlow/docs/todo.md
绝尘 6e1485e4f9 修复: BUG-260620-05 工程内路径弹窗 + G1 多目标累积 + 测试编译债
- BUG-260620-05: reload_allowed_dirs 无条件插入 workspace_root,工程内路径免授权
- G1: pinned_goal → pinned_goals Vec<String>,多目标追加去重 + MAX_GOALS=5
- tool_registry.rs 测试 4 处补 data_dir 形参
- 更新 todo.md / docs/todo.md 状态
2026-06-27 22:02:41 +08:00

1101 lines
168 KiB
Markdown
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# DevFlow 工作看板
> 来源:`docs/02-架构设计/滚动规范/功能决策记录-2026-06-14.md`「需求与待办」+ `PROGRESS.md` 各 Sprint 遗留2026-06-14 汇总去重 + 代码核对修正。
> 互操作:执行走 mission-control回写 mission_id审查走 cr发布走 publish-*。
> 核对说明2026-06-14 经代码勘察后修正——detached 卡死已部分修复降 P2、Sprint 19 遗留 3 项补入、依赖关系标注。
>
> **2026-06-18 归档**: 已完成 `[x]` 与历史分析段已迁至 [07-项目管理/todo归档/2026-06-18.md](./07-项目管理/todo归档/2026-06-18.md)。
---
## 🎯 统一规划2026-06-26 全景)
> 整合知识图谱设计 + 22 条未完成项 + G1 目标钉扎(✅落地待实测) + 多工程方案。
> **核心主线AI Working** — AI 拥有项目知识图谱,自主分解/编排/执行/自检,人只决策。
> **执行策略(两阶段)**:① 先攒批无冲突小 bug/小调整 → 提交;② workflow 编排相关任务批量推进大重构(减少文件读取/LLM 交互,提高上下文效率)。
### 主线脉络
```
G1目标钉扎(✅落地,待实测) → 知识图谱Phase1(任务网络,AI编排地基)
↓ ↓
实测验证 Phase4注入(复用G1的system_prompt注入基建)
↓ ↑
F-09多会话B(立项) ────────────────┘ (Phase4前置:多会话各自注入)
```
### 任务图谱DAG · 有方向有状态)
> 节点=任务(着色=状态),边=依赖方向(前置 → 后继)。**推进时更新状态**CLI/edit 同步此表 + Mermaid
> 图例:✅完成 / 🔨进行中 / 📋待办 / 🚫阻塞
```mermaid
graph TD
classDef done fill:#bfb,stroke:#393,stroke-width:2px
classDef doing fill:#ffd,stroke:#993,stroke-width:2px
classDef todo fill:#eef,stroke:#99c
classDef blocked fill:#fcc,stroke:#c33
G1["G1 目标钉扎<br/>✅代码 8ce18cb / 🔨待实测"]:::doing
P1["父① 小bug攒批<br/>✅①.2/①.3(①.1过时·①.4归⑤)"]:::done
P2["父② 知识图谱Phase1<br/>✅Phase1(数据层+业务层)"]:::done
P3["父③ AI对话体验<br/>📋待办"]:::todo
P4["父④ F-09 per-conv<br/>✅前端per-conv(accessor)"]:::done
P5["父⑤ 灵感模块<br/>✅完成(⑤.1/①.4/⑤.2)"]:::done
P6["父⑥ Phase2-5<br/>🔨Phase2-3✅·4-5待办"]:::doing
P7["父⑦ 技术债<br/>📋待办"]:::todo
P1 -.先行.-> P2
G1 ==基建==> P6
P2 ==> P6
P4 ==> P6
P2 -.task_links.-> P5
```
| 任务 | 状态 | 子项 | 依赖(前置) |
|---|---|---|---|
| **G1** 目标钉扎 | 🔨 代码✅`8ce18cb` 待实测 | G1/G2/G4 落地 | — |
| **父①** 小bug攒批 | ✅ 完成 | ①.2 白名单✅(settings.rs) / ①.3 priority✅(idea.rs) / ①.1 BUG层1❌过时(F-260619-03 方案①取代,层2待决策) / ①.4 雷达图→归父⑤ | — |
| **父②** 知识图谱Phase1 | ✅ Phase1完成 | ②.1 V29迁移✅ / ②.2 TaskRecord+TaskLinkRepo✅ / ②.3 IPC(create_task扩展+task_link CRUD+move_queue+get_tree)✅ / ②.4 父聚合✅(set_status_for_aggregation绕status收口) / ②.5 AI工具6✅(基线38) | G1(弱) |
| **父③** AI对话体验 | 📋 待办 | ③.1 B-260619-04 ToolCard / ③.2 REFACTOR-260619-04 审批状态机 | — |
| **父④** F-09 per-conv | ✅ 前端per-conv | ④.1 streaming/currentText per-conv Map(accessor委派,单会话回归零变化,BUG-260624-01根因清除,vue-tsc 0) | — |
| **父⑤** 灵感模块 | ✅ 完成 | ⑤.1 软删除✅ / ①.4 雷达图✅ / ⑤.2 #05✅/#06拆const✅/#09/#10表单(逗号tags)✅ / #07 DEC-02保留purge(不改) / 附:priority_from_i32跨层映射修复(对齐前端0=critical) | #07→②.1 |
| **父⑥** Phase2-5 | 🔨 Phase2-3✅ | ⑥.1事件流✅(V30) / ⑥.2基础设施✅(V31 project_services+D10凭证审查+IPC+AI工具,基线41) / ⑥.3注入(依赖全就绪) / ⑥.4前端 | ⑥→父②, ⑥.3→父②+父④+G1 |
| **父⑦** 技术债 | 📋 待办 | SMELL-P1-6 / conditions / CR缓存 / UX分页 / 审批超时 / miniapp / 双监听器 | — (穿插) |
> **推进路径**:父①先行(攒批提交)→ 父②主线 workflow → 父③/④并行 → 父⑤/⑥/⑦穿插。每父任务一个 workflow 批,子任务相关文件批量读改减少交互。**状态更新约定**:子项完成→父状态 🔨;全子完成→父 ✅;每批提交后同步此表。
### 协同点(跨主题复用)
1. **IDEA-FIX-07 关联单向 ↔ 知识图谱 `task_links`**:并入 Phase 1 用独立表§2.2 论证 JSON 缺陷),别补 JSON 双向同步
2. **G1 system_prompt 注入基建 ↔ Phase 4 L0 注入**:复用 `pinned_goal` 拼接链路
3. **F-09 per-conv ↔ Phase 4 多会话注入**F-09 前置
4. **REFACTOR-260619-04 审批状态机 ↔ `queue=decision` 待决策池**:审批对齐
### 待决策
| 决策点 | 选项 |
|---|---|
| 多工程合并 | 方案 A 合并 V28前提:确认子1 表设计find 未找到独立文档)/ 知识图谱独立 Phase 1 |
| conditions 启用 | 启用求值 / 保留观察 |
| BUG-260620-05 层2 | workspace_root 外默认放行(黑名单制)?— 详见 `## 🔴 2026-06-20 BUG-260620-05` |
---
## 交接状态2026-06-14
**代码健康度**`cargo test --workspace` 全过、`npx vue-tsc --noEmit` 0 error主代理独立验证非 mission 自报)。
**工作区状态(重要)**`git diff` 104 文件8066+/6941-)是**三层混合**——①会话前未提交基线Sprint 19 等大量工作i18n 拆目录、knowledge 全栈、Settings 拆分、appSettings 迁移…)②本次会话重构 ③代理越权修复。**接手前务必 `git diff` 通览区分**,勿整体当作单一改动提交。
**本次会话完成**
- 重构(用户授权):删 5 僵尸 cratedf-evolve/plugin/stages/task/traceability、清 7 死模块df-execute docker/git_ops/ssh + df-project scheduler/timeline/context + df-ideas graph、拆 ai.rs→`commands/ai/` 11 文件、拆 ai.ts→6 composable、models 字段 bug 修复、coordinator B 路线标注
- 代理越权追加修复 6 处(已标✅,主代理验证编译+测试通过;逐行正确性建议接手方 `git diff` 复核B-01 审批持久化 / B-02 ConditionEngine 默认 false / B-04 删 NodeRegistry Default impl / T-05 工具结果截断 50KB / B-08 promote 补偿删除 / T-07 诊断日志清理
**待设计交其他会话(核心)**df-workflow 审批闭环三连 B-06/B-07/B-03。**✅ B-03 设计已完成**接手会话2026-06-14见 [B-03-人工审批响应机制-2026-06-14.md](./02-架构设计/已编号方案/B-03-人工审批响应机制-2026-06-14.md),通道选型定为 **工作流独立审批通道**(复用 EventBus broadcast + HumanApprovalResponse 事件 + approve_human_approval IPC非 ai.rs AiApprovalRequired——后者是 AI Chat 工具审批路径,与工作流节点审批是两条独立链路)。拆 B-03a响应等待 + 超时,不依赖 B-07/ B-03b取消机制。**B-06 / B-07 仍待实施**B-06 = execution_id 下沉并发隔离B-07 = 共享 StateMachine 取消前置),是 B-03a 并发安全 / B-03b 的前置。
**失控代理教训**:本次会话派的后台拆分代理在 stop hook 循环里失控,越权改代码/文档(先斩后奏)。接手方若再派 agent注意约束其不碰决策记录用户已要求手动触发+ 限定单任务不自主续推。
---
## 待办
### 🔍 2026-06-21 AI Chat 技术债审查(workflow wf_92b61c35-4d2 · 10 模块串行 grep 核验)
> 详单见 [aichat-技术债审查-2026-06-21.md](./05-代码审查/aichat-技术债审查-2026-06-21.md)。0 P0 / 7 P1 / 25 P2 / 40 P3 / 已登记 8 / 新债 64。**P1#5/#6 已即时修并自验过**。
**P1 已即时修(2)**:
- [x] ✅(主代修·chat.rs:458-461 ai_approve exec_result 包 tokio::time::timeout(60s) 对齐 ai_authorize_dir:658·cargo check EXIT 0) **TD-260621-05a****[P1🔴]** ai_approve 缺 60s 执行超时(用户"静默吞消息"bug F-260620 同型复发)。ai_authorize_dir 有超时 ai_approve 漏。卡死→generating 永真→前端 130s 看门狗吞消息。
- [x] ✅(主代修·useAiConversations.ts:99 switchConversation activeConversationId 设定后加 `state.streaming = state.generatingConvs.has(id)`·vue-tsc EXIT 0) **TD-260621-06a****[P1🔴]** switchConversation 不复位 streaming,真并发切非生成会话残留 stop 按钮→点 stop 发错会话。
**P1 归路线(5 项·涉大改/行为变更/需设计·未即时修)**:
- [x] TD-260621-01 [P1·归 F-09 B 路线 per-conv state] ✅(批2-A 落地·useAiStream `_watchdogTimers` Map<convId,timer> + `_legacyWatchdog` fallback 双路·onStreamTimeout 携 convId 仅清该 conv·2026-06-21) — **watchdog 单计时器全清误杀并发会话**`useAiStream.ts:30` `_streamWatchdog` 模块级单计时器无 per-conv 隔离;`onStreamTimeout` 全清 generatingConvs+queue+currentText。F-09 真并发下 A 超时连带杀 B + 静默丢用户排队输入(注释称"防丢失"但 clear 即丢失)。修:watchdog 改 per-conv Map<convId,timer>,onStreamTimeout 携 convId 仅清该 conv;queue clear 改仅清当前 conv 关联。归 F-260616-09 B 路线统一改造。
- [x] TD-260621-02 [P1·归 F-09 B 路线 per-conv state] ✅(批2-A 落地·pendingMaxRounds ref<string|null> 挂起 convId + 守卫比对 activeConversationId·DirAuthDialog visibleDirAuths filter conversationId·2026-06-21;🟡D pendingDirAuths 全清→per-conv filter 由 wqnd4axf8 补) — **pendingDirAuth/pendingMaxRounds 全局单 ref 非 per-conv**`useAiEvents.ts:99/113` 模块级单实例;DirAuthDialog:60/MaxRoundsCard:54 守卫仅判 isViewingGenerating 未比对 conversationId(pendingMaxRounds 纯 boolean 无 convId 字段)。真并发下挂起弹窗错显于非挂起会话。修:pendingMaxRounds 改 ref<string|null>(convId)+ DirAuth 守卫加 convId 比对。归 F-09 B 路线。
- [x] TD-260621-03 [P1·patch_file 落地债 F-260615] ✅(读改写整体移入 _patch_guard 锁内串行化防 lost update + 删 entry().or_insert() 内存泄漏 + drop 写序列后释放·2026-06-21) — **patch_file 读改写在 FILE_LOCKS 外,lost update**`tool_registry.rs:1247` 指纹校验(1176)+old_text CAS(1209)+new_content 计算(1197-1240)全在无锁纯读段;FILE_LOCKS(1242-1269)仅序列化 backup→tmp→rename 不防读写交错。两并发 patch 同文件:B 用基于旧内容算的 new_content 覆盖 A。write_file 不取 FILE_LOCKS 同根因。修:读+校验+new_content 整体移入锁内,或 per-path Mutex。
- [x] TD-260621-04 [P1·patch_file 落地债 F-260615] ✅(read_file 三返回点返 file_hash 闭环 + 抽 compute_file_hash helper read_file/patch_file 共用消格式漂移·2026-06-21) — **expected_hash 契约破裂,指纹防并发形同虚设**`tool_registry.rs:1112` schema 描述"read_file 返回的 file_hash"但 read_file handler(892-976)从不返回该字段。expected_hash 实由 patch_file 内部 format!("{}_{}",modified_secs,len)(1180)即造即比,LLM 拿不到前置值只能瞎传/省略→常 mismatch bail 或被跳过。L3 防行号漂移设计在实际 LLM 调用链落空。修:read_file 返回 file_hash 同格式闭环回传。
- [x] ✅(2026-06-24·ba811ea chat.rs executed→completed 6处:460幂等读/584 ai_approve写/634判定/668 IPC返回/885 authorize_dir日志/889 authorize_dir写 + df-storage V27 迁移 UPDATE 存量 executed→completed + v27 两测试 unify/idempotent·cargo check EXIT 0 + df-storage 78 passed) **TD-260621-05** [P1·归 SMELL-P1-6] — **审批状态字符串双轨**(已统一)。核验全绿:前端无硬编码 'executed'(grep 全 src 零命中,AuditLog:84 是 executed_at 字段名/settings:120 是文案)+ 无 WHERE status='completed'/'executed' 统计查询(仅 pending 查询)+ find_cached SW-16 已透传真实值兼容双值 + ai_authorize_dir 无第二处判定。治 AuditLog executed 记录显示错位(蓝pending标签+raw"executed")+ DTO audit/mod.rs:53 契约对齐。**注:仅统一状态值(String 仍 String),SMELL-P1-6 String→newtype/enum 强类型化整体仍待决策(独立项,本修复不涉)**。
**P2 精选(25 项·详单见报告)**:try_continue pending_conv_id 跨会话泄漏(agentic:1314)/ patch_file FILE_LOCKS 永不清理内存泄漏(tool_registry:1248)/ auto-compress 成功未即时 save(agentic:778)/ MidStream 保文 chunk.error 未回填 err(stream_recv:275)/ audit_tool_call `let _ =` 吞 insert 错(finalize:35)/ switch+delete retain 未配套 finalize 占位终态化(conversation:317)/ PerConvState.created_at 全死字段(mod:520)/ PendingApproval.diff 写而不读(mod:570)。
**[2026-06-24 核验销账]**:audit_tool_call 吞错 ✅已修(commit a7dbd50,finalize:35 `let _ =`→match Err tracing::error,对齐 audit_finalize)+ switch+delete retain 占位终态化 ✅已修(a7dbd50,conversation/chat finalize_pending_placeholders pub(crate))+ patch_file FILE_LOCKS 永不清理 ✅已修(TD-260621-03 06-21,_patch_guard 串行化 + 删 entry().or_insert())+ PerConvState.created_at ✅已是正确态(mod.rs:772 #[allow(dead_code)]+预留注释「待 upsert 接入会话创建时间」)+ PendingApproval.diff ✅已是正确态(阶段3a 下沉 enum ApprovalKind::Risk{diff} 活调用链 audit/remote_bridge/useAiEvents,误删断前端 diff 预览);剩 3 项登记不动 —— try_continue pending_conv_id 🟡语义复杂(R-PD-6 优先审批 conv_id 设计+try_continue 回退,非确定性低风险)/ MidStream chunk.error 🟡UX 结构变更(Partial 保文加 err 字段+前端渲染,非 bug)/ auto-compress save 🟡需理解 loop save 时机判真缺口。
**销账核对(已登记 8 复核)**:SMELL-P1-6(line221·#5 覆盖 executed/completed 双轨)/ MED-1(tool_registry:546 bind_directory)/ REFACTOR-260619-08(tool_registry:881 拆)/ REFACTOR-260619-09(context.rs:1 God 1552行)/ ARC-260619-05(agentic:318 超长)/ UX-260617-28(双监听器 INFO)。**均仍在未完成,审查复核确认,保留**。
**残留诊断代码(待清)**:chat.rs `authz_debug`/`authz-debug.log`(L2 调试遗留,P2 报 chat.rs:548 硬编码绝对路径),L2 完成 + 用户实测后可清。
---
### 🔍 2026-06-21 查询能力缺口 + run_workflow 缺陷(本轮排查登记·未实施)
> 用户排查任务/项目/灵感查询能力现状,登记缺口为待办/缺陷,本轮不实施代码。详单见 [查询能力补全方案-2026-06-21.md](./02-架构设计/专项设计/查询能力补全方案-2026-06-21.md)。
- [x] BUG-260621-01 [P1🔴·确定修法] — **run_workflow 空 dag 反序列化失败(missing field nodes),阻塞任务工作流推进****2026-06-22 修复**:DagDef.nodes/edges 各加 `#[serde(default)]`,`{}` 反序列化成空 DagDef → workflow.rs:91 `is_empty()` 进 template_for 选模板。2 单测防回归(空 `{}` + 非空零回归),df-workflow cargo test 51 passed。— `crates/df-workflow/src/dag_def.rs` + `src/views/TaskDetail.vue`(:333)
- [x] F-260621-02 [P2] — **任务/项目/灵感列表查询维度补全(下沉后端+关键词搜索+排序分页)****2026-06-22 实施**:三实体新增 `list_by_query` 动态 WHERE(累积式 where_clauses+params_vec 收口)+ order_by 白名单防注入 + limit/offset 钳制;命令层 `list_{tasks,projects,ideas}``Option<XxxQuery>` 双参向后兼容(旧无参/单参路径等价全量);前端 Tasks/Ideas status/keyword 筛选下沉后端 query(Tasks.vue buildTaskQuery / Ideas.vue watch 防抖)。索引缺口已补(2026-06-22 V26 migrate_v26 idx_tasks_priority/assignee + SCHEMA 同步,commit 82059ba)+ P3 分页 UI 已落(Paginator.vue 客户端切片 + Tasks/Ideas/Projects 三视图接,commit 83cee61)。详见查询能力补全方案-2026-06-21.md。— `commands/{task,project,idea}.rs` + `crates/df-storage/src/crud/{task,project,idea}_repo.rs` + `src/api/{task,project,idea}.ts` + `src/views/{Tasks,Projects,Ideas}.vue`
---
### 📋 编排推进总览2026-06-18 更新)
未完成待办按可执行性分 8 组(详细条目见下方各分类,勿重复记录):
| 组 | 说明 | 代表项 |
|---|---|---|
| **A todo 卫生** | 矛盾清理/被取代退役标注 | ✅ 本批已完成 |
| **B 零风险减法** | 死链清/空壳合并 | ✅ 本批已完成 |
| **C 需用户输入** | 阻塞,无法代办 | S-260615-01 curl 测 / S-260614-01 多开澄清 / S-260614-02 实测重评 |
| **D 功能增强** | 设计清晰可推进P1 | F-15-01 审批选项 / F-15-02 task 详情 / F-15-03 分页⚠breaking / F-15-04 卡片折叠 |
| **E 架构重投入** | 需设计,非小改 | F-14-01 模型能力 / F-14-07 trait 下沉 / ARC-05·06 store 拆·循环依赖 |
| **F 全局 review P2 需设计** | 安全/竞态收口 | R-PD-4/5/6/8/9/12/13 + R-PD-10 .map_err 85 处 |
| **G 测试/dev 验证** | 收尾土壤 | ARC-08 dev 验证 / B-03b-R8 human 端到端 / T-14-01·02 Sprint 实测 |
| **H 长期功能池** | 不进主线 | F-14-02~10 / T-14-06 Settings 拆 / T-14-11 条件引擎 / B-14-05 / B-03b-R9 |
**推进原则**:能并行不串行(独立子任务 8-12 并发);每批重汇总+全优先级重排+销账核对 ✅C 组阻塞项不代办等用户。**本会话首批**A 卫生 + B 清死链。
**③④C类已迁**2026-06-16需人工决策项(③产品取舍 7 / ④设计方向 7 / 重投入排期 9 / C用户实测 5)见 [待决策.md](./待决策.md),从本文件抽离职责分离。**batch54 可自主批进展(2026-06-16·workflow wsfz7zl8i + 主代核查)**:复核-新⑪ 看门狗幽灵 ✅ 销账(清主窗口 state 视觉残留;**核验更正**:原 watchdog 幽灵已被 CR-260615-24 stopListener:371 clearStreamWatchdog 兜底);AE-03 diff预览 ⚠️ 降级阻塞(后端 write_file 未返 diff,待后端补,见 line 538);UX-04 断线保文 待 DEC-260616-03 授权(见待决策.md 🟡,本批不含)。
> **📦 已完成项归档**: [07-项目管理/todo归档/2026-06.md](./07-项目管理/todo归档/2026-06.md) — 2026-06-18 拆分, 已完成 `[x]` 与历史分析段迁此。另有 [2026-06-18.md](./07-项目管理/todo归档/2026-06-18.md) — 本次归档。
### 🔴 2026-06-20 BUG-260620-05 F-260619-03 工程内路径误弹窗(reload_allowed_dirs 丢 workspace_root·b22e9ae 回归)
**现象**:AI Chat file_info 等文件工具访问工程内路径(如 docs/02-架构设计/单对话并行多轮-设计-2026-06-20.md)触发授权弹窗,用户称"本来就有访问权限,为什么还来申请"。
**根因**:`state.rs:586-619 reload_allowed_dirs` 合并 `kv_dirs(KV allowed_dirs)+ project_dirs(projects.bind_directory)``all_dirs`,**仅当 `all_dirs.is_empty()` 时(:618)才插入 workspace_root**。KV 配过 allowed_dirs 或有绑定项目 → all_dirs 非空 → **workspace_root 不入 persistent** → 工程内路径(在 workspace_root 下)`is_authorized`(:356 starts_with)失败 → `check_path_authorization``NeedsAuthorization` → 弹窗。
**注释承诺失配**::346-348 / :583 / :617 注释均称"KV 未配时保持 workspace_root 免授权(向后兼容)",但 :618 条件用 `all_dirs`(含 project_dirs),project_dirs 非空也丢 root。b22e9ae "reload 尊重用户 persistent" 引入的回归。
**用户政策指令**(2026-06-20):"默认直接访问,明确知道无权限才申请"。指向工程内(workspace_root 下)默认免授权,仅 workspace_root 外非白名单路径才弹窗。
**修法(层1·1 行确定性 bug)**:`state.rs:618` 去掉 `if all_dirs.is_empty()` 条件包裹,**无条件** `set.insert(workspace_root_path())`(工程内始终免授权,对齐用户政策 + 注释承诺)。不破坏现有能力:workspace_root 经 `set/get_allowed_dirs`(:664/680 filter root)本就不可见不可删,"用户删 root"语义未落地,保 root 无冲突。
**层2 待决策(政策范围)**:用户"默认直接访问"是否要求更宽语义——workspace_root 外的非黑名单路径也默认放行(翻转 `is_authorized` 为黑名单制,实质去白名单)?涉 P0 去固定根决策方向,见待决策.md。dev 自用场景层1 即满足。
**关联**:F-260619-03(todo :528 b22e9ae 回归源)/ 待决策 workspace_root 分发适配(方案b 用户项目绑定)/ memory [[devflow-project-path-binding]]
**状态**:🔴 待用户授权(层1 1 行修法可直改;层2 待政策澄清)
---
### ✅ 2026-06-19 BUG-260619-06 L0 clear 致冷启动审批丢失(批2+跨批遗留·非batch8回归·方案A已修复)
> **来源**:CR-260619-06 巡检独立核验(不信主代自审 PASS)。F-09 batch8(commit 6ad4ec2)在 L0 握手新增 `session.pending_approvals.clear()`,与冷启动 restore 重建链路时序冲突,致重启后待审批工具**完全丢失**。
**现象**:重启 devflow 后,DB 里 status=pending 的待审批工具(tool_call)在前端不显示 toolCard,用户无法审批;即使触发审批报「未找到挂起的审批」。restore 功能被 batch8 clear 抵消。
**根因(跨函数时序冲突 + 设计契约违反)**:
1. **restore 填充(冷启动同步)**:`AppState::init`(`state.rs:500` setup block_on)→ `restore_pending_approvals`(`audit/restore.rs:26`)从 DB `list_pending`(`:27`)重建 pending 到 `session.pending_approvals` 内存。
2. **clear 必触发(冷启动必达)**:`AiChat.vue onMounted → store.startListener()``useAiEvents.ts emit('ai-client-ready')` → L0 回调(`lib.rs:38`)→ **`lib.rs:62` 原为 `session.pending_approvals.clear()` 无条件清空全部**(含 restore 重建)。
3. **前端不显示**:`ai_pending_tool_calls`(`commands/chat.rs:692` 数据源 = `session.pending_approvals.values()` 内存,非 DB)→ clear 后返空。
4. **审批报错**:`ai_approve`(`commands/chat.rs:344`)→ DB `find_by_tool_call_id`(`:358` match)查到 status=pending 但不在「已处理」白名单(`:360 executed/rejected/failed`)→ `:370 return Err「未找到挂起的审批」`
**batch8 回归证据**:`git show 6ad4ec2 -- lib.rs` 确认 `session.pending_approvals.clear()`(`:62`)是 batch8 **新增**;批2 版本 L0 只 reset generating,不 clear pending。
**设计契约直接违反**:`commands/conversation.rs:312-315` switchConversation 用 `retain(... != Some(&conversation_id))`(`:315`)精确保留 restore 重建的 pending,注释明说「防 init 重建的内存 HashMap 被清空,重启恢复链路:restore → switchConversation → ai_pending_tool_calls → ai_approve」。L0 `:62 clear()` 无条件清空全部 → 断该链路。
**修复方案**:
- **方案 A(推荐)**:`lib.rs:62` `clear()``retain(|_, a| !a.recovered)`。仅清非 recovered(本次会话/HMR 死 pending),保留 restore 重建(`PendingApproval.recovered=true` `audit.rs:331`)。对齐 switchConversation 保护意图,两全。
- **方案 B**:删 `lib.rs:62`(批2 前无此 clear;pending 清理由 switchConversation retain / delete_conversation retain `:1723` / ai_chat_clear 各路径精确管理)。
**核验清单(实施时)**:① cargo check --workspace;② 手测:DB 注入 status=pending 记录 → 重启 → 前端显待审批 → 审批成功落库;③ 回归 HMR 场景(后端不重启,死 pending 仍被清)。
**关联**:F-09 batch8 / CR-260619-06 / 详单见 [待审查.md CR-260619-06](./待审查.md)。
---
### 🔴 2026-06-18 Agentic 最大轮次设置不生效(设 30 仍按 10 截断·session-role-diagnose-only·未实施
> 用户报Settings 里 Agentic 最大循环轮次设 30但实际跑到 10 就提醒「达到最大」。走查全链路定位根因 + 记 todo不改代码。
**现象**:设置项显示 30前端持久值实际 loop 跑 10 轮即触发 `AiMaxRoundsReached`(达 max 提醒)。
**根因(后端内存态 + 恢复同步绑错生命周期)**
后端 `agent_max_iterations``Arc<AtomicUsize>` 纯内存(`state.rs:242`**重启回默认 10`DEFAULT_MAX_AGENT_ITERATIONS`),自身不落 DB**。持久化只在前端 `appSettings`(SQLite),后端靠前端启动时调 `ai_set_agent_max_iterations` IPC 同步恢复。而该同步 `syncAgentMaxIterations()` 只绑在 **`GeneralPanel.onMounted`**`GeneralPanel.vue:267`)—— GeneralPanel 是 Settings 页子组件,**只在用户打开 Settings 时才挂载**。两失效场景(任一即中):
1. **重启后直接用 AI Chat没先开 Settings 页)** → GeneralPanel 未挂载 → onMounted 不跑 → 后端停留默认 10 → loop 10 轮提醒;前端 Settings 显示 30持久值与后端实际 10 不一致。
2. **即便开了 Settings`appSettings` 缓存异步未就绪时**`settings.agentMaxIterations` 初值读到默认 10`:153`)→ onMounted 把 10 sync 给后端onMounted 只跑一次cache 就绪后不重跑)。
`agent_max_retries` 同病(同款 onMounted sync `:269`,后端 `agent_max_retries` 同款内存态 `state.rs:285-287`)。
**排除项**
- ✅ 后端 clamp 1-50`commands.rs:1680`——30 不会被压成 10。
-`@change→IPC` 链路(`GeneralPanel.vue:226-237`)——用户改值当时会同步,**当前会话生效**;失效只在重启/未挂载场景。
- ✅ loop 入口 load 快照(`commands.rs:138/264/739/879`)——热改后当前 loop 不受影响是设计(注释 :1670但用户是发**新消息**仍 10非当前 loop 热改问题。
**证据链**`state.rs:282-284`(默认 DEFAULT) · `agentic.rs:45`(`DEFAULT_MAX_AGENT_ITERATIONS=10`) · `commands.rs:1674-1683`(set 只 store AtomicUsize 不写 DB) · `GeneralPanel.vue:262-270`(恢复 sync 在 onMounted,非根组件) · `agentic.rs:891-909`(`!converged` emit AiMaxRoundsReached)。
**修法(二选一)**
- **B 推荐(前端小改)**`syncAgentMaxIterations()/syncAgentMaxRetries()` 从 GeneralPanel.onMounted **上提到 App.vue 根 onMounted**(应用启动必挂载)+ 确保 `appSettings` loadAll 就绪后再调(读持久真值)。利用前端已有 SQLite 持久化,几行改动,立即生效。
- **C 彻底(后端持久)**`AppState::init` 从 DB settings 表读 `agent_max_iterations/retries``ai_set_*` IPC 同时写 DB。后端自持不依赖前端同步时机改动较大但根治。
**待用户确认**:触发时是否「重启应用后直接发消息(没先开 Settings 页)」——若是,根因 1 坐实。
- [x] ✅(workflow wuirgcxoy·App.vue:215-224 onMounted loadAll+migrate 后加 setAgentMaxIterations/setAgentMaxRetries 恢复同步(clamp 对齐 GeneralPanel:228/245)·GeneralPanel:266 onMounted sync 保留双保险(IPC 幂等)·修bug正向重启后正确恢复持久值·主代 vue-tsc EXIT 0) **B-260618-23 [P1]****Agentic 最大轮次设置不生效(设 30 仍按 10 截断)**。后端 `agent_max_iterations`(`state.rs:242`) 纯内存 AtomicUsize 重启回默认 10、不落 DB恢复同步 `syncAgentMaxIterations` 只绑 `GeneralPanel.onMounted`(:267)GeneralPanel 仅 Settings 页挂载 → 重启后直接用 AI Chat 不同步 → 后端用默认 10 与前端显示 30 不一致 → 达 10 提醒。`agent_max_retries` 同病。**修法 B推荐**sync 上提到 App.vue 根 onMounted + 等 appSettings 就绪;**修法 C**:后端 AppState::init 从 DB 读持久化 + set IPC 写 DB。— `src/components/settings/GeneralPanel.vue`(:262-270) + `src/App.vue`(根 onMounted) 或 `src-tauri/src/state.rs`(:282-287) + `commands.rs`(:1674-1683)
### 🔧 2026-06-17 走查·tauri 打包目标收窄 + 状态映射 DRY仅分析·未实施
> 本轮 git diff 核验工作区未提交改动 + 最新提交 1cd7652。session-role-diagnose-only。
- [x] ✅(主代核验·tauri.conf.json:28 `["nsis"]` 已入库 commit a2871a6 非临时工作区·当前 Windows 开发保留合理·**WATCH**:跨平台意图待用户,若需 mac/Linux 改 `"all"` 或按平台条件配置) B-260617-11 [P2] — **tauri.conf.json 打包目标收窄**`bundle.targets: ["nsis"]`(:28)锁 Windows NSIS,已入库。当前 Windows 开发保留;跨平台待用户定。—— src-tauri/tauri.conf.json(:28)
### 🔧 2026-06-17 走查·DeepSeek reasoning_content 实施审查P1 提交不完整)
> 核验 HEAD 74003bcBUG-260617-12 修复提交)+ 工作区残留。session-role-diagnose-only逐行核验非信声明。**闭环逻辑正确(见 :49 注记),但发现提交不完整 P1**。
- [x] ✅(2026-06-20 核验·MidStream 保文已回填 reasoning_content 到 ChatMessage,mod.rs:1058/1116/1121·P3 观察项已处理) B-260617-16 [P3·可选] — **Partial(MidStream 保文)回填半截 reasoning_content 语义待评**。agentic.rs MidStream 保文路径用 `round_reasoning_content`(本轮部分累积)写回 ChatMessage回传下一轮 DeepSeek 会看到不完整推理。属异常路径(网络中断保文),保文后已加"响应不完整"系统提示,用户大概率重发。影响小,登记观察。— src-tauri/src/commands/ai/agentic.rs(:765)
### 🔧 2026-06-17 对话标题不更新(还叫"新对话"·待运行时验证)
> 用户报:连续对话很多内容 + 刷新重启后,标题仍"新对话"。session-role-diagnose-only排查链路 + 记 todo。
**排查结论(排除项)**
-**save_conversation 不覆盖 title**(核验 conversation.rs:166-194已落库分支只改 messages/updated_at/token/model**不碰 title**;首次落库 `title:None` :194。排除"save 重置 title"。
-**title 初始 None**(懒创建不落库 commands.rs:1286 + 首次落库 None→ ensure_conversation_title :38-42 `is_some()`=false → **不跳过**,应生成。
-**extract_title 兜底**title.rs:157 取首句前 30 字)即使 LLM 失败也应落库非"新对话"。
**最可能根因(待运行时 tracing 验证)**
- **嫌疑 a**ensure_conversation_title 在 spawn 后台 taskagentic.rs:924-931`generate_title_via_llm`title.rs:90/120 网络耗时)**未完成时用户已刷新/重启** → 标题未落库 → 重启读 NULL → "新对话"。
- **嫌疑 b**title.rs:95 `let _ = conv_repo.update_field(...)` 落库失败被吞(无日志),标题没入库但 :96 emit 照发(前端短暂更新,重启丢)。
- **嫌疑 c**title.rs:141-142 双层信号量 acquire_global+acquire_per_conv 阻塞permit 未释放)→ ensure 永不完成。
**矛盾点**:用户"很多内容多次对话"应多次触发 ensureagentic.rs:462/776/843/930 四点),总有完成的——除非每次刷新打断或信号量持续阻塞。**确诊需运行时日志**grep tracing 看"标题生成跳过"(title.rs:72)/"更新对话失败"(conversation.rs:186) / ensure 是否触发。
**修法方向(待确诊后)**:① ensure 改同步loop 内 await完成后再 emit Completed② :95 update_field 失败加 error 日志 ③ 排查信号量阻塞。
- [x] ✅(2026-06-18·根因重定:非刷新时序,是 LLM 标题生成卡住时无即时兜底·修:title.rs ensure_conversation_title 进入即 extract_title 兜底落库+emit(LLM 成功覆盖)+LLM 调用包 tokio::time::timeout 20s 防卡死堆积·cargo check src-tauri EXIT 0) **B-260617-17 [P2]****对话标题不更新(对话很久/刷新后仍"新对话"**。save 不覆盖 title已排除。**根因(重定)**:`ensure_conversation_title` 原仅在 LLM 返回 None 或 provider 构建失败时落 extract_title 兜底,但 LLM **卡住**(generate_title_via_llm 的 llm_concurrency 信号量 acquire 阻塞/网络挂起/spawn 未跑完)时既不返回 None 也不落库 → 标题长期停留"新对话"。`spawn_ensure_title` 在 agentic 4 处(471/785/852/939)触发均中招(原记"嫌疑 a 刷新时序"为表象之一)。**修法**:进入即 extract 兜底落库+emit(侧栏即时非新对话),LLM 成功后覆盖;LLM 调用包 20s 超时,超时/失败保留兜底。— `src-tauri/src/commands/ai/title.rs`(ensure_conversation_title + generate_title_via_llm 外层 timeout)。**配套(历史会话点开重总结)**:`ai_conversation_switch`(commands.rs:1454) 加 `AppHandle` 参数,title 空(`record.title.is_none()`)时 `drop(session)``get_active_provider` + `spawn_ensure_title` 触发重生成(无 provider 静默跳过,不阻塞切换;ensure 内 title Some 判断防重复)。前端无需改(AppHandle 自动注入)。cargo check src-tauri EXIT 0。
### 💡 2026-06-16 新需求(已分析·待实施)
> 功能需求,已走查定位链路 + 方案记录,待用户确认后实施。
- [ ] F-260616-09 [P1/架构·**A路线✅(batch60 + 06-19核验补漏已落地:commands.rs:1404/useAiConversations.ts:80 → 阶段1跳过),B 设计完成 2026-06-19([F-09设计文档](./02-架构设计/已编号方案/F-09-多会话并发架构设计-2026-06-19.md))·阶段2 批1-8 待拍板 b-1(messages per-conv)启动(见待决策.md)·**messages per-conv 已落地(2026-06-20 核验):mod.rs:326 `pub per_conv: HashMap<String, PerConvState>` + :384 `conv(&mut self, conv_id)` 访问器 + :394 `conv_read` + `tests_f09_per_conv` 测试模块 + 决策e 真并发已上线(见文件末 F-09 决策e 段,CR-260620-02 审 PASS)****] — **AiSession 单例 → 多会话并发架构(多窗口来回切 + 并行执行)**。用户需求(3 条消息强化):「应用级支持同时具备多会话」+「同时具备多会话的能力」+「多个会话窗口来回切,**并行执行**」。现状Explore 走查确认):`AiSession` **应用级单例**`state.rs:164` `ai_session: Arc<Mutex<AiSession>>`init:205messages/generating/stop_flag/pending_approvals/agent_language 全一份。设计「单例 + 软隔离」——generating 互斥(同一时刻**只一个对话能跑**) + 切换旧 loop 退出(`agentic.rs:177-190` B-260615-11) + 切换时 generating→readonly(`commands.rs:873-881`)。`llm_concurrency` per_conv 因此退化为单对话内并发(`state.rs:93-96` 注释已标注预留改 HashMap)。**memory 已标记「AiSession 单例未动」架构债**(aichat-arch-extensibility)。**残留 bug**(表象=新建会话上下文残留):前端 `newConversation`(`useAiConversations.ts:31-41`) 漏清 queue/generatingConvId/agentRound/searchQuery后端 `ai_conversation_create`(`commands.rs:784-821`) 漏清 agent_language/stop_flag(设 true 停旧 loop 没复位 false)。**用户「并行执行」明确=B 方向**(A 只解切换残留不满足并行)。**分档****A 短期隔离修复**(补漏清字段,维持单例软隔离,解残留,低风险立即做,**B 前置**) + **B 多会话并发架构**(单例 → `HashMap<conv_id, SessionState>`,每会话独立 messages/generating/stop_flag/pending_approvals + llm_concurrency per_conv 改真 per-conv HashMap + loop 内校验改 conv_id 索引各自 session + 切换不退出旧 loop 各自跑完真并发 + 事件路由核对 conversation_id 全覆盖)。**决策点(待定)**(a) A 先做解燃眉 vs 直接 B——倾向 **A 先**(残留真 bug 立即解B 架构另立项) (b) B 形态——AiSession 整体 HashMap vs 拆字段(messages/pending 已可按 conv reload只拆 generating/stop_flag per-conv)——倾向**后者侵入小** (c) B 并发上限——复用 llm_concurrency.global 限并发会话数防 token 暴增——倾向是 (d) **多窗口 UI 形态**——(d1) 单面板侧栏会话列表切换+后台并行(改动小) vs (d2) 每会话独立 Tauri 窗口(`useAiWindow.ts` detached 已有基础可扩,改动大但「窗口来回切」语义最贴)——**待用户定**,倾向先 d1 后 d2 (e) 旧 loop 切换处理——不退出各自跑完(真并发,倾向) vs 仍退出。— state.rs(AiSession 去单例化) + commands.rs(create/switch 重构 + 漏清补) + agentic.rs(:177-190 校验改 conv_id 索引) + context.rs(ContextManager per-conv) + useAiConversations.ts(漏清补) + stores/ai.ts(per-conv state) + llm_concurrency per_conv + useAiWindow.ts(若 d2 多窗口)。**B 是大改架构升级,需单独设计文档 + 立项**。**✅ 已决(2026-06-16·待决策归档):a — A 隔离修复 + B 立项(🔥 B 阶段高优先级尽快实施),UI d1+d2 都做(侧栏切换 + 独立 Tauri 窗口)**。
- [ ] F-260616-12 [P2/依赖F-09] — **retry 持 permit 不释放(多会话隐患)**。F-260616-07 落地的流式重试循环(`agentic.rs:238-239`)重试期间持有 global+per_conv permit 不释放(注释「防新请求挤占」)。当前 AiSession 单例 + 主 loop 串行无影响,但 **F-260616-09 多会话并发后**,重试期间阻塞其他对话 LLM 调用。**方向**:多会话落地时核对——主 loop 串行下重试持 permit 防自己挤占无意义,倾向重试不持 permit 或仅持 per_conv。— agentic.rs:238 + F-260616-09 多会话架构。**依赖 F-260616-09 立项后一并处理**。
### 💡 2026-06-16 新需求UX 交互优化·分析完成·待实施)
> 用户实时反馈的交互优化需求,已走查定位链路+方案记录。
- [x] ✅(F-15 全阶段完成·波3 is_active白名单+push token / 波5 compress_prompt四段式+compress_via_llm+context辅助 / 波6 阶段2手动 2IPC+3事件+前端2按钮 / 波8 阶段3自动 loop顶部检测压缩+降级·主代分波核查 cargo0err+vue-tsc0+df-ai/devflow test passed) **F-260616-15** [P1] — **AI Chat 上下文管理增强:会话分段 + 手动压缩 + 智能裁剪**。三个子需求,详见 [F-15-上下文管理增强设计-2026-06-16.md](./02-架构设计/已编号方案/F-15-上下文管理增强设计-2026-06-16.md)。**阶段1 基础部分 ✅(波3·8a142c2 is_active 白名单+push token 修正)**阶段1 剩余 ✅(波5·63bff8b compress_prompt四段式+compress_via_llm+context 9辅助方法,幂等/单向不可逆/纯函数/零行为变化,df-ai 107pass)阶段2 手动功能 ✅(波6·4194842+主代核查,cargo0err+vue-tsc0+devflow79pass,2 IPC ai_chat_clear_context/compress_context+3事件 AiContextCleared/AiCompressing/AiCompressed+前端2按钮+status分组渲染+useAiContext composable,clear标archived_segment不删DB/compress LLM失败不阻塞)阶段3 自动 ✅(波8·0202b51+主代核查,cargo0err+vue-tsc0+devflow79pass,loop顶部检测budget×0.6+压缩+降级原裁剪,set_compressing成对,延迟mutate失败零副作用)。**F-15 全阶段完成 ✅**。
**核心问题**:① `ai_chat_clear` 全删 DB 消息不可回溯 ② `build_for_request` 超预算直接丢弃旧消息零保留(`agentic.rs:226` `_trimmed` 被忽略) ③ 无压缩能力。
**统一方案**`ChatMessage.status` 扩展两个值 `archived_segment`(分段标记)/`compressed`(压缩标记)`is_active()` 改正面白名单 `matches!(status, None | Some("active"))`(零行为变化,新状态自动不 active`sanitize_messages` step 0 已是唯一发送过滤点改 is_active 即隔离旧段/压缩段。`push()`(context.rs:174) 不计 `!is_active()` 消息 token当前全量计入致 restore 后 token 虚高误判超预算)。
**① 会话分段**IPC `ai_chat_clear_context`:当前 active 消息标 `archived_segment` + 扣除 history_tokens + 落库。不插分隔线 system前端按 status 渲染折叠分隔条)。三元组原子标记(复用 `build_eviction_units` 分组)。
**② 手动压缩**IPC `ai_chat_compress_context`:取 active 消息 → `compress_prompt` 四段式(意图/决策/文件/约束) → `provider.complete()` 非流式调用(复用 `title.rs:111` `generate_title_via_llm` 模式 + `build_provider_for`) → 原始消息标 `compressed` + 插入摘要 system。压缩单向不可逆DB 原始消息保留。
**③ 智能裁剪(自动压缩)**agentic loop 循环体顶部 `build_for_request` 前检测:`history_tokens > budget×0.6` 且有可压缩消息 → `compress_old_messages`(保护区外消息 LLM 摘要 → 标 compressed → 插摘要 system→ emit `AiCompressing`/`AiCompressed`。LLM 失败降级原有裁剪(不阻塞 loop`compress_old_messages` 是手动/自动共用的公共函数。幂等:已 compressed 不二次压缩,`is_compressing()` 标志防重入。
**实施顺序**阶段1 基础(is_active 白名单+push token 修正+compress_prompt+compress_via_llm+辅助方法) → 阶段2 手动功能(2 IPC+前端按钮+渲染+i18n) → 阶段3 自动(loop 压缩+事件+前端 loading)。阶段1-2 完成用户已有完整能力。
**涉及文件**`df-ai-core/provider.rs`(is_active 白名单) + `df-ai/context.rs`(push token 修正+辅助方法) + `commands/ai/prompt.rs`(compress_prompt) + `commands/ai/title.rs 或新文件`(compress_via_llm) + `commands/ai/commands.rs`(2 IPC) + `commands/ai/agentic.rs`(loop 自动压缩) + `commands/ai/mod.rs`(2 事件) + `lib.rs` + `api/ai.ts` + `useAiPanel.ts` + `useAiConversations.ts:79`(过滤扩展) + `useAiEvents.ts` + `AiChat.vue` + `i18n/{zh-CN,en}/aiChat.ts`
### 🔴 aichat API Key 401 排查2026-06-16
> 用户报对话失败「调用失败: API Key 无效或无权限」+ 重设 key 无效。**排查结论:代码链路全对(保存 keyring✓ / 读取 resolve_provider_secret✓ / 鉴权头 openai=Bearer·anthropic=x-api-key✓ / URL 智能拼接✓),401 来自服务端,非 devflow bug**。根因四选一(key 无效 / provider_type 不匹配 / base_url 错 / model 名错)。详见 [aichat-apikey-401排查-2026-06-15.md](./09-问题排查/aichat-apikey-401排查-2026-06-15.md)。
- [ ] S-260615-01 — **[待用户确认根因]** 用户跑直连测试(curl)区分 key/provider_type/base_url/model 哪项错,见详情文档「验证步骤」
### 🟦 架构审查待修项2026-06-16
> 纯架构层评估(边界/依赖/抽象/扩展性/状态管理),详见 [架构审查-2026-06-15.md](./05-代码审查/架构审查-2026-06-15.md)。共 🔴6 🟡6 ⚪4 + 亮点 6。与 06-14 三份报告去重(不重复 bug/性能 FR-*、aichat AR-*)。
**中期(技术债)**
- [ ] ARC-260615-07 — 其余见文档df-core 改名 df-types类型库非核心/ src-tauri IPC 编排层抽取(df-app5711 行成事实业务层) / AI agent loop 从 IPC 下沉 df-ai / 3 view 绕 store 调 api / 类型契约 ts-rs 代码生成 / AiSession 多会话(B 路线前置) / AppState 分组 / IPC 命名统一
### P1 — 重要缺陷
- [ ] T-260614-01 — **[P1]** Sprint 9/10/14/15/16/18 多项未 tauri dev 实测 — 评分 IPC 缩放 / update_full / promote_idea / Store getter / token 落库 / 知识库 Tier 1 全栈 / LLM 并发 Semaphore / 知识生命线(#54 跟踪)— source:Sprint 9-18 (06-14)
- [ ] T-260614-02 — **[P1]** 切对话不中断路由部分场景运行时实测A 路线场景 2/3 — source:Sprint 8 (06-14)
- [x] ✅(workflow w999qdu86·ai_approve None 分支 match 三路分流对齐 audit_finalize·cargo check EXIT 0 + vue-tsc EXIT 0 + 主代 grep 印证) **BUG-260618-11****[P0🔴]** `commands.rs:289` ai_approve 幂等路径 `unwrap_or_default` 吞 DB 错误 — audit.rs 已修(B-260617-17 match 三路分流),但 ai_approve 的 find_by_tool_call_id 仍 unwrap_or_default。DB 故障→Err 压 None→返回「未找到挂起审批」误导。**修复**: 对齐 audit_finalize 模式 match { Ok(Some)→rec, Ok(None)→return Err, Err(e)→error!+return Err } — source:CR-260618-11 batch69 全面走查 (06-18)。**行为变更**:DB 故障现返回「查询审批记录失败(DB 故障),请重试」明确错误(原误导为「未找到挂起」),两者同属 Result Err 分支调用方语义不变,仅文案更精确可重试。
### 🔴 架构坏味道全面扫描2026-06-18·6路并行 Explore agent·57K行代码
> 来源: God文件/长函数 + 循环依赖/耦合 + DRY违反 + 错误处理不一致 + 命名API/类型安全 + 测试覆盖/死代码。6 维度全覆盖。
**🔴 P0 必须修复 (4项)**:
- [x] ✅(workflow w5siwnipj 核验+主代实施·agentic:348/knowledge_inject:130,139,312/idea:292-296 加 tracing::warn 降级不改返回值·audit:344 误报(CR-11 已修三路 match)·commands:290 BUG-11 已修·cargo check EXIT 0 + vue-tsc EXIT 0) **SMELL-P0-1****[P0🔴]** `unwrap_or_default` 吞错 **5 高危闭环**。agentic.rs:348(provider 池空走原空池兜底)/knowledge_inject.rs:130,139,312(检索/解析失败走原降级不注入/提炼跳过)/idea.rs:296(tags 坏降级空继续评估)本批 match+warn(空 Vec 零行为变更);audit.rs:344 误报(audit_finalize CR-11 已修三路 match,line 漂移+旧版本);commands.rs:290 BUG-11 已修。**58 处全量排查降 P2 非紧急**(高危已清,余为 Option 取默认/JSON 序列化等低危)。
- [x] ✅(workflow w2xkw4ybh 抽 register_data_tools(18 持 db 工具:list_projects/list_tasks/list_ideas/update_project/create_project/bind_directory/create_task/update_task/advance_task/run_workflow/delete_task/create_idea/delete_project/restore_project/purge_project/list_trash/get_project_count/get_task_count)+ 主代抽 register_file_tools(10 文件工具:run_command/read_file/list_directory/write_file/patch_file/file_info/append_file/delete_file/rename_file/search_files)·build_ai_tool_registry 1091→7 行·加基线测试 test_build_ai_tool_registry_baseline_tool_count(Database::open_in_memory 断言 len()==28 + tool_names() 集合锁定)·主代核查 cargo check --workspace EXIT 0 + cargo test df-ai 119 passed + devflow 基线 1 passed + vue-tsc EXIT 0) **SMELL-P0-2****[P0🔴]** `tool_registry.rs:363 build_ai_tool_registry` **1091 行单函数** — 拆分为按功能分组注册函数(register_data_tools 18 db 工具 / register_file_tools 10 文件工具)build_ai_tool_registry 收敛到 7 行编排 + 基线测试锁定工具数防回归。**注(2026-06-20 核验):基线已升至 29(tool_registry.rs:1841 `test_build_ai_tool_registry_baseline_tool_count` 断言 `len()==29`,18 data + 10 file + 1 http;F-260619-03 Phase A 后 http_request 注册致 +1)。**
- [x] ✅(2026-06-20 核验·AiChat.vue 4075→750 行·`src/components/ai/` 子组件已拆 ConversationSidebar/MessageList/ChatInput/TopBar/DirAuthDialog/MaxRoundsCard·状态/composable 已外移·与 REFACTOR-260619-07 同件) **SMELL-P0-3****[P0🔴]** `AiChat.vue` **4026 行 God 组件** — 拆分: ConversationSidebar(侧栏+搜索) / MessageList(消息列表+流式) / ChatInput(输入框+附件) / ApprovalPanel(审批卡片)。目标单组件 <500 行。
- [x] ✅(workflow wowdnw4ba·tests/shell.rs 5 #[tokio::test] 覆盖成功/非零/超时/env/working_dir + shell.rs:55 过时TODO注释清理·execute逻辑零改动·主代 cargo test -p df-execute 5 passed + cargo check --workspace EXIT 0) **SMELL-P0-4****[P0🔴]** `df-execute` crate **零测试**(shell.rs 120行已集成工作流) — 补 shell 命令执行+超时+输出截断基础测试。
**🟡 P1 应该改进 (9项)**:
- [x] ✅(workflow w8774xcev·7 agent 按 crate 全域扫·绝大多数 no-action:生产 unwrap 全在 #[cfg(test)] 或已是安全 expect/unwrap_or(crates 错误处理健康,之前多批重构清理)·仅 df-workflow dag.rs:106/135 两处运行时 unwrap 加 safe 注释(contains 守卫+in_degree 全覆盖不变量)·主代 cargo check --workspace EXIT 0 + cargo test df-workflow EXIT 0) **SMELL-P1-1****[P1🟡]** 生产代码 206 处 `unwrap()` 排查——逐处加注释说明为何安全(如"guaranteed by upstream invariant")或改 `expect("msg")`/`?`/`ok_or_else`
- [x] ✅(workflow wuirgcxoy no-action·前提过时:err_str helper 已存在 src-tauri/src/commands/mod.rs:23·纯 .map_err(|e|e.to_string()) 样板 0 残留(仅 mod.rs:22 注释字面量)·101 处已用 err_str·剩 5 处 String 上下文闭包+28 处 anyhow 不同抽象层不应统一·主代 grep 印证) **SMELL-P1-2****[P1🟡]** IPC 层 `.map_err(err_str)?` 样板 100+ 处 —— 抽 `fn err_msg(s: &str) -> impl FnOnce(E) -> AppError` 或 Result 扩展方法
- [x] ✅(workflow wuirgcxoy no-action·前提不成立:views 仅 AuditLog.vue:102 直接 invoke·invoke 已统一 src/api 层封装(9 文件)·try-catch-finally 副作用高度异构(scanError/showToast/task=null/递归回退等)无标准 loading/error/invoke 三件套可封装·主代 grep 印证) **SMELL-P1-3****[P1🟡]** Vue views try-catch-finally invoke 模式 15+ 处重复 —— 抽 `useTauriCommand<T>(cmd, args)` composable 封装 loading/error 状态
- [x] ✅(workflow w8774xcev 评估·df-nodes Cargo.toml 依赖 5 crate 属实·task_state_machine.rs 零 crate 依赖(仅 HashMap+OnceLock)+单一消费者 advance_task_atomic·拆出不解锁循环依赖·结论缓做:收益不抵成本,触发条件=第二 crate 需复用状态机判定时再拆) **SMELL-P1-4****[P1🟡]** df-nodes 聚合点依赖 5 crate —— 评估拆分 df-task-state-machine(状态机从节点实现分离)
- [x] ✅(workflow weckqp9mv no-action·现场核验两半:advance_task_atomic 已在领域层 df-nodes/task_advance_node.rs:43(状态机+CAS+bump_rounds),3 IPC 调用点 task.rs:171/workflow.rs:319/tool_registry.rs:565 纯 thin 转发零逻辑✅已达成;record_to_idea 下沉经依赖图核验为错误方向——df-ideas→df-storage 倒依赖违反六边形架构(IdeaRecord 是 storage DTO,IPC 层是 storage-DTO→domain-entity 映射的正确 adapter 归属)·主代 grep 印证) **SMELL-P1-5****[P1🟡]** IPC 层职责泄漏 —— `advance_task_atomic`(workflow.rs) / `record_to_idea`(idea.rs) 转换逻辑下沉 domain 层
- [ ] **SMELL-P1-6****[P1🟡]** String 类型滥用替 newtype/enum —— execution_id/status/tool_type 等 5+ 处改强类型
- [x] ✅(workflow weckqp9mv no-action·enum 负收益:archived/pinned 是 bool DB 列直映射,动作式 enum 在 crud 边界每调必 bool↔enum 转换只搬噪声;2 crud+2 IPC 调用者紧邻定义已自解释;真正歧义在前端 store.archiveConversation(id,true) 但正解是拆 archive/unarchive 两 TS 方法(纯前端重构)非推 enum 到 JS 边界;IPC 引 enum 触 Tauri 序列化层 prod runtime 才报错·主代 grep 印证) **SMELL-P1-7****[P1🟡]** bool 参数陷阱 —— `set_archived(id, bool)` / `set_pinned(id, bool)` 改 enum
- [x] ✅(workflow wowdnw4ba·6篇文档加「实施状态(2026-06-18核对)」标注段·grep file:line佐证·不改设计正文·主代 grep 6篇标注段全在:B-03:11/AiNode:11/类型对齐:7/F-01:11/F-05:10/secret:10) **SMELL-P1-8****[P1🟡]** 架构文档过时 6 篇更新 —— B-03/AiNode自审/前后端类型对齐/F-01/F-05/secret下沉 标注实际实施状态
- [x] ✅(agent crud-split 实施 + 主代兜底核验·crud.rs 2212行→crud/ 6文件(mod/settings/project_repo/task_repo/conversation_repo/idea_repo)·re-export `pub use *_repo::*` 零调用方改动·宏 pub(crate) use + 子模块 use super::impl_repo·基线测试 all_known_tables_have_column_whitelist(12表)+ all_repos_constructible_in_memory(13Repo)·主代 cargo check --workspace EXIT 0 + df-storage 35+11 passed + devflow 96 passed) **SMELL-P1-9****[P1🟡]** crud.rs 2212 行按表拆分 —— project_repo / task_repo / conversation_repo / idea_repo 独立文件
**⚪ P2 可选优化 (5项)**:
- [x] ✅(workflow wowdnw4ba no-action·SMELL描述过时:实测run方法151行+2参非描述的282行+8参·SW-01 TOCTOU保留(line136守卫/138-153对称emit)·描述系历史形态已被先前重构改善·销账) **SMELL-P2-1** — run_workflow_inner 282行+8参数 → 配置对象封装 + 阶段子函数提取
- [x] ✅(workflow wowdnw4ba·main.ts app.config.errorHandler + ErrorBoundary.vue onErrorCaptured 降级UI+重试 + i18n error.ts×2中英对称·未嵌入仅提供能力零行为变更·主代 vue-tsc EXIT 0) **SMELL-P2-2** — 前端全局 ErrorBoundary(Vue errorHandler)
- [x] ✅(workflow w8774xcev 评估·**描述修正**:NodeContext 无 metadata 字段(node.rs:13-26),含 metadata 的是 NodeOutput(node.rs:34)·NodeOutput.metadata 死字段(2构造点初始化空 HashMap+0消费者)·ShellRequest.env 同死字段(5构造点全 HashMap::new() 空)·改结构体波及小但纯类型整洁 ROI 低·建议直接删字段或保留,不实施) **SMELL-P2-3** — HashMap<String,String> → 结构体(NodeContext.metadata / ShellRequest.env)
- [x] ✅(workflow w5siwnipj 核验·line 3 模块注释已有「⚠ B 路线占位...有意保留空壳,勿删」三要素齐全·零外部调用·保留 B 路线预留点) **SMELL-P2-4** — coordinator.rs 空壳标注 roadmap **已完成**(line 3 注释),保留非删(B 路线 aichat 决策能力预留,对齐 memory aichat-arch-extensibility)
- [x] ✅(workflow w5siwnipj 核验 7 处+主代补 #6/#7 注释·全部标注完成·cargo check df-ai EXIT 0) **SMELL-P2-5** — #[allow(dead_code)] **7 处全部标注完成**(实际 7 非声称 12,部分已清)。5 处原有注释(prompt.rs:147/compress.rs:45,98 F-15 阶段预留 + openai_compat.rs:81,98 SW-24 反序列化对齐)+ 本批补 anthropic_compat.rs:61 id/:65 stop_reason 注释(对齐 openai SW-24 风格)。全合理保留无真死代码。**另:cargo warning 死字段 4 处**(PendingApproval.risk_level/diff + AppState.releases/node_executions)属另一类未标 allow,待核对。
**🆕 sweep 派生登记2026-06-18·workflow w2xkw4ybh data 抽出 + wo7d6zdqm 多维度 sweep**:
- [ ] ARC-260618-01-d [P1/🟡已实现待启用] — **conditions 条件引擎**(核验2026-06-22:conditions.rs:31 ConditionEngine + executor.rs:106 已调 evaluate 求值 + executor.rs:95 `cfg!(feature="conditions-eval")` feature flag 控制,求值逻辑已实现待产品确认启用时机)。`df-workflow/conditions.rs` `ConditionEngine` 零消费(无调用方),`Edge.condition` 字段存储但工作流执行器未求值分支——边条件当前形同虚设。**产品决策点**:① 实现条件求值(executor 在节点完成后求值出边 condition 决定路由)还是 ② 标 `#[allow(dead_code)]` + 文档明示"条件为预留未启用"。涉及行为变更(executor 控制流改变),确认产品意图后再实施。对齐 H 组长期池 T-14-11 条件引擎(line 42 仅指针无详情)。— `crates/df-workflow/src/conditions.rs`(ConditionEngine) + `crates/df-workflow/src/executor.rs`(边求值点)
- [x] ✅(2026-06-22 核验·adversarial.rs:147 `self.recommendation_for(&analyst.final_assessment)` 已守卫 final_assessment/recommendation 一致性,矛盾组合经 recommendation_for 收敛·漏勾销账) **ARC-260618-01-e** — adversarial evaluate_with_llm 一致性。已有守卫。`df-ideas/adversarial.rs` `evaluate_with_llm` 返回的 `final_assessment`/`recommendation` 两个字段语义一致性未校验(如 final_assessment=强烈反对 但 recommendation=promote 的矛盾组合无守卫)。LLM 输出存在字段间语义漂移风险。**产品决策点**:① 加一致性校验(矛盾时降级或重评)还是 ② 视为 LLM 自由表达不加约束。涉及行为变更(评估结果可能被改写),确认产品意图后再实施。— `crates/df-ideas/src/adversarial.rs`(evaluate_with_llm 返回结构)
- [x] ✅(2026-06-20·formatRelativeZh→formatRelative 重命名 6 文件:time.ts 定义+注释去「中文」限定 + ConversationSidebar/MessageList/ActiveProjectsPanel/AuditLog/Tasks 5 调用点·AiChat 已拆 750 行无引用·grep 全 src 无残留 + vue-tsc --noEmit EXIT 0) SW-260618-21 [P2] — **formatRelativeZh 重命名 formatRelative**`src/utils/time.ts` `formatRelativeZh` 名带 Zh 后缀但实际非中文硬编码(已读 i18n.global.locale, sweep 批2 time.ts formatDate 已修 CR-260615-08 漏修),函数名误导。原缓做前提(AiChat 沉淀)已满足,本次落地。— `src/utils/time.ts`(formatRelative) + 5 调用点(ConversationSidebar/MessageList/ActiveProjectsPanel/AuditLog/Tasks)
- [x] ✅(2026-06-20 核验·resolveLang 已抽 `src/composables/ai/aiShared.ts:30-35 resolveAiLang` 共享,消除重复) SW-260618-22 [P2/已知 B-260617-03] — **useAiSend resolveLang DRY**`useAiSend``resolveLang` 与其他 composable 语言解析逻辑重复,应抽 `aiShared` 共享。已知登记为 B-260617-03,此处补 sweep 维度记录便于检索。— `src/composables/ai/useAiSend.ts`(resolveLang) → 抽 `src/composables/ai/aiShared.ts`
### 🟡 AI Chat 交互体验改进2026-06-14 方案 → 待办化)
> 来源:[aichat交互体验改进方案-2026-06-14.md](./02-架构设计/构想审查/aichat交互体验改进方案-2026-06-14.md)。7 大类 20 个改进项,按原文优先级分 4 批。**去重**§1.3 复制 ⊂ F-260615-13/14已写、§3.4 新建中断 ⊂ B-260615-10已做软复位本文档加 confirm 增强、§6.2 分离窗口同步 ⊂ CR-24/25已写。**状态: 待讨论→看板跟踪**。
**第三批 — 锦上添花**
- [ ] UX-2025-12 [P2] — **历史消息分页懒加载**§2.3)。**▶ 已打开推进2026-06-17 用户决定不再等痛点,与 UX-19 一并纳入)**。方案switchConversation 首次加载最近 50 条,滚顶加载更多(需后端 messages 查询加 offset/limit 支持)。关联 B-260629(messages 无上限)。**决策点(待定)——与 UX-19(虚拟滚动)二选一**解决长对话渲染卡顿:分页(后端配合,简单稳定,但切换/搜索跨页体验割裂vs 虚拟滚动(纯前端,体验流畅,但流式末条挂载复杂)。**倾向 UX-19 虚拟滚动优先**(纯前端零后端改动+体验优),本项分页作为虚拟滚动不足时的补充方案。— src/stores/ai.ts + src/composables/ai/useAiConversations.ts + 后端 conversation.rs
### 待澄清 / A-B 待定
- [ ] S-260614-01 — **[用户已澄清 2026-06-17]** 「显示多开」= 多个会话来回切都可对话(非 AI Chat 窗口多开)。关联 T-260614-02(切对话不中断实测)+ F-09 多会话架构。**A 路线(单例软隔离,已做)**:切会话软复位+DB 加载上下文+active 切换,来回切应能对话(需实测 T-260614-02)。**B 路线(真多会话,未做)**:切走会话 generating 不打断,每会话独立 AiSession(memory aichat-arch-extensibility 单例未动)。**✅ 已并入 F-260616-09(2026-06-16 已决 a):A 路线已实施待实测(T-260614-02),B 高优先立项,详见待决策归档/2026-06.md**
- [ ] S-260614-02 — 审批可见性 A/B 待定 — B-01 修复后 pending_approvals 内存态不再被 switch 清空,前端 `ai_pending_tool_calls` 查询有数据,但 `state.pendingApprovals` 在 AiChat.vue 是否有兜底渲染仍需实测确认。A. 加兜底渲染 / B. 实测 tc 卡片是否渲染 — **去重**:与 AR-3 审批卡片可读化重叠AR-3 修完reason 拼对象名 + id 特化 + 卡片完整)后改实测重评 (06-14)
### P2 — 不阻断缺陷 / 增强
- [x] ✅(2026-06-20 核验·reattachPanel 接线 useAiWindow.ts:98 + tauri://destroyed 监听:86 已修·localStorage df-ai-gen/df-ai-text Sprint19 有意保留) B-260614-05 — **[P2→降级]** 分离窗口detached跨窗口状态 — **核对修正**reattachPanel 已接线(不再死代码)+ `tauri://destroyed` 监听已复位状态,"detached 永真卡死"已修复;剩余 localStorage `df-ai-gen`/`df-ai-text` 是 Sprint 19 **有意保留**(流式临时快照高频写),非 bug。仅在出现新场景失效时再评估改全局 emit — **去重**:与 AR-M5跨窗口 state 完全隔离同类aichat 审查描述更深 — source:代码审查 + Sprint 19 (06-14)
### 🔧 2026-06-18 6 域并行走查 sweep-fixworkflow wd2fnjh3s·6 agent·主代核查 cargo check --workspace EXIT 0 + vue-tsc EXIT 0
> 开放式扫模块找新问题 + 修确定性低风险 bug + 记歧义为新 todo。**6 agent 修 12 处 / 新 todo 27 项**(去重后 24 项入此节,3 项重复跳过:conditions 未实现=T-260614-11 / AiSession 单例跨会话 clear=F-260616-09 B 路线 / dead_code 6 处基线 warning 合 1 条)。**主代独立核验**(不信 agent 自报 checkOk):cargo check --workspace EXIT 0(仅 6 pre-existing dead_code warning 基线)+ vue-tsc EXIT 0 + grep 抽查 scan.rs 索引对齐/audit.rs 吞错分流/commands.rs retain 源码形态全正确。**审查防污染拦截 1 瞬态误报**:fe-other agent vue-tsc 抓 ToolCard.vue:653 cmdOutput TS6133 never read,实为 fe-aichat 改 ToolCard 中间态(computed 已加模板未引用)被并发 vue-tsc 抓到,最终态模板 line 122/128 已引用,全量 vue-tsc 复核 EXIT 0 确认非 bug(memory [[review-batching-worktree-transient]] 教训)。
**已修 12 处(主代核查全通过)**:
- df-ai:① context.rs `estimate_message` 累加 parts Text/Image.base64 token(F-05 回归,大图绕过预算致 provider 400/500,加单测 estimate_message_counts_parts_tokens)② anthropic_compat.rs Image 转换 clone→move(DRY/效率)
- df-storage/project/ideas:③ scan.rs `is_pure_badge_line` to_lowercase→to_ascii_lowercase(非ASCII索引错位/panic)④ scan.rs `is_monorepo` workspaces null 误判(.is_some_and(!is_null))⑤ adversarial.rs MockProvider 补 reasoning_content(测试 E0063 编译失败)
- rust-ipc:⑥ audit.rs `audit_finalize` 拆 unwrap_or_default 吞错(Err/None 分流+error 日志,对齐 B-260617-17)⑦ commands.rs `ai_conversation_delete` 补非活跃对话 pending_approvals retain(对齐 switch,修死审批残留)⑧ audit.rs 抽 PENDING_APPROVAL_PLACEHOLDER 常量(防去重漂移误命中)
- fe-aichat:⑨ ToolCard.vue isToolFailure/commandOutput 下沉 computed(复用 parsed,DRY/性能)⑩ AiChat.vue 清 4 处虚拟滚动残留死注释(B-260618-01 已移除)
- fe-other:⑪ Projects.vue onUnmounted 清 _toastTimer(timer-leak)⑫ Knowledge.vue refConvTitle parseContext 两次→一次(冗余 JSON.parse)
**新 todo 24 项**:
🔴 P1(2·均需确认设计意图/跨函数,非立即修)
- [x] ✅(workflow wexu1isx1·cargo check EXIT 0 + cargo test df-workflow 23 passed executor 取消不回归) SW-260618-01 [P1] — **executor Ok 路径取消节点事件/状态不一致(TOCTOU 残留)**。executor 阶段三 Ok 分支:已取消节点(set_cancelled 后 execute 返 Ok)跳过 set_completed 正确,但仍 emit NodeCompleted(:139-144 不在 is_cancelled 判断内)+ 仍 outputs.insert(:145)。对比 Err 分支 :151-157 已对称处理(取消则 emit NodeCancelled 替代 NodeFailed)。结果:前端收到状态机=Cancelled 但事件=NodeCompleted 的矛盾信号。**修法**:把 emit Node Completed 与 outputs.insert 移入 if !is_cancelled 分支。但 outputs.insert 移入涉及下游 inputs 行为变更(当前测试锁定 run Ok=取消不中止工作流),需先确认设计意图:Ok 路径取消到底该不该中止后续层?属行为变更,先确认再改。— crates/df-workflow/src/executor.rs(:131-145)
- [x] ✅(workflow wexu1isx1 + 主代修借用E0502 helper改接&mut session disjoint borrow·cargo check EXIT 0) SW-260618-02 [P1] — **审批占位 tool_result 在 stop/clear/create/delete 清 pending_approvals 时未替换终态文本**。占位「需要用户审批,等待确认」残留在 messages,下次发送会把占位文本喂给 LLM。涉及 5 处 .clear()(commands.rs:415/762/863/1317/1334),需统一 replace_tool_result_content 终态化。属行为变更 + 跨多函数,需设计统一收口(可走 session_state 视图 mod.rs:393 已就位待替换)。— src-tauri/src/commands/ai/commands.rs(:861-869 ai_chat_stop 审批清理分支,同型 5 处)
🟡 P2(8)
- [x] ✅(评估暂缓·url 模式当前不可达(前端 Phase2b 只产 base64)+ anthropic_compat 已 warn;加 commands 层校验需追 parts 全链路(IPC→ContextManager→provider)定校验点有歧义 + 误拒合法 url 风险,待 url→base64 预拉落地后 url 模式变可达再加) SW-260618-03 [P2] — **Anthropic 图片 url 模式静默 400**。url 模式只 warn + 发空 data(必触发 Anthropic 400)。注释声明 url 模式当前不可达(前端 Phase2b 只产 base64),但 url→base64 预拉未落地前前端误传 url 会静默 400。建议 commands 层加 parts 合法性校验:url 片无 base64 直接拒绝。— crates/df-ai/src/anthropic_compat.rs(:356-378)
- [x] ✅(workflow wexu1isx1·KnowledgeEventsRepo::list_recent ORDER BY timestamp 兜底·cargo check EXIT 0 + df-storage test passed) SW-260618-04 [P2] — **knowledge_events 通用 query 硬编码 ORDER BY created_at 崩溃**。impl_repo! 宏 query() 硬编码 ORDER BY created_at DESC,但 knowledge_events 表无 created_at(只有 timestamp)。当前无调用方(均走 list_by_knowledge),误调 state.knowledge_events.query() 触发 SQLite "no such column"。修法:① 加专用 query 带 ORDER BY timestamp ② 重构宏支持 per-table 排序列(架构级)。属行为/架构改不直修。— crates/df-storage/src/crud.rs(:139-163 宏 query + :1592-1611 KnowledgeEventsRepo)
- [x] ✅(评估暂缓·当前 PENDING_APPROVAL_PLACEHOLDER 集中常量+SW-24/CR-01 防漂移有效,内容匹配可靠;改进是未来防御(占位含动态片段)+ 跨 df-ai crate 加 status 字段设计成本高) SW-260618-05 [P2] — **pending 占位靠内容字符串匹配(应改 status/kind 字段)**。find_cached_high_risk_result(audit.rs:464)用 PENDING_APPROVAL_PLACEHOLDER 字符串内容排除 pending 占位,依赖内容相等,若未来占位文本含动态片段(如审批 id)失效。建议 ChatMessage 加 status 字段或 ToolResultKind 枚举(pending/resolved)区分。跨 df-ai crate 需设计。— src-tauri/src/commands/ai/audit.rs(:458-464)
- [x] ✅(主代修·SW-06·vue-tsc EXIT 0) SW-260618-06 [P2] — **ToolCard 双 watch(props.tc.status) 合并**。合并 watch 放原 watch2 位置(approving :552/approvingTimer :553/cmdOutputExpanded :534 三 ref 都已定义避 TDZ——原 watch1 在 :535 immediate 访问 approving/approvingTimer 会 TDZ),加 immediate(原 watch1 语义),callback 内 cmdOutputExpanded(completed 初始化)+ approving 复位(pending_approval 判断)合并。**immediate 边界**:s=初始 status 时 cmdOutputExpanded 仅 completed 设(初始非 completed 无副作用)+ approving 初始 false/approvingTimer 初始 null(if null 跳过 clearTimeout)安全。删原 watch1+watch2,合并 watch 放原 watch2 位置。— src/components/ToolCard.vue
- [x] ✅(主代修·SW-07·vue-tsc EXIT 0) SW-260618-07 [P2] — **AiChat 双 watch(currentText) 合并**。删原 :2040 watch(currentText→onContentChange),并入 :2047 watch(currentText→scheduleStreamParse) 单 callback:先 onContentChange(滚动跟随)后 scheduleStreamParse(rAF 分块),**保持原注册顺序**(:2040 先 :2047 后 → callback 内同序)。:2039 watch(messages.length→onContentChange)不同源保留。无 immediate(两原 watch 均无),无 TDZ(onContentChange :2032/scheduleStreamParse :912 均在前)。— src/components/AiChat.vue
- [x] ✅(主代修·SW-08·vue-tsc EXIT 0) SW-260618-08 [P2] — **AuditLog.vue 全硬编码中文未 i18n**。新建 auditLog 命名空间(en/zh-CN:title/refresh/desc/loading/empty + col×7 + pager×4 + risk×3 + status×6 + decided×2)+ AuditLog.vue 引入 useI18n,template 15 处文本 t() 化 + riskLabel/statusLabel/decidedLabel 改 t(`auditLog.xxx.${val}`)(class 映射 statusClass/riskClass/decidedClass 保留不国际化)。模式对齐 Dashboard/Knowledge/Ideas。— src/i18n/{en,zh-CN}/auditLog.ts(新建) + src/views/AuditLog.vue
- [x] ✅(主代修·SW-09·cargo check df-nodes EXIT 0 无 warning + cargo test 82 passed 0 failed) SW-260618-09 [P2] — **AiNode/AiSelfReviewNode provider 解析+构建 DRY 重复**。抽 2 helper:resolve_and_parse(合并 resolve_provider+parse_params)+ provider_from_params(封装 build_provider 5 行)。AiNode :254-262(9 行)→ 2 行;SelfReview parse(:465-466)+ build(:493-498)→ resolve_and_parse + provider_from_params(中间 task 读保留,p 生命周期跨 task 读正确)。messages/temperature 差异不抽(AiNode 用 p.system_prompt/p.temperature;SelfReview 强约束 Some(0.0)+REVIEW_SYSTEM_PROMPT,各节点自有)。原 resolve_provider/parse_params fn 保留(test 直接调用),helper 上层封装。— crates/df-nodes/src/ai_node.rs(2 helper 新增 + 2 节点 execute 改)
- [x] ✅(主代修·SW-10·cargo check df-ai EXIT 0 无 warning + cargo test df-ai 112 passed 0 failed) SW-260618-10 [P2] — **reqwest Client 构建重复(OpenAI/Anthropic Provider::new)**。抽 `lib.rs::build_provider_client()` pub(crate) fn(connect_timeout 30s + 回退 Client::new + tracing::warn 集中),两 Provider::new 调 `crate::build_provider_client()` 消除 7 行逐字重复。**副作用 grep 核验**:Client import 两文件仍用于 struct 字段(self.client:Client);warn 仍多处用(openai:503/anthropic:183/330/364/497/521/535);Duration import 仍用于 :484/:481 timeout。纯 DRY 抽函数零行为变化。— crates/df-ai/src/lib.rs(+14) + openai_compat.rs(-8+2) + anthropic_compat.rs(-6+2)
⚪ P3(14)
- [x] ✅(评估暂缓·当前构造器保证非空不可达,外部 struct 字面量非预期路径,加 normalize 属未来防御 ROI 低) SW-260618-11 [P3] — **OpenAI 图片构造防御缺失**。Image{base64:Some, media_type:None} 或 url+base64 双空退化为空 image_url.url(400)。当前构造器保证非空故不可达,外部 struct 字面量构造缺防御。可加 ContentPart::normalize。— crates/df-ai/src/openai_compat.rs(:345-358)
- [x] ✅(评估确认·设计合理:archived 状态变动需动 updated_at 排序,pinned 排序走 set_pinned 专用方法不碰 updated_at 保值语义;非 bug) SW-260618-12 [P3] — **ai_conversations 白名单 pinned 与 archived 不对称**。白名单含 archived 不含 pinned,启用 pinned 会动 updated_at 破坏置顶时间语义。建议先确认产品意图(或把 archived 也移除强制走专用方法)。— crates/df-storage/src/crud.rs(:372-375)
- [x] ✅(评估暂缓·当前文档约定+测试守护够,内部 normalize 需改 crud 宏影响面大 ROI 低) SW-260618-13 [P3] — **find_path_conflict 跨层规范化靠文档约定**。期望调用方先 normalize_path,第三个调用方漏掉则静默误判。可选内部 normalize 或文档+测试守护。— crates/df-storage/src/crud.rs(:681-695)
- [x] ✅(评估不做·注释已承认权衡:误读为 0=未达阈值无副作用,迁 app data dir ROI 低) SW-260618-14 [P3] — **keyring failcount sidecar 用 current_dir 跨启动不稳**。注释已承认权衡(误读为0即按未达阈值无副作用)。若要稳定需迁 app data dir。— crates/df-storage/src/secret.rs(:34-38)
- [x] ✅(主代修·SW-16·cargo check --workspace EXIT 0 + cargo test -p devflow 92 passed 0 failed) SW-260618-16 [P3] — **F-05 去重审计 status 固定 completed 不透传 rejected/failed**。find_cached_high_risk_result 加 audit_repo 参数 + 改 async + 返回 Option<(String,String)>(content,status);命中旧 tool_result 后查 AiToolExecutionRepo::find_by_tool_call_id(old_id)(**已存在** crud.rs:1256,复用零新增方法)拿缓存来源真实 status(completed/rejected/failed),透传 audit_tool_call 替代固定 "completed";审计记录缺失/查询失败 fallback completed(不阻塞去重降级原行为)。调用点 :625 拆 (cached,status)+.await / :639 &status。审计语义与结果内容一致,防"rejected/failed 结果记 completed"误导安全追溯。— src-tauri/src/commands/ai/audit.rs(find_cached 签名+返回+status 查询 / process_tool_calls :625/:639)
- [x] ✅(评估不做·破坏 UX-260616-03 记忆态设计) SW-260618-17 [P3] — **ToolCardList 4 Set 跨会话不重置累积**。**评估结论:不做**。userExpandedCards/userCollapsedCards 是 UX-260616-03「用户主动展开/折叠记忆态」设计(切回 A 恢复展开记忆=feature 非 bug);expandedCards/expandedTools 已被 collapseInactive 按 activeIds 过滤清。切会话清 Set 会破坏记忆态。「内存慢增长」是记忆保留代价(tc.id 全局唯一 UUID 短字符串,描述自承认「无错配」),可忽略。— src/components/ToolCardList.vue
- [x] ✅(主代修·SW-19·vue-tsc EXIT 0) SW-260618-19 [P3] — **combineAndTruncateLines 与 cmdOutput 合并逻辑冗余**。抽 `combineOutputs(stdout, stderr)` 共享函数,combineAndTruncateLines(:472)与 cmdOutput computed(:652)都复用。逻辑收敛,行为不变。— src/components/ToolCard.vue(:463 函数 + :472/:652 调用)
- [x] ✅(主代修·SW-20·vue-tsc EXIT 0) SW-260618-20 [P3] — **Ideas 本地 parseTags 与 store 版 DRY 重复**。删本地 parseTags(IdeaRecord),import stores/knowledge.ts parseTags(tags:string|null),3 调用点改传 .tags(currentIdea.tags / i.tags)。复用 store 版,签名收敛。— src/views/Ideas.vue(:214 import + :148/:149/:278 调用)
- [x] ✅(评估暂缓·chip/tag 数量小,O(n) filter + 模板重复求值影响可忽略,优化 ROI 低) SW-260618-22 [P3] — **Knowledge getCategoryCount+parseTags 模板重复求值**。getCategoryCount v-for 每项 O(n) filter,parseTags 同模板调 2 次。chip/tag 数量小影响可忽略,优化项。— src/views/Knowledge.vue(:314-317 + 132-133)
#### 架构观察INFO · 2 项·不进修复队列)
- [ ] **UX-260617-28 [INFO]****双监听器同通道 fragility** — useAiEvents + useAiContext 各自 listen('ai-chat-event'),人工协调防双重处理(AiCompressing flag)非架构保证。未来新增事件处理可能触发双重 bug。长期考虑单一分发器模式。— src/composables/ai/useAiEvents.ts:269 + src/composables/ai/useAiContext.ts:85-105
### 🔧 2026-06-18 模型能力维度cost_tier/intelligence数据无客观依据·定位完成待实施
> session-role-diagnose-only·铁证定位不臆想。用户追问"高/中/低价、标准/增强怎么定的",定位根因后用户决策:**API 无判别依据的维度不要瞎填/写死,去掉**。
**根因(铁证:`model_fetch.rs:247-259` ModelsList struct**provider `/v1/models` API 仅返回 `{data:[{id}]}`**不含 cost_tier/intelligence/modalities/capabilities/context_window 任何能力判别字段**。这 5 维度 100% 由 `model_probe::probe()` 填:预设表(`presets/models.json` 写死 16 条)+ 启发式(模型名关键词猜)。零客观依据、必然过时、对新模型全错。
**症状实证**GLM-5.2(旗舰)被启发式猜成 standard/medium名不沾 4o/lite 词素走默认兜底GLM-4.5 被模糊匹配命中 glm-4 → plus/medium低估
**路由副作用**`router.rs` 用 cost/intel 做硬过滤步骤4 `intelligence >= min_intelligence`、步骤5 `cost_tier <= max_cost`+ 排序步骤7 `Reverse(cost_tier)`)。`title.rs`max_cost=Low/ `compress.rs`max_cost=Medium按错误档位过滤可能选不到合适模型。
**用户决策2026-06-18**API 无判别依据的 cost_tier/intelligence → 去掉,不写死不瞎填。(曾尝试补预设表硬编码 GLM 新模型,用户否决:模型数据不该写死进代码会过时。已回滚清理,`git diff` 证两文件零残留。)
- [x] ✅(workflow wexu1isx1·router删min_intelligence/max_cost字段+步骤4/5 filter+步骤7 Reverse(cost_tier)+9调用点+test改写·cargo check EXIT 0 + cargo test df-ai 109 passed) **B-260618-03 [P1] 后端路由解耦 cost_tier/intelligence**`router.rs` 去步骤4min_intelligence 过滤)/步骤5max_cost 过滤)/步骤7`Reverse(cost_tier)` 排序)→ 排序纯 weight。TaskRequirements 的 min_intelligence/max_cost 字段 + 8 调用点(`title.rs` max_cost=Low / `compress.rs` max_cost=Medium / `agentic.rs`·`project.rs`·`ai_node.rs`·`knowledge_inject.rs`·`adversarial.rs`·df-ideas 的 min_intelligence同步清理。因 API 无判别依据这俩维度数据不可信,不应参与硬路由。**属主链路由行为变更**,需 cargo test router 全验证 + 核对 title/compress 选模型不退化。— `crates/df-ai/src/router.rs` + 调用点
- [x] ✅(workflow wexu1isx1·heuristic_infer cost/intel 中性默认 Medium/Standard + presets/models.json 17条删两字段·cargo check EXIT 0 + cargo test df-ai 109 passed) **B-260618-04 [P2] model_probe 去瞎填 + 预设表机制存废**`heuristic_infer` 的 cost/intel 猜测(`model_probe.rs:158-163`+ `presets/models.json` 两字段改中性默认medium/standard或标记"无依据"。预设表16 条写死随厂商发新模型必过时存废待定。注modalities/capabilities/context_window 同 API 不返回也瞎填但功能性维度vision/embedding/code_gen有路由意义选错致 vision 任务选纯文本模型),性质不同,保留但需 UI 暴露用户可改(另立)。— `crates/df-ai/src/model_probe.rs` + `presets/models.json`
- [x] ✅(主代修·UX-04·vue-tsc EXIT 0 + grep 核验无 tagCost/tagIntel 残留) **UX-260618-04 [P2] 前端删 cost/intel 标签** — 自主裁决全删(用户已全局决策去掉 cost/intel·B-260618-03 + 独立于后端路由 + grep 确认仅 2 组件引用 + 回退易)。ProviderPanel.vue 删 tag-cost/tag-intel 两 span;AiChat.vue modelOptionLabel 删 intel/cost 拼接只返 label/model_id;i18n en/zh settings.ts 删 tagCost/tagIntel 命名空间 + 注释 4 维度→模态+能力。— `src/components/{settings/ProviderPanel,AiChat}.vue` + `src/i18n/{en,zh-CN}/settings.ts`
- [x] ✅(workflow wexu1isx1 + 主代修前端 types.ts 删 'free'·CostTier 枚举删 Free + test 同步·cargo check EXIT 0 + cargo test df-ai-core 24 passed + vue-tsc EXIT 0) **B-260618-05 [P3] CostTier::Free 死档 + 枚举清理** — 预设表 0 条 free + 启发式从不赋 Free只写 low/medium/high。Free 变体形同虚设router 测试 `same_weight_picks_cheaper_cost_tier` 用到但实际数据不产生。B-260618-03 解耦后评估删 Free 变体 / 收敛 CostTier 枚举。— `crates/df-ai-core/src/model.rs`
### 🔧 2026-06-18 AI Chat markdown 表格布局破坏(确定性 CSS bug·未实施
> 用户截图反馈"展示样式有问题"分析截图AI 回复测试报告 markdown 表格 + 连续失败重试卡片。session-role-diagnose-only定位根因 + 修法。
**核心问题**markdown 表格(测试报告 #/组件/触发条件/代码位置 四列)**行列对齐丢失、单元格堆叠换行**。
**根因(确定性 CSS bug**AiChat.vue:3366-3371
```css
.ai-msg-bubble.ai-md :deep(table) {
display: block; /* ← 破坏表格布局上下文 */
max-width: 100%; width: max-content; overflow-x: auto;
}
```
`display:block` 让 table 失去 `display:table` 的表格布局上下文 → 内部 thead/tbody/tr/tdtable-row-group/row/cell脱离 table 上下文 → **行列对齐丢失,单元格塌成块级堆叠**。视觉分析的"表格列间距/行高/列标题与行叠放/代码位置文字溢出换行"全是此根因表象非独立间距问题。注释意图UX-260617-21是"宽表格横向滚动不撑破气泡",但 display:block 实现破坏了表格结构。
**修法(确定性)**:移除 `display:block`,改**父容器滚动**保持 table 布局:
```css
.ai-msg-bubble.ai-md { overflow-x: auto; } /* 气泡横向滚动 */
/* :deep(table) 不设 display保持默认 table 布局 */
```
或 markdown 渲染给 table 包 `<div style="overflow-x:auto">`(更可控,不依赖气泡 overflow
**附带观察**连续失败重试卡片1/4 2/4 3/4堆叠密集——但这是**连续失败重试的功能现象**BUG-260617-01 `||true` 已修 924158c截图若仍复现需另查重试逻辑非纯样式。
- [x] ✅(workflow w5siwnipj 核验两处 display:block(AiChat.vue:3366 局部 + ai-md.css:59 全局根因)+主代实施·移除两处 display:block/overflow-x:auto·table 恢复默认 display:table 列对齐·气泡 .ai-msg-bubble--ai.ai-md 加 overflow-x:auto 承载宽表格滚动·vue-tsc EXIT 0) **🚨 B-260618-06 [P1]** — **markdown 表格 display:block 破坏布局**。两处根因:全局 ai-md.css:59 + AiChat.vue:3366 局部覆盖。display:block 让 table 失去表格布局上下文 tr/td 塌堆叠丢列对齐(截图测试报告表格错乱根因)。修法:两处移除 display:blockoverflow 在 display:table 下浏览器忽略一并移除table 恢复默认 display:table + max-width:100%,宽表格横向滚动交气泡容器 .ai-msg-bubble--ai.ai-md overflow-x:auto 承载CSS spec table-display 下 overflow 须由 block 父容器)。— src/styles/ai-md.css(:59) + src/components/AiChat.vue(:3366 UX-260617-21 方案修正)
### 🔧 2026-06-18 aichat 工具结果渲染核对agent 全量核对·session-role-diagnose-only
> agent 核对 ToolCard.vue 各工具结果渲染 vs 后端 tool_registry.rs 返回结构,找"结果字段被丢弃/显示不全/裸 JSON"。**search_files 已修**body 文件列表 + bar pattern本轮。余 11 条待修。
**P0**
- [x] ✅(workflow w2drz3ppo 核验+主代实施 run_workflow 显 execution_id 删误导 workflowHint·vue-tsc EXIT 0) UX-260618-05 [P0] — **run_workflow 结果丢 execution_id**。header 硬显 workflowHint「请到工作流页面运行」(误导,工作流已触发)+ body 走裸 JSON。后端返 `{task_id, target_status, execution_id, status, note}`,核心 execution_id 没渲染,用户无法对应执行实例。修:加专门 case 显 execution_id / task_id→target_status删误导 workflowHint。— `ToolCard.vue`(:809 toolResultSummary + formatToolResult default)
**P1**
- [x] ✅(workflow w2drz3ppo+主代 patch_file 模板分支渲染 diff 红绿行+resultDiffLines computed·vue-tsc EXIT 0) UX-260618-06 [P1] — **patch_file 丢 diff**。后端 `generate_diff` 产完整 unified diff(tool_registry.rs:1148)formatToolResult 只显「已修改 path(±N 字节)」diff 整个丢弃,用户无法验证 AI 改了什么。修:加专门 body 分支复用 write_file 审批卡 diffLines 红绿行渲染。— `ToolCard.vue`(:424)
- [x] ✅(workflow w2drz3ppo+主代 delete_file formatToolResult+toolResultSummary 加 backup_path·vue-tsc EXIT 0) UX-260618-07 [P1] — **delete_file 软删丢 backup_path**。只显「已软删除(已备份)」backup_path(回收站文件名)不显,用户不知从哪恢复。修:补「已备份至 {backup_path}」。— `ToolCard.vue`(:418)
- [x] ✅(workflow w2drz3ppo+主代 read_file 模板 has_more 显「shown/total 行,已截断」·vue-tsc EXIT 0) UX-260618-08 [P1] — **read_file 丢截断提示**。>500 行默认截断返前 500 行bar 只显 lines·sizehas_more/returned_lines 丢弃用户不知是截断。修has_more=true 时显「显示 returned_lines/lines 行,已截断」。— `ToolCard.vue`(:72-82)
- [x] ✅(workflow w2drz3ppo+主代 advance_task 加 review_rounds 退回累加(>0 显)·vue-tsc EXIT 0) UX-260618-09 [P1] — **advance_task body 裸 JSON**。后端返完整 TaskRecord(~15 字段含 review_rounds)formatToolResult 只显「任务 id 推进至 status」body 走裸 JSON。修body 显 status + review_rounds(退回累加值)。ToolResult type 缺 review_rounds 字段需补。— `ToolCard.vue`(:414)
- [x] ✅(workflow w2drz3ppo+主代 list_trash toolResultSummary case 返计数,被 UX-12 list_* 重写吸收·vue-tsc EXIT 0) UX-260618-10 [P1] — **list_trash 缺 header 摘要**。toolResultSummary 覆盖 list_tasks/projects/ideas 漏 list_trash折叠态无计数。返回结构与 list_projects 同构 `{items,total,has_more}`。修:补 case 返计数。— `ToolCard.vue`(:769)
**P2**
- [x] ✅(workflow w2drz3ppo+主代 list_directory 模板 truncated 显「已截断,仅前 1000 项」·vue-tsc EXIT 0) UX-260618-11 [P2] — **list_directory 丢 truncated**。达 1000 条上限截断返 truncated:true前端无提示。修truncated=true 显「已截断,仅前 1000 项」。— `ToolCard.vue`(:85-104)
- [x] ✅(workflow w2drz3ppo+主代 list_* 重写修 Array.isArray 对象误判+结构化列表+file_info modified+write_file old_size/encoding+rename_file cross_volume/bytes_moved·vue-tsc EXIT 0) UX-260618-12 [P2] — **list_* body 全裸 JSON + 各工具零散字段丢失**。① list_projects/tasks/ideas/trash body 走裸 JSON(items 完整 Record),应加专门 body 分支列表渲染 name+关键字段;② file_info 丢 modified③ write_file 丢 old_size/encoding④ rename_file 丢 cross_volume/bytes_moved。ToolResult type 缺 execution_id/note/modified/old_size/cross_volume/bytes_moved/truncated 等字段需补。— `ToolCard.vue`(formatToolResult + 各 body 分支 + ToolResult type :181)
### 🔧 2026-06-18 列表摘要 Markdown 语法字符泄露三类列表·session-role-diagnose-only·未实施
> 用户报AI Chat 对话列表、知识库列表、待审核项目列表的摘要信息都显示 Markdown 语法字符(`**`/`#`/`` ` ``/`-`/`>`)。走查定位链路 + 记 todo不改代码。
**核心问题**:列表/卡片摘要字段用 `{{ }}` 纯文本插值,但数据源是 AI 生成内容(含 Markdown 语法),既未渲染富文本也未剥离语法字符 → 裸字符外露。详情页都正确 `v-html` 渲染,列表层缺处理。
| # | 位置 | 代码 | 数据源AI 生成·含 MD | 详情页对照 |
|---|------|------|------------------------|-----------|
| 1 | 知识库列表 | `Knowledge.vue:71` `{{ item.content }}` | AI 从对话提取的知识内容 | `:120``v-html="renderedContent"` ✅ |
| 2 | 项目卡片 | `Projects.vue:148` `{{ project.description }}` | `aiScan` 填的 LLM 描述(`:256` | 无详情渲染 |
| 3 | 灵感列表pending 待评审) | `Ideas.vue:47` `{{ idea.description.slice(0,60) }}` | 灵感描述 | `:63``v-html="renderedDesc"` ✅ |
**AI Chat 对话列表(存疑待核)**:侧栏对话项只渲染 `{{ conv.title }}``AiChat.vue:49/122/171`),无摘要字段。若用户所说"摘要"即标题,则需核后端自动生成标题(`title.rs` `generate_title_via_llm`)是否产出 Markdown 语法——前端 `{{ }}` 会原样显示。倾向标题本应纯文本,若 LLM 偶发带 `**` 等需后端 prompt 收口或前端 strip。
**根因**:列表摘要本应纯文本预览,三处均走 `{{ }}` 插值AI 生成源文本含 Markdown 未剥离。
**修法方向(待实施·二选一)**
- **A 推荐**:加 `stripMd(text)` util剥离 `**/__/_/#/`/`` ` ``/-/>/\[]()/!\[]()` 等语法留纯文本),列表摘要统一过滤。列表空间小 + `-webkit-line-clamp` 截断Knowledge 卡片)+ 富文本样式不可控,剥离比渲染更合适。
- B列表也 `v-html` 统一渲染——不推荐(卡片截断 + 富文本样式溢出/`<p>` 撑高)。
- [x] ✅(主代串行·规避 AiChat God 文件·新建 src/utils/markdown.ts stripMd 剥离 MD 语法(粗体/斜体/标题/代码/列表/引用/链接/图片)+ Knowledge.vue:71/Projects.vue:148/Ideas.vue:47 三列表摘要接入·修自引入注释含 */ 致 JSDoc 提前闭合 TS1011/1127·改安全措辞·vue-tsc EXIT 0) **UX-260618-13** [P2] — **列表摘要 Markdown 语法字符泄露**。三处列表摘要 stripMd 过滤。**AiChat 标题 strip 子项**:规避 God 高频文件·留待并发期后(待核 title.rs 是否产 MD)。— `src/views/{Knowledge,Projects,Ideas}.vue` + `src/utils/markdown.ts::stripMd`
---
### 🔧 2026-06-18 用户实测·aichat bug 反馈(review 会话 2 agent 并行审查·独立 grep/read 核验·session-role-diagnose-only·待实施)
> 用户实测报 2 bug。review 会话派 2 agent 并行核验源码,根因 + 修法定位完成,待主代实施。
- [x] ✅(workflow wwchro468 general-purpose agent 实施 6 文件 + 主代兜底核验·前端 ToolCard.vue advance_task 渲染取 r.title||r.id(:474/:891)+TASK_ID_TOOL_ARG 映射(:799)+displayArgValue task_id→title(:834)+i18n zh/en aiTool.ts;后端 audit.rs resolve_task_label(查 tasks 表 :151 三臂对齐 resolve_project_label)+build_approval_reason advance_task 分支(:173)+tool_registry.rs 删 tool_display_hint advance_task 项(:1575 让 :1549 模板生效)·主代 cargo check -p devflow EXIT 0 + vue-tsc EXIT 0 + grep 印证) **UX-260618-14 [P1]****advance_task 审批卡/结果渲染缺任务名 + 显 UUID/空白**。①审批卡 `ToolCard.vue:800` `PROJECT_ID_TOOL_ARG` 未登记 advance_task → id 裸显 UUID;②审批 reason `audit.rs:163` tool_display_hint 短路致 `tool_registry.rs:1546` 模板死代码 + `:172` 任务 id 误查 projects 表;③结果 `ToolCard.vue:477` 模板取 r.id(UUID)非 r.title(TaskRecord 有 title `models.rs:57`),"空白"=UUID 不可读非字段缺失。**修法 B(最快)**:ToolCard:477 取 r.title fallback r.id + i18n 改 `任务「{title}」已推进至 {status}`;**A1** 登记 TASK_ID_TOOL_ARG(advance_task/run_workflow)id→title 回显;**C** 删 `tool_registry.rs:1572` tool_display_hint 让 :1546 模板生效 + audit.rs 加 resolve_task_label。附带:`:1546/:1547` 模板均死代码 + audit.rs:172 任务 id 误查隐患。— `src/components/ToolCard.vue`(:474-478,800-825) + `src-tauri/src/commands/ai/{tool_registry.rs:1546/1572,audit.rs:163/172}` + `src/i18n/{zh-CN,en}/aiTool.ts`
- [x] ✅(agent ux15-batch1 方案A实施 3 文件 + 主代兜底核验·stream_recv 5处重试路径移除AiError emit + agentic Fatal/耗尽单emit AiError + useAiEvents AiStreamRetry首次创建+后续更新·主代 cargo check EXIT 0 + cargo test df-ai 119 passed + vue-tsc EXIT 0 + grep核验5处emit判定 + 修正注释残留 :251/:299) **UX-260618-15 [P1] 第一批(方案A根治N+1)** ✅ — **流式失败重试(1/4 2/4 3/4)每轮独立气泡**。N+1 降为「1 重试气泡 + 1 最终错误气泡」。**第二批(方案C 聚合气泡显示错误原因)可选增强待定**。后端每轮重试 emit AiError(`stream_recv.rs:206/265/306/364/407` 5 处 InitFailed 返回点·**注(2026-06-20 核验):UX-15 已实施,5 处均改为「不 emit AiError,返回 InitFailed{retryable,error} 交调用方」,注释明示「UX-260618-15: 不再 emit AiError」,见 stream_recv.rs:120/203/262/303),前端 `useAiEvents.ts:326` AiError case 每次 push 新气泡无去重;`AiStreamRetry`(:177)本意更新末条合并但被新 AiError 破坏 → N+1 独立气泡。`agentic.rs:262` 注释误以为 stream_llm 只 emit 一次(实多次)。**用户诉求升级**:不只是修 bug,要**聚合显示设计**(重试 1→4 轮过程聚合单气泡 + 最终结果,勿一排错误吓人)。详见 review 会话重试聚合设计方案(3 角度论证:可行性/前后端一致性/架构整合)。**修法 A(推荐)**:`stream_recv.rs` 5 处 InitFailed 移除 AiError emit(改纯返 retryable,emit 权交调用方)+ `agentic.rs` 重试 loop 只 emit AiStreamRetry、耗尽/Fatal 才 emit 最终 AiError。影响仅流前失败重试路径(Fatal/MidStream 保文/正常错误/压缩失败不受影响)。— `src-tauri/src/commands/ai/{stream_recv.rs:200/258/298/358/398,agentic.rs:207-300}` + `src/composables/ai/useAiEvents.ts:177/326`
- [x] ✅(主代串行·doc-discovery 源码核验 `time.ts:44``(i18n as any).global.locale.value === 'en'`·CR-260618-24 修复闭环 + CR-260618-25 A-time 复审 ✅ 双印证·漏销账补登) **UX-260618-16 [P1🔴]****`time.ts:44` formatDate 漏 `.value` 致 en locale i18n 失效**(CR-260618-24 批2 审查发现·Agent C 铁证)。`legacy:false` composition 模式 `i18n.global.locale` 是 ref,`(i18n as any).global.locale === 'en'` 比对象===字符串**恒 false** → en locale 下 formatDate 永走 zh-CN 分支,i18n 化失效(对齐 CR-260615-08 未完成回归)。铁证:`App.vue:227`/`GeneralPanel.vue:259` 均用 `i18n.global.locale.value`,唯 time.ts:44 漏。**修法**:补 `.value``(i18n as any).global.locale.value === 'en'`(locale 取值 'en' 非 'en-US',App.vue:227 印证)。— `src/utils/time.ts:44`
- [x] ✅(2026-06-20 核验·handleApprovalMulti 已随审批逻辑迁移到 `src/components/project/ApprovalDialog.vue:91-102`·submitting try/finally 已正确对齐 handleApproval + 模板 `:25 :disabled="submitting || multiDecisions.length === 0"` 已绑·todo 行号 431-437/196-202 过时为迁移前 ProjectDetail.vue 坐标) **UX-260618-17 [P1🟡]****ProjectDetail.handleApprovalMulti 漏 submitting 复位(防双击破口)**(CR-260618-25 前端审查发现·Agent B 对抗核验)。原状:`ProjectDetail.vue` handleApprovalMulti 无 submitting set true/finally,模板只绑 `:disabled="multiDecisions.length === 0"` → 多选审批 IPC 进行中按钮不禁用,可重复触发。**已修**:迁移 ApprovalDialog.vue 时补 submitting 自治 + try/finally + 模板绑 submitting。— `src/components/project/ApprovalDialog.vue`(:91-102 + :25)
### 🔧 2026-06-19 文件拆分升级3 代理并行分析·建任务·未实施)
> 大文件统计(总 51143 行)+ 3 代理并行分析拆分方案。与已有 SMELL-P0-2(tool_registry)/SMELL-P0-3(AiChat.vue)/SMELL-P1-9(crud.rs) 合并。**通用执行原则**:可见性升级(私有 fn/struct → pub(super))/ 测试跟随被测函数 / **不改逻辑不改 await 边界不改签名** / 三段式验证(cargo check+test+clippy 分 crate,对齐 [[workflow-cargo-timeout-wrap]])/ 保守方案(热路径整块搬不拆函数体)。
**P0(超红线/最脏,本周期优先)**
- [x] ✅(2026-06-20 核验已拆分·`commands.rs` 已删除→`src-tauri/src/commands/ai/commands/` 目录 5 模块:chat.rs(56888 字节·发送审批控制 B 域)/config.rs(5496·杂项 E)/conversation.rs(38573·会话CRUD D)/mod.rs(2058·re-export only,`pub use self::{chat,conversation,provider,config}::*` 透传)/provider.rs(15262·提供商 C)·glob 链保留:commands/mod.rs:13 注释明 `pub use self::{chat,conversation,provider,config}::*` → ai/mod.rs `pub use self::commands::*` 透传 commands::ai::*) **REFACTOR-260619-01 [P0]****commands.rs(ai,1923 行)拆 5 模块**。5 组职责正交:B 发送审批控制(1140,13 IPC)/C 提供商(305)/D 会话CRUD(360)/E 杂项(63)+A helper。**先拆 C `provider_cfg.rs`(零风险试水:仅依赖 ai_providers 无 session 锁/per_conv/spawn)→ D `conversation_crud.rs`(`conversation.rs` 已存在,命名避冲突)→ E `misc_cfg.rs`→ B `chat_control.rs`(最复杂,等 F-09 B 批4 更稳)**。关键:`pub use self::commands::*;` glob 保留(mod.rs:58)否则 invoke_handler 注册断。— `src-tauri/src/commands/ai/commands.rs` + `mod.rs:58`
- [x] ✅(2026-06-22 核验·types+SSE 已抽 crates/df-ai/src/anthropic_helpers.rs(5 struct+2 const+apply_anthropic_event),anthropic_compat.rs 残留 881 行 Provider+impl(Rust 硬约束 impl 不跨文件不可再拆),lib.rs:4-5 双模块注册·对齐 _compat/_helpers 同构约定(openai/model_fetch/model_probe/context 同)·漏勾销账) **REFACTOR-260619-02** — anthropic_compat.rs 拆模块。已完成(types+SSE 抽出)。
- [x] ✅(2026-06-20 核验已拆分·`audit.rs` 已删除→`src-tauri/src/commands/ai/audit/` 目录:mod.rs(31813 字节·process_tool_calls 热路径 + 编排)/reason.rs(7796·build_approval_reason)/finalize.rs(3874·audit_tool_call+audit_finalize,mod.rs:115 `pub(crate) use finalize::{audit_finalize, audit_tool_call}`)/cache.rs(7685·find_cached_high_risk_result)/diff.rs(1686·build_write_file_diff)/restore.rs(10033·restore_pending_approvals)/idea_source.rs(13607·任务→灵感溯源)/data_change.rs(2432)/utils.rs(1486·risk_str/常量)) **REFACTOR-260619-03 [P0]****audit.rs(959 行)拆 5 模块**`audit/{mod,list,reason,finalize,dedup,process}.rs``process_tool_calls`(250+行热路径)**整块搬 process.rs 不拆函数体**(锁内 await 边界不动,CR-260618-11#5 性能注记)。11+ 私有 fn 升 pub(super):audit_tool_call/audit_finalize/find_cached_high_risk_result/build_write_file_diff/build_approval_reason 等。`PENDING_APPROVAL_PLACEHOLDER`/`risk_str` 提 mod.rs 共享。— `src-tauri/src/commands/ai/audit.rs`
**P1**
- [ ] **REFACTOR-260619-04 [P1·部分已做]****ToolCard.vue 拆分**。**2026-06-24 核验**:ToolCard 已从 1527→**428 行**,已抽 `ToolResultBody.vue` + `useToolCardHeader.ts` + `useToolCardRender.ts`(主体瘦身完成);但原计划的 `useToolFormat.ts`/`useToolApproval.ts`/`ToolCardHeader.vue`/`ToolApproval.vue` 未独立(当前 useToolCard/useToolCardHeader/useToolCardRender 三模块)。剩余:审批状态机独立(useToolApproval)+ 头部组件(ToolCardHeader)。风险:折叠态 shouldKeepOpen 共享 / 审批状态机断链(B-260616-08 回归)/ ToolCardList 批量审批联动。— `src/components/ToolCard.vue` + `ToolCardList.vue`
- [x] ✅(2026-06-20 核验已拆分·`agentic.rs` 已删除→`src-tauri/src/commands/ai/agentic/` 目录:mod.rs(83390 字节·run_agentic_loop 主 loop)/guard.rs(2894·GeneratingGuard+ContinueSnapshot 抽出对齐 A+E+F `agentic_runtime`)·stream_recv.rs(独立文件,StreamOutcome+stream_one_provider 对齐 B+C `agentic_stream`)) **REFACTOR-260619-05 [P1]****agentic.rs(1231 行)抽 agentic_runtime.rs + agentic_stream.rs**。主 loop 是**单函数 720 行不可按函数拆**(工具执行/审批是 loop 内 if 分支;process_tool_calls 在 audit.rs)。先抽 A+E+F `agentic_runtime.rs`(GeneratingGuard+try_continue_agent_loop+ContinueSnapshot,~195,最高收益最低风险)+ B+C `agentic_stream.rs`(StreamOutcome+stream_one_provider,~165)。**D run_agentic_loop 等 F-09 B 批4 落地再评估**(避免 per_conv 双线作战)。— `src-tauri/src/commands/ai/agentic.rs`
- [x] ✅(2026-06-20 核验·已拆:ai_node.rs 423 + ai_self_review_node.rs 447 + ai_helpers.rs 298,测试分块清晰) **REFACTOR-260619-06 [P1]****ai_node.rs(1107 行)拆 3 模块**`ai/{mod,params,ai_node,self_review}.rs`。params.rs(provider 解析 helper ~270)+ ai_node.rs(~130)+ self_review.rs(~300)。测试分块清晰(752/939/1069 三段),低风险。fixture provider_stub/config_with 留 params.rs `pub(super)`。— `crates/df-nodes/src/ai_node.rs`
- [x] ✅(2026-06-20 核验已拆分·`src/components/ai/` 子目录已存在子组件:ConversationSidebar.vue + ChatInput.vue + MessageList.vue + TopBar.vue + DirAuthDialog.vue + MaxRoundsCard.vue·AiChat.vue 从 4075 行缩到 750 行·状态/composable 已外移,模板拆分完成) **REFACTOR-260619-07 [P1]****(已有 SMELL-P0-3)AiChat.vue(4075)拆 ConversationSidebar/ChatHeader/MessageList/ChatInput**。方案已定,状态/composable 已外移,拆 template+局部 script。前置:先提交工作区未提交改动(本会话 shouldRenderMsg/scroll/1214 预检等)。— `src/components/AiChat.vue`
- [x] ✅(2026-06-22 核验·tool_registry.rs:442-514 已拆 register_*_tools 系列 data/file/http/project/task/workflow/idea/trash,SMELL-P0-2 批次落地·漏勾销账) **REFACTOR-260619-08** — tool_registry.rs 按功能分组注册函数拆。已完成。register_crud_tools/register_file_tools(已抽)/register_workflow_tools 等,每个 <200 行。— `src-tauri/src/commands/ai/tool_registry.rs`
**P2 暂缓(窗口未到/收益低)**
- [ ] **REFACTOR-260619-09 [P2 暂缓]****context.rs(1332)等 F-15 压缩链路稳定再拆**。生产 745+测试 587。impl 跨文件方案(同 crate 多 impl 块,零字段可见性改动)。先 sanitize.rs(最大连续块 ~184)。当前 context.rs 被 F-15/压缩频繁改动,拆分窗口未到。— `crates/df-ai/src/context.rs`
- [x] ✅(2026-06-22·scan.rs(实际741行)拆 scan/{mod,stack,discover,readme,sample}.rs,共享 SAMPLE_IGNORED_DIRS/truncate_chars 提 mod.rs pub(super),has_file_with_ext/read_readme_raw 跨模块降 pub(super),测试 29→29 不降,cargo check --workspace EXIT 0 外部消费方零改) **REFACTOR-260619-10** — scan.rs 拆 4 模块。已完成。
- [x] ✅(2026-06-20 核验·`crud.rs` 已删→`crates/df-storage/src/crud/` 5 文件 mod/conversation_repo/idea_repo/message_repo/project_repo·与 SMELL-P1-9 同件) **(已有 SMELL-P1-9)crud.rs(2212)按表拆** project_repo/task_repo/conversation_repo/idea_repo。— `crates/df-storage/src/crud.rs`
**执行顺序建议**:01-C provider_cfg(零风险试水) → 02 anthropropic → 03 audit → 04 ToolCard useToolFormat → 05 agentic_runtime → 06 ai_node → 07 AiChat.vue → 08 tool_registry。每步 cargo check+test+clippy 分 crate + 手测。
---
### 🔧 2026-06-19 命令行黑窗修复 + GLM 1214 数据调查DB 直查定位)
> 用户报两问题:①执行命令行弹黑窗闪烁 ②GLM 1214 messages 非法(多轮)。Build 版无 tracing subscriber 看不到日志,改 DB 直查(`C:\Users\23780\AppData\Roaming\top.1216.devflow\devflow.db`)定位。
- [x] ✅(2026-06-19·df-execute shell.rs 全仓唯一子进程源(grep 确认)·tokio Command 加 creation_flags(0x0800_0000) CREATE_NO_WINDOW·cargo check df-execute EXIT 0 无 warning) **B-260619-01 [P1]****执行命令行弹黑色窗口闪烁**。Windows tokio::process::Command 创建 cmd/powershell 子进程默认带控制台窗口(黑窗闪现)。修:`#[cfg(windows)] cmd.creation_flags(0x0800_0000)`(CREATE_NO_WINDOW)。tokio Command Windows 自带 creation_flags 方法(无需 std CommandExt trait)。覆盖 run_command 工具 + 工作流 shell 节点(全经 df-execute)。— `crates/df-execute/src/shell.rs`(:86-93)
- [x] ✅(2026-06-22·conversation.rs:65 TRUNCATE_THRESHOLD 50KB→8KB + HEAD/TAIL 各3KB + 截断提示含原字节数让 LLM 感知非全文·cargo check devflow EXIT 0 + conversation 测试 25 passed·仅作用持久化视图不污染内存真相源) **B-260619-02** — GLM 1214 tool_result 过大截断。已修(大小根因,配 CR-260620-04 配对错根因互补)。DB 直查报错对话 f64dee94:全量 546 条(多数 compressed),**active(发往GLM)仅 7 条结构合法**(system/assistant/user/assistant/user 交替,tool_use↔tool_result 配对 orphan=0,无连续 role,57K 字节 ~19K tokens **未超 GLM 128K**)。**真凶**:单条 tool_result 巨大(read_file 整文件 #2=21843B/#3=16511B/#6=11482B),T-05 截断阈值 50KB 未触发(21KB<50KB),但 **GLM anthropic 端点单条 tool_result content 限制更严(~10-20KB 即拒 1214)**。次要:#4 assistant content 空(len=0 只有 tool_use);"回复一半消失"=1214 在 GLM 流极早(message_start 前)error→stream_recv InitFailed→emit AiError→前端清 currentText,保文逻辑未覆盖。**修法方向**:① tool_result 截断阈值收紧 50KB→8-10KB(read_file/list_directory 大文件截断+提示)② MidStream 早 1214 保文(apply_anthropic_event error 时若已 message_start 不清流式)③ 空 assistant convert 兜底。**已加诊断(待重编译验证)**:anthropic_compat precheck(首条/连续/input/空content/orphan 5 类 Init bail)+ MidStream error 附 messages 摘要(SSE error chunk 塞摘要到前端 raw)+ HTTP/1.1 治 GLM HTTP/2 RST + 错误源链进 anyhow 文案。— `crates/df-ai/src/anthropic_compat.rs`(precheck/summarize/MidStream 摘要/version HTTP_1_1) + tool_result 截断(T-05 `tool_registry.rs`/`audit.rs` 阈值 50K→8-10K) + MidStream 保文(`stream_recv.rs`)
- [x] ✅(2026-06-20·cargo df-ai+devflow EXIT 0 零 warning + vue-tsc EXIT 0·主 worktree 改,workflow worktree baseRef 故障绕过) **CR-260620-04 [P0]****1214/400 messages 非法·配对错根因(compress 绕过 sanitize)+ 停用模型路由穿透**。用户贴 deepseek 400「tool_calls must be followed by tool messages」/「tool must be response to tool_calls」确认 **tool_use/tool_result 配对错**(与 B-260619-02 tool_result 大小根因**互补,1214 多根因**)。**根因**:`sanitize_messages`(context.rs:212 畸形配对自愈,三档+孤儿)只在 `build_for_request` 调(主对话),**`compress_via_llm`(compress.rs:46)构造 prompt 不经它**(:70 extend active_msgs 直进)→ 压缩带毒 → 400「自动上下文压缩失败」。**修法**:① `context.rs:212 fn sanitize_messages``pub fn`(跨 crate);② `compress.rs` 空检查后 `let active_msgs = df_ai::context::ContextManager::sanitize_messages(active_msgs)` 治毒 + `:90` 错误带 `active_count` 诊断;③ 停用模型 `agentic/mod.rs:188,450` model_override `.any(|m|m.model_id==id)``==id && m.enabled`(会话内 stale override 不穿透 disabled)。**关联**:B-260619-02(tool_result 大小根因 GLM 1214,截断 50K→8-10K **仍待做** `:412`)/ F-09 newConversation(CR-260620-02 ✅)/ 用户实测 deepseek 400。**未做**:tool_result 截断(B-260619-02 大小根因,GLM 若还犯需做)+ stream_recv SSE 错误 summarize(诊断仅 compress 层,stream_recv 待补)。— `crates/df-ai/src/context.rs` + `src-tauri/src/commands/ai/compress.rs` + `src-tauri/src/commands/ai/agentic/mod.rs`
---
### 🔧 2026-06-19 ToolCard 同批/跨轮 UI 合并子代理方案·其他会话实施·P1
> 用户需求:AI 多轮 read_file(轮1×2 + 轮2×2)前端一排展开卡片("上面两个下面两个"),希望 UI 合并。后端 convert 已合并连续 tool_result(B-260619-03),前端展示层对齐。子代理分析:同批同类合并**已存在**(ToolCardList groupedToolCalls L92-110,calls>=2 显标题行+默认收起),**痛点是跨轮**(轮1/轮2 各独立 ToolCardList 实例→各成分组)。用户强调"谨慎不出错"。
- [ ] **B-260619-04 [P1]****ToolCard UI 合并:跨轮同类自动收起 + 标题信息增强**。推荐**方案 A**(不合并卡片本体/不跨 message 边界/不动数据结构,只做 ① 标题增强 ② 新轮收起旧轮):
- **现状(已查清)**:`ToolCardList.vue` groupedToolCalls 按 tc.name 全局聚合,calls>=2 显「[图标] 读取文件 [N] ▸」标题行 + 默认收起(initDefaultCollapse L139-147)。**同批 read_file×2 应已是折叠组**;用户「下面又两个」=下一轮新 message 的独立 ToolCardList 实例→第二个分组(跨轮未合并)。
- **方案 A 改动**:
1. **标题信息增强**(低风险):收起态「读取文件 [2]」→「读取 2 个文件 · shortPath1 · shortPath2」(从 group.calls[].args.path 取前 2-3 个 shortPath 截断)。新增 `groupSummary(group)` computed + i18n 键 `aiTool.readNFiles`(zh/en,**漏 en 键 vue-i18n prod 报错**)。
2. **新轮到达自动收起旧轮已完成组**(中风险):AiChat.vue watch 末尾 message toolCalls 到达→对前序 message 的 ToolCardList 调 expose 新方法 `collapseCompletedGroups()`(只收起 calls 全 completed 且无 pending_approval 的组,尊重 userExpandedCards 记忆)。
- **前置必做(步骤1,0 改动验证)**:跑 read_file×2 确认同批是否真成分组标题(排除 initDefaultCollapse watch 一次性锁 bug——immediate 在 toolCalls 异步到达 len===0 跳过,L152-160 initialized 锁可能误锁)。若同批未分组→先修此 P0 前置。
- **风险(全列,谨慎)**:R2/R3 收起的分组 `display:none` 会盖过审批卡 `shouldKeepOpen` v-show(现有风险),自动收起**必须跳过含 pending_approval 的组**;R5 流式 calls 1→2 跃变时不应收起用户正在看的卡(组内已有 userExpandedCards 命中→该组不默认收起);R7 scrollToFirstPending 兜底(offsetParent===null→全展开重查)需保证自动收起走 collapsedGroups 机制而非硬 display:none。
- **谨慎实施步骤**:① 0 改动验证同批分组 → ② 标题增强(低风险) → ③ 新轮收起旧轮(中风险,跳 pending) → ④ 流式跃变保护 → ⑤ 回归(审批徽标/批量审批/切对话/High 二次确认)。
- **不推荐方案 B**(跨轮全局合并,破坏 message 边界,审批/折叠重构高风险)/ **C**(纯视觉框,不解决占用)。
`src/components/ToolCardList.vue`(groupedToolCalls L92-110/initDefaultCollapse L139-160/isCardExpanded L222-234/collapseInactive L265-296) + `src/components/AiChat.vue`(watch messages) + `src/i18n/{zh-CN,en}/aiTool.ts`(readNFiles)
---
### 💡 2026-06-19 新需求(任务关联灵感·已分析·待实施)
> 用户需求:推进任务时能即时、方便地关联到灵感及灵感的对抗式评估等相关信息。当前关联链路是 Idea → promote → Project → Tasks任务和灵感只能通过项目间接关联无法直接追溯。
- [x] ✅(2026-06-20 核验已落地·migrations.rs:47 `(20, migrate_v20)` + :361 `migrate_v20` 列存在探测 ALTER + :562/:579 V9_SQL tasks 建表带 idea_id 列(新库直有,老库 V20 补)·models.rs:39 + :82 TaskRecord `pub idea_id: Option<String>` + `#[serde(default)]` 兼容旧 JSON·crud/task_repo.rs:34 `task_from_row row.get("idea_id")` + :52 INSERT + :63 UPDATE + :83/:184/:205 SELECT 列表(list_active/find_by_id/list_deleted)全含 idea_id·测试 migrations.rs:1052 `v20_legacy_db_adds_idea_id_column` + :1078 列存在跳 ALTER 验证·对齐 F-260619-05 已标✅ 同实现) **F-260619-01 [P2]****任务关联灵感TaskRecord 新增 idea_id 字段**。推进任务时即时查看关联灵感的描述、多维评分(scores)、对抗式评估(ai_analysis)等信息,辅助决策。
**数据模型变更**TaskRecord 新增 `pub idea_id: Option<String>`(关联灵感 ID可空=未关联),`#[serde(default)]` 兼容旧 JSON。
**涉及改动7 处)**
1. **迁移 V20**`migrations.rs`tasks 表 `ALTER TABLE ADD COLUMN idea_id TEXT`nullable老数据 NULLV1 建表 SQL 同步补 idea_id 列新库直接有steps 数组追加 `(20, migrate_v20)`;用 `column_exists` 探测(同 v17/v18/v19 模式),对新库/老库均安全。
2. **Model**`models.rs`TaskRecord 加 `pub idea_id: Option<String>` + `#[serde(default)]`
3. **CRUD 层**`crud/task_repo.rs``task_from_row``idea_id: row.get("idea_id")?``impl_repo!` 的 insert/update SQL 加 idea_id 列 + params 占位;`list_active` / `list_deleted` / `advance_status_atomic` 的显式 SELECT 列表补 idea_id。
4. **白名单**`crud/settings.rs`tasks 白名单加 `"idea_id"`(允许 `update_field` 改关联)。
5. **命令层**`commands/task.rs``CreateTaskInput``pub idea_id: Option<String>``create_task` 构造 TaskRecord 时写入 idea_id`update_task` 对 idea_id 做跨表存在性校验(对标 project_id 校验模式:查 ideas 表确认存在,空值=解除关联允许通过)。
6. **AI 工具层**`commands/ai/tool_registry.rs``create_task` 工具 schema 加 idea_id 可选参数;`update_task` 工具 schema 加 idea_id描述说明可关联灵感
7. **前端**:任务创建表单加灵感选择器(下拉选 ideas 列表,可空);任务详情页加关联灵感卡片(只读,展示灵感标题 + 描述摘要 + 多维评分 scores + 对抗式评估 ai_analysis + promoted_to 状态);`update_task` 支持 idea_id 字段更新(空值解除关联)。前端获取方式:任务详情展开时若 idea_id 非空,调 `list_ideas` 或新增 `get_idea_by_id` IPC 拉取关联灵感记录渲染卡片。
**验收标准**:① 创建任务时可选关联灵感;② 任务详情页展示关联灵感的描述 + 评分 + 对抗式评估;③ 可更新/解除关联update_task idea_id = "" 清空);④ 老任务idea_id NULL无回归`cargo check --workspace EXIT 0` + `vue-tsc EXIT 0`
`crates/df-storage/src/{models.rs,migrations.rs,crud/task_repo.rs,crud/settings.rs}` + `src-tauri/src/commands/{task.rs,ai/tool_registry.rs}` + 前端任务组件
---
### 💡 2026-06-19 新需求MCP Server·灵感来源·待实施
> 灵感来源:`9ffa5f15` DevFlow MCP Server对抗式评估 7.2/10推荐 with resources
> 评估关键结论技术切入点和研发性价比极佳ToolDefinition ↔ MCP schema 几乎一致,转换成本极低),但反方强度 0.78——暴露 delete_project 等破坏性操作缺乏鉴权/确认/回滚,外部 AI 幻觉可能导致不可逆误删。**必须在协议接入的同时做工具集安全降级**。
- [x] ✅(2026-06-20 核验已落地·crates/df-mcp 存在:lib.rs+protocol.rs+server.rs+tools.rs,`pub use server::run_server` 导出 ·src-tauri/src/main.rs:16 `args.iter().nth(1) == Some("mcp-server")` 子命令分支 + :28 `run_mcp_server` + :99 `df_mcp::run_server(&db_path, read_only)` 启动 ·src-tauri/Cargo.toml:36 `df-mcp = { path = "../crates/df-mcp" }` 依赖已接 ·lib.rs 注释含安全降级(High 默认拒绝/Medium 日志/--read-only/不暴露文件工具)对齐设计) **F-260619-02 [P2]****DevFlow MCP Server对外暴露任务/项目/灵感管理能力**。以 MCPModel Context Protocolserver 形式对外暴露 DevFlow 数据层工具,让外部 AI 客户端Claude Code / Cursor / Cline直接读写 DevFlow 的项目、任务、灵感数据。
**形态**:独立 stdio MCP server 子命令(方案 A`devflow mcp-server` 启动,应用未运行时也能被 Claude Code 拉起。
**协议实现**
- `initialize` → 返回 server info + capabilities(tools)
- `tools/list` → 从 AiToolRegistry::tool_definitions() 转换为 MCP Tool 格式
- `tools/call` → 路由到 handler返回 `content: [{ type: "text", text: JSON }]`
- 推荐用 rmcp官方 Rust SDK或最小化手写 JSON-RPC 2.0 over stdio
- MCP Tool schema 与现有 ToolDefinition 结构几乎一致name/description/parameters JSON Schema写一个 `tool_definition_to_mcp()` 转换函数即可
**工具集(首版只暴露数据层,不暴露文件系统工具)**
- 项目list_projects / get_project / create_project / update_project / delete_project / bind_directory
- 任务list_tasks / create_task / update_task / advance_task / delete_task
- 灵感list_ideas / create_idea / update_idea / delete_idea / evaluate_idea
- 工作流run_workflow首版只读/需确认)
- 其他list_trash / restore_project
**⚠️ 安全降级(对抗评估反方核心风险,必须同步实施)**
- **High 风险操作delete/purge/run_workflow默认拒绝** + 返回「请在 DevFlow 应用内执行」——防外部 AI 幻觉导致不可逆误删(反方强度 0.78 核心论点)
- Medium 风险操作create/update默认允许 + 日志审计(写 ai_tool_executions 表留痕)
- 可选 `--read-only` 标志仅暴露 list/get 工具
- **不暴露文件系统工具**read_file/write_file/delete_file 等),避免绕过应用内路径校验
- 仅 stdio / 127.0.0.1,不暴露网络
- 不引入 MCP 客户端功能DevFlow 只做 server
**数据库访问**
- stdio server 启动时打开同一 SQLite`app_data_dir/devflow.db``--db` 参数指定)
- **SQLite WAL 模式确认**:需确认 df-storage 已启用 WAL反方论点并发锁竞争未处理。若未启用 WAL首版要求关闭 DevFlow 再用 MCP server或 MCP server 只读连接
**配置与发现**
- `devflow mcp-server --print-config` 输出 Claude Code / Cursor 的 MCP 配置 JSON 片段
- Claude Code 接入:`claude mcp add devflow -- devflow.exe mcp-server`
**涉及文件**
- 新建 `crates/df-mcp/`Cargo.toml / src/{server.rs, tools.rs, protocol.rs}
- 修改 `src-tauri/src/main.rs`CLI 参数解析,`mcp-server` 子命令分支)
- 修改 `src-tauri/Cargo.toml`(加 df-mcp 依赖 + clap
- 可选:`ai_tools.rs` 把 handler 闭包抽为可独立复用函数
**验收标准**
1. Claude Code 配置后能 `list_projects` / `list_tasks` / `create_task` / `advance_task` 成功
2. 应用运行时 MCP server 也能读写WAL 并发安全)
3. High 风险操作delete/purge/run_workflow被拒绝并返回友好提示
4. `cargo check --workspace EXIT 0` + `cargo test -p df-mcp`(基础协议测试)
5. `devflow mcp-server --print-config` 输出正确配置片段
— 新建 `crates/df-mcp/` + `src-tauri/src/main.rs` + `src-tauri/Cargo.toml` + 可选 `crates/df-ai/src/ai_tools.rs`
---
### 💡 2026-06-19 新需求AI 工具文件访问动态权限模型·已分析·待实施)
> 用户需求:将现有 `workspace_root` 单一根目录模型,升级为**动态白名单池 + 运行时权限申请**机制(类似 macOS / VS Code 的权限申请模式)。解决「锁太死」(只能绑定单一 workspace_root和「放太宽」的矛盾。
>
> **与 MCP ServerF-260619-02无关**:本需求是 DevFlow 应用内部 AI 工具read_file/write_file 等)的文件系统访问权限升级,不涉及对外 MCP 协议暴露。
- [x] **F-260619-03 [P1]** ✅ — **AI 工具文件访问动态权限模型**(Phase A 持久白名单 + Phase B 会话临时/弹窗 + Phase C 黑名单 + workspace_root 去固定根完整,CR-09 ✅ + b22e9ae reload 尊重用户 persistent)
- **2026-06-24 三档授权演进**:Phase B 弹窗从 once/always 两档 → **once/session/always 三档**(本次单次执行后清 / 当前会话切会话清 / 始终落KV)。修 once 原写 session 致"本次"实为"本会话"名实不符(确定性 bug)。AllowedDirs 加 `once` 层(state.rs)+ ai_authorize_dir decision 改 match 四分支(chat.rs)+ 执行后 clear_once + tracing 审计 + 前端 DirAuthDialog 加"当前会话"按钮 + useAiApproval/api/types.ts/i18n 联合类型扩 session。cargo check 0 + vue-tsc 0。审查见待审查 CR-260624-01。
**核心机制**:当 AI 调用 `read_file` / `write_file` 等文件系统工具时,路径校验从「单一 workspace_root 前缀匹配」升级为「动态白名单池校验 + 未命中则挂起 Agentic Loop 向前端弹窗申请授权」。
**数据结构变更**
1. **持久化白名单**Settings KV 存储):`app_settings` 表 key=`allowed_dirs`value=JSON 数组 `["E:/wk-lab/u-abc", "E:/wk-lab/u-img"]`。前端 Settings 页提供列表增删改查 UI。
2. **会话级临时白名单**(内存):`AiSession` 新增 `session_allowed_dirs: HashSet<PathBuf>`,仅限当前会话有效。
3. **全局状态**`AppState` 引入 `AllowedDirs { persistent: HashSet<PathBuf>, session: HashSet<PathBuf> }`,替代写死的 `workspace_root()`
**权限拦截与申请流程**
1. **规范化路径**`canonicalize`(解析软链接、`.``..`)。
2. **检查白名单**:判断 `canonicalize` 后的真实路径是否在持久化或会话白名单中(`real_path.starts_with(allowed_dir)`)。
3. **命中则放行**:执行原逻辑。
4. **未命中则拦截**:挂起当前 Agentic Loop → Tauri 事件向前端弹窗 `AiDirAuthRequired { path, tool_name }` → 用户选择「仅本次」(加入 session_allowed_dirs/「未来都允许」(写入 DB Settings + 更新 persistent/「拒绝」→ 恢复执行。
**授权粒度**:弹窗中建议授权目标文件所在的**项目根目录**(而非具体文件),减少弹窗频率。用户可在弹窗中手动收窄或放宽范围。
**安全防护(三层)**
- **第一层 canonicalize**:解析软链接后校验真实路径,防软链接逃逸(授权目录本身也需 canonicalize
- **第二层白名单**`real_path.starts_with(allowed_dir)` 校验。
- **第三层黑名单兜底**:保留现有 `validate_path`,禁止系统敏感目录(`/etc``/var``C:\Windows\System32` 等)。
**写操作额外约束**写操作write_file/delete_file/patch_file即使目录已授权仍走现有 RiskLevel 审批流Medium/High 需用户确认);`delete_file` 始终 High 风险审批,不受白名单影响。
**分阶段实施**
| 阶段 | 内容 | 复杂度 | 优先级 |
|------|------|--------|--------|
| **Phase A** | Settings 持久化白名单 + `resolve_workspace_path` 改造为多目录校验canonicalize + starts_with | 低 | P1 |
| **Phase B** | 会话级临时授权 + Agentic Loop 挂起/恢复 + 前端弹窗 UI + Tauri 事件 | 高 | P2 |
| **Phase C** | 软链接深度防护 + 系统目录黑名单完善 + 写操作额外约束 | 中 | P2 |
Phase A 成本最低但收益最大——立即解决「只能绑定单一 workspace_root」的限制且为后续动态授权打好数据基础。
**与现有架构的契合点**
- SettingsRepo KV 存储:已有,`allowed_dirs` 直接复用,零迁移成本。
- RiskLevel 审批流:已有完整 tool approval 机制,动态授权可视为「路径级别的 approval」。
- Agentic Loop已有挂起/恢复能力(审批等待),扩展路径授权挂起是同构的。
- Tauri 事件系统:已有 `ai-chat-event`,新增 `AiDirAuthRequired` 事件类型即可。
**关键改动点**
- `tool_registry.rs`handler 闭包需引入 `AllowedDirs``Arc` 引用(当前闭包是无状态 `Box::new(|args| ...)`),调整注册逻辑。
- `resolve_workspace_path`(或等效路径校验函数):从单一 workspace_root 前缀匹配 → 多目录白名单 canonicalize 校验。
- `state.rs`AppState 新增 `allowed_dirs` 字段。
- `commands/ai/mod.rs`AiSession 新增 `session_allowed_dirs` 字段。
- `agentic.rs`:捕获 `PATH_AUTH_REQUIRED` 信号 → 挂起 → emit 事件 → 等待恢复。
- 前端 Settings 页:新增「授权目录」管理 UI。
- 前端 AiChat新增路径授权弹窗组件。
**验收标准**
1. Settings 页可管理持久化授权目录列表(增删改查)
2. AI 访问授权目录内文件正常执行,无额外弹窗
3. AI 访问授权目录外文件时弹窗申请,用户可选择「仅本次」/「未来都允许」/「拒绝」
4. 软链接逃逸被 canonicalize 校验拦截
5. 系统敏感目录始终被拒绝(黑名单兜底)
6. `cargo check --workspace EXIT 0` + `vue-tsc EXIT 0`
`src-tauri/src/state.rs` + `src-tauri/src/commands/ai/{mod.rs,tool_registry.rs,agentic.rs}` + `src-tauri/src/commands/settings.rs` + 前端 Settings 页 + AiChat 弹窗组件
---
### F-260619-05 任务可关联灵感tasks ↔ ideas 关联)
**背景**:当前 tasks 表无 idea 关联字段,任务与灵感(来源/启发无结构化关联。projects 表已有 `idea_id REFERENCES ideas(id)`(项目关联灵感),任务侧缺失。
**现状**(核对 `df-storage/src/migrations.rs`
- tasks 表(:378-391id/project_id/title/description/status/priority/branch_name/assignee — **无 idea 关联**
- projects 表(:373`idea_id REFERENCES ideas(id)` — 项目关联灵感(**可复用模式**
- ideas 表(:354-365`source TEXT`(自由文本来源)
- releases 表(:399`task_ids JSON`发布关联多任务1对多 JSON 数组先例)
**设计点(待决策)**
1. **关联粒度**:一对一(`source_idea_id`任务来源单个灵感vs 一对多(`related_idea_ids` JSON 数组,借鉴 releases.task_ids
2. **存储**tasks 加 `idea_id` 列(迁移,复用 projects.idea_id 外键模式vs 关联表 `task_idea_links`(多对多,灵活但复杂)
3. **方向**单向任务→灵感vs 双向(灵感侧反向显示关联任务列表)
4. **AI 工具**`update_task` 支持 idea_id白名单加列vs 新工具 `link_task_idea`
5. **前端**:任务卡片/详情显示关联灵感 + 选择关联mention 复用 ChatInput @灵感
**推荐**tasks 加 `idea_id TEXT REFERENCES ideas(id)`(复用 projects 模式1对1 起步,后续按需扩展 1对多灵感侧反向查询 `list_tasks_by_idea`
**依赖**:改 `df-storage/migrations.rs` + `models.rs`TaskRecord 加 idea_id— 与消息级溯源 P0 改同文件冲突,**待 P0 完成后实施**。
**改动点**
- `crates/df-storage/src/migrations.rs`tasks 加 idea_id 列(新迁移 V
- `crates/df-storage/src/models.rs`TaskRecord 加 idea_id 字段 + from_row + insert/update SQL
- `crates/df-storage/src/crud/task_repo.rs`:白名单 + idea_id 读写
- `src-tauri/src/commands/ai/tool_registry.rs`update_task 支持 idea_id或新工具
- 前端:任务卡片/详情关联灵感展示(友好 title 非裸 id+ 选择
**验收**
1. 任务可关联灵感(创建/更新时指定 idea_id
2. 任务卡片显示关联灵感title 友好展示)
3. 灵感详情可查关联任务列表(若双向)
4. `cargo check --workspace EXIT 0` + `vue-tsc EXIT 0`
**状态**:✅ 已实施(tasks.idea_id 1对1 单向,commit 4a87c55,粒度/方向已定)
---
### 💡 灵感模块升级(2026-06-21,批1-3c·已实施)
> 灵感模块可信度+体验+评估历史+统计+晋升携带+关联关系全面升级。对齐 memory [[devflow-idea-module]](df-ideas 评估闭环已通,对抗未接 LLM/晋升走前端)后续延伸。
**✅ 批1 可信度+体验**:
- P0 `evaluated_by` 落库(ai_analysis 关联谁评的)+评估深度标签动态(LLM/启发式/降级,直显评估来源可信度)+评估失败重试(瞬时失败可恢复)+列表排序(评分/时间,支持按评分挑高价值)+详情描述可编辑(描述可订正)+已晋升项目跳转(promoted_to → 项目直跳)
**✅ 批2 评估历史全栈**:
- `idea_evaluations` 版本快照表(每次评估留版本可追溯)+`IdeaEvalRepo`(版本数据访问层)+`evaluate_idea` 每次 insert 历史(评估产生历史记录)+`list_idea_evaluations` IPC(暴露历史查询)+`IdeaDetail` 版本时间线(前端时间线组件渲染历次评估)
**✅ 批3 IdeasPanel 统计看板**:总数/待评估/已立项/平均分(模块概览一眼可见)
**✅ 批3a 晋升携带**:`ProjectDetail` 来源灵感卡片(反查 `idea_id` 显示评估结论,项目可追溯灵感来源)
**✅ 批3b 关联关系 schema**:`ideas.related_ids` JSON 列 + `IdeaRecord` 字段(为关联 UI 打底)
**✅ 批3c 关联关系前端**:`IdeaDetail` 关联灵感展示区 + 多选管理选择器 + `Ideas.vue update-related` handler
**✅ P1 信号词否定前缀**:`scoring.rs count_any` 不复用/无增长等否定不计正分(修评分虚高,如"无增长"误算"增长"正分)
**验证**:cargo check src-tauri ✅ + df-storage 62/11 tests ✅ + df-ideas 22 tests ✅ + vue-tsc 灵感模块零新错 ✅
**连带修**:`augmentation/resolvers.rs``ResolverRegistry` import(预存编译阻断,顺手补)
**状态**:✅ 批1-3c 全完成(主代独立验证 cargo + tests + vue-tsc 通过)
### ⚠️ 灵感升级-遗留缺陷(2026-06-21·待另一会话修)
- [x] ✅(2026-06-22 核验·settings.rs:123 ideas 白名单已含 related_ids,14 列非 13,批3c 落地时已补,关联 update_field 放行非静默失败·过时 todo 销账) **related_ids 白名单** — 原"P0 功能缺失"为过时信息,实际已落地。
- [P2 并发·🟡部分完成] 评估历史 version 并发重复:idea.rs:288-293 evaluate_idea 已加重试兜底 + V25 (idea_id,version) 唯一约束(migrations.rs)兜底。约束+重试就位,运行时确认约束生效后销账。单用户桌面低概率,严格讲需加唯一约束或事务。批2 引入。
- [P2 运行·未验] 批1-3c 前端运行时未实测:编译/vue-tsc 过,但运行(评估历史加载/关联管理交互/统计 computed/ProjectDetail 来源卡片反查 store.ideas)未实际跑过。需 npm run tauri dev 实测。
---
### 💡 灵感模块-来源采集/追溯(2026-06-21·待办·新功能范畴)
> 继批1-3c 升级后的下一步:灵感来源录入增强 + 自动采集机制。**属新功能范畴**,需独立设计采集规则/触发/去重,下一阶段推进。
- [ ] **F-260621-01 [P2/新功能]****source 字段录入增强 + 自动采集机制**
1. **source 录入增强**:灵感捕捉模态框加 `source` / `tags` / `priority` 输入(现仅描述),让录入即结构化(来源/标签/优先级)
2. **自动采集机制**:从对话 / 任务 / 代码中自动提炼灵感(降低录入门槛,捕捉随手遗失的灵感)
- **需设计**:采集规则(什么信号算灵感 — 关键词/意图/重复模式)/ 触发时机(对话结束/任务完成/手动触发)/ 去重(相似灵感合并避免洪水)/ 用户确认流(自动采集不污染灵感池,需用户确认入库)
- **依赖**:批1-3c 升级完成(已有 source/tags 字段基础) + 对话历史可读(F-15 archived_segment 状态) + 任务可读
- **关联**:memory [[devflow-idea-module]] / [[aichat-skill-slash-autocomplete]](联想需求,场景相关)
---
### ⚠️ 预存债-非灵感模块:ToolResult 类型缺字段(用户并行开发中间态·阻断 build)
> 用户并行开发工具结果展示时,`ToolResultBody.vue` + `composables/ai/useToolCard.ts` 引用了 `ToolResult` 类型不存在的字段(vue-tsc 7+ 错)。**非本次灵感模块改动引入**,是用户工作区并行开发中间态。不阻断 `vite dev`(dev 宽松),阻断 `npm run build`(prod 严格)。
- [x] ✅(2026-06-22 核验·useToolCard.ts:88-100 ToolResult 已含 output_mode/matches/files/counts/total_files 五字段(F-260621 grep 跨文件搜索)+注释说明三模式语义·ToolResultBody.vue:71-95 三模式渲染消费·useToolCard.ts:424-446 formatGrep 消费·vue-tsc --noEmit EXIT 0 无 TS 错·漏勾销账) **TD-260621-07 [P2/预存债·待用户对齐]****ToolResult 类型缺 output_mode/files/counts/matches/total_files 字段**
- **现状**:`ToolResult`(types.ts)未定义 `output_mode` / `files` / `counts` / `matches` / `total_files` 五字段,但 `ToolResultBody.vue` + `useToolCard.ts` 模板/computed 已引用 → vue-tsc 报 7+ Property does not exist
- **定位**:用户并行开发工具结果展示增强(可能是为了批1 评估深度标签/批2 历史时间线之外的工具结果多模式渲染 — output_mode 暗示 tab/列表/树等多种展示模式),属进行中的中间态
- **修法**:① `ToolResult` 类型补齐 5 字段(对齐后端返回结构) + ② `ToolResultBody.vue` / `useToolCard.ts` 消费方与类型对齐(可能需后端 `tool_result_summary` IPC 返回结构同步扩字段)
- **不阻断 dev**:vite dev 宽松回退,vue-tsc prod build 严格;用户 dev 调试不受影响,仅 build/release 受阻
- **待用户**:确认字段语义(output_mode 取值集合 / files 结构 / counts 计什么)后对齐,非本会话灵感模块范畴
- [x] ✅(workflow wf 批1·streamingGuard.ts 新增 setStreaming/forceResetStreaming guard + 14 处 state.streaming 散布赋值收敛(useAiSend 7/useAiEvents 2/useAiStream 1/useAiConversations 2/useAiWindow 2/useAiPanel 1)+ feature flag df-ai-generating-statemachine(appSettings,关时回退散布语义)+ onStreamTimeout 改走 forceResetStreaming 兜底 + scripts/verify-streaming-guard.mjs 9 单测全过·vue-tsc EXIT 0·cargo check EXIT 0) **TD-260621-GUARD****[P0·用户实测卡死根因]** generating 状态机前端落地(对齐 memory [[devflow-generating-statemachine]] + 专项设计文档)。后端 RAII guard(B-09)+ session_state() enum 视图(B-12)+ per_conv 化(F-09 批4)历史批次已落地,本批补前端 streaming 写收敛:state.streaming 散布在 6 文件 14 处直接赋值,任一 return 漏写/前端 JS 异常跳过复位 → streaming 永久 true 卡死输入框。收敛到 setStreaming 单一写入口(合法性观测日志 + generatingConvs 联动 + flag 灰度),onStreamTimeout 走 forceResetStreaming 兜底复位 Idle+清 currentText/queue。注:TD-260621-06a(switchConversation 重算 streaming)本批已纳入 guard(convId=id 幂等联动)。
- **关联**:批1-3c 灵感升级无涉(独立预存债)
---
### F-260620-01 跨端 AI Chat:微信小程序 ↔ Rust 云后端 ↔ DevFlow 桌面端
**背景**:微信小程序远程用 DevFlow AI Chat,桌面端与小程序双向实时同步(微信电脑+手机同时在线模式)。
**设计**:[F-260620-01-跨端AIChat-微信小程序-2026-06-20.md](../docs/02-架构设计/已编号方案/F-260620-01-跨端AIChat-微信小程序-2026-06-20.md)(三层架构 df-tunnel/df-relay/df-miniapp + Rust 云后端选型)
**关系**:灵感 4495fbcd(选型论证,pending_review)+ 任务 6c816709(todo)
**实施分阶段**:
- **P1** df-relay 云后端(axum WS Server + 广播中继 + 鉴权)— 无依赖
- **P2** df-tunnel 桌面端(WS client + 事件桥接 ai-chat-event 透传 + 指令路由 + 配对绑定)— 依赖 P1
- **P3** df-miniapp 小程序(前端 UI 复用 + WSS 连云)— 依赖 P1/P2
- **P4** 双向同步完善(冲突处理 + 安全 + 离线降级 + 部署)— 依赖 P1/P2/P3
**状态**:✅ Phase2 三层全落地(2026-06-22:df-relay `2b8b30e` Hello握手+鉴权+ConnRegistry配对路由+BroadcastMessage透传 / df-tunnel `25d6565` WS客户端出站穿NAT+心跳25s+指数退避+TunnelCommand 5变体 / df-miniapp `280baea` uni-app WS连relay透传+18变体AiChatEvent镜像+useAiChat分派;双 crate 独立消息骨架不依赖 src-tauri/df-types 避跨 crate 强耦合)。📐 **Phase3 联调设计已出** [F-260622-01](../docs/02-架构设计/已编号方案/F-260622-01-跨端AIChat-Phase3联调设计-2026-06-22.md)(协议统一方案A纯透传 + AiSession桥接风险 + 4阶段路线),待实施。
**Phase3 联调待办**(D1-D6 决策点推荐:A纯透传 / 全19变体透传 / EventBus汇聚 / switch不处理 / 强类型保留 / 桥接层R1兜底):
- [x] ✅(2026-06-22)F-260622-01-阶段1(D1=A):tunnel `CommandHandler``serde_json::Value` + `parse_payload_from_broadcast` 返回 Value(入站纯透传,cargo check + 5 测试过,D5 保留 TunnelCommand 弱校验)。**出站 `send_raw_event` 移阶段2**(无阶段1 验证场景,阶段2 EventBus 接入同步加)
- [x] ✅(2026-06-22)F-260622-01-阶段2:前置决策 ② EventBus Value(0fb5af5)+ emit 双写全接入 chat19/audit12/guard3/stream_recv2/agentic18(612de4c)+ send_raw_event(030ddb6)+ tunnel subscriber(ed8e2fc)。**后端上行闭环**(EventBus→tunnel→relay→miniapp)。剩余:relay_url/device_id Settings UI(方案A)+ token 生产级(Phase4)
- [x] ✅(2026-06-22)F-260622-01-阶段3:remote_bridge 桥接(0fb5af5)+ miniapp 4命令(8ccdce2)+ on_command 注册(ed8e2fc)。**下行闭环**(miniapp Command→tunnel on_command→remote_bridge→Tauri command)
- [ ] F-260622-01-阶段4:真机联调 + 多会话并发验证矩阵(6 场景含 F-09 跨端并发)
- [x] ✅ 核验项:ai_chat_send 内部 generating guard(chat.rs:354-358 有 `is_gen → Err<String>`);R1 仍加作双保险(桥接层前置 + 内部 guard 双层)
- [x] ✅ 核验项:ai_chat_stop AwaitingApproval(chat.rs:1469-1484 finalize_pending_placeholders + retain 清 pending + generating=false + emit AiCompleted,R3 已缓解)
---
### AI Chat 跑题/抓不住重点改进(跑题修复)
**背景**:AI Chat 对话跑题/抓不住重点。根因(深度分析):系统提示无聚焦 + 意图未接入 loop + 压缩丢重点 + 工具挤占 + 无主题检测。
**设计**:改进方案 Plan agent 规划中(优雅/可靠/易迭代三原则)
**修复分阶段**(每阶段独立可回退,均 ✅ 完成):
-**P0** 系统提示聚焦(prompt.rs ## 聚焦准则/## Focus 独立段,102d398)
-**P0** 意图接入 agentic loop(intent filter_tool_defs 收敛工具 29→5-10 + 三重 fallback,102d398)
-**P1** 压缩增强(compress prompt 主题锚点 + 失败兜底 extract_keyword_summary,013ce21)
-**P1** 工具结果压缩(should_summarize/extract_key_info view-only 不改持久化,013ce21)
-**P2** 主题检测(TrackedMessage.topic + 双高置信保守 + tokenize 2-gram 修复中文锚点,a2db5c7)
- ✅ 苛刻测 38(边界/对抗:全漂移/全停用词/全错误行/2KB 边界/连续主题切换)+ db 基线试验记录文档
**状态**:✅ P0-P2 全完成(合并 de04970 + a2db5c7 + 38 苛刻测 + 试验记录文档)。待重启 dev 实跑验证 LLM 实际响应效果(机制测过 ≠ LLM 行为改善)。
---
### 单对话内并行多轮推理(主循环 ReAct 串行 → 规划式并行)
**背景**:单对话内并行现状评估——工具级并行(Low risk `join_all`,`audit/mod.rs:448/499`)✅ + 主/辅助 LLM 2 路并发(`per_conv permits=2`,`state.rs:121`)✅,但**主循环单链 ReAct 不支持并行多轮推理**(`run_agentic_loop` `agentic.rs:331-1206` 一轮一轮串行,无法拆子流并行)。这是单对话内并行的真正天花板,`permits=2` 是配套限流(非瓶颈)。
**根因**:ReAct 每轮依赖前轮结果,不产生并行多轮需求。要并行多轮需**规划式分解**,`coordinator.rs`/`conditions` 空壳未落地(SMELL-P2-4 B 路线占位 / memory [[aichat-arch-extensibility]])。
**方向(架构·规划放哪层——分轻/重)**:
- **轻规划融入意图层**:扩展 `intent.rs` `tool_subset_for``plan_hint`(工具编排/可并行组/顺序)+ `suggested_model_tier` 模态。**保持纯函数零延迟**(intent 已接入 loop,跑题 P0 `filter_tool_defs` 收敛工具 29→5-10 + 三重 fallback,102d398)。轻规划是自然延伸(意图→编排提示)。
- **重规划独立 planning 层**:`coordinator.rs` 空壳 → 演进 planning,消费 intent + plan_hint,做任务分解/子流并行调度/条件分支(conditions)。接入 loop,有状态(intent.rs 是无状态纯函数,重规划违背其定位,不塞进去)。
- **三层链路**:`intent`(识别,纯函数)→ `plan_hint`(意图层轻规划)→ `planning`(独立重规划/调度)→ `execution`(loop 按 plan 并行跑)
**决策点(待定)**:
- (a) 轻规划先扩(`intent → plan_hint`,低风险,跑题 P0 基础延伸)vs 直接重规划 planning(大改,coordinator 落地)
- (b) planning 形态:`coordinator.rs` 空壳演进 vs 新建 `plan.rs`
- (c) 并行多轮语义:用户是否真需单对话内并行探索多方案(当前 ReAct 单链足够大多数场景,可能非刚需)
**关联**:`intent.rs`(CR-08)/ 跑题 P0 intent 接入(102d398)/ `coordinator.rs` 空壳(SMELL-P2-4)/ conditions(T-260614-11)/ F-09 B 路线 / memory [[aichat-arch-extensibility]] / [[aichat-roadmap-ab-split]](B 路线)
**状态**:🔧 Phase1 已落(2026-06-22 PLANNING_ENABLED=true 主 loop 规划开关启用 + filter_tool_defs_planned 接入点就绪 commit cf7fe86,plan_hint 函数 PLAN_HINT_ENABLED 就绪双层开关可回退)。Phase2 评估(2026-06-22 agent 核验):plan_hint 完整非空壳(4 启发式规则 plan_hint.rs:264-351,Phase1 已生效生产路径非占位),Phase2=接 planner.rs LLM Plan DAG 重写主 loop 调度(L 工作量+🔴高回归风险:主 loop 绑 F-09并发/DIRAUTH/generating状态机/断路器/压缩 6 子系统),**暂缓先验 Phase1 收益**(埋点 agentic/mod.rs:648-656 就绪跑数据)。📋 待决策(Plan 严格调度 vs hint 模式双语义未定 plan_hint.rs:20-25)。属 aichat B 路线(决策能力)范畴,P0-P2 跑题已大幅改善 ReAct 单链覆盖大多数场景。
---
### 🟡 F-09 决策e 前端 newConversation 未跟进(中断弹窗遗留·用户实测)
**背景**:用户实测——生成中点新建对话,弹「当前正在生成,确定中断并新建?」。F-09 决策e 真并发已落地(切会话/新建不该打断旧 loop,各 conv 并行),该弹窗与并行矛盾。
**根因**:前端 `newConversation`**A 路线单 loop 软隔离遗留**(B-260615-10 + F-260616-09 A 路线),没随决策e B 路线升级:
- `AiChat.vue:246-251 confirmNewConversation`:生成中弹「中断并新建」确认(B-260615-10 软复位,单 loop「中断当前+新建」逻辑)
- `useAiConversations.ts:78 state.generatingConvs.clear()`:A 路线 F-260616-09 清字段,清空**所有**后台生成跟踪(后台 loop 后端还在跑,前端丢失跟踪 → 侧栏不显旧 conv 生成态 / 事件路由错乱)
- 对比 `switchConversation:89-91`:决策e **已落地**(「允许生成中切换:后台继续生成,事件按 conversation_id 路由」)。**`newConversation` 漏升级**。
**决策e 应有行为**:新建对话 = createConversation + 切 active,**旧 conv 后台 loop 继续**(后端 loop 按 conv 存在性退出,新建不删旧 conv 不打断)。不弹中断 + 不 clear generatingConvs(后台生成态保留,侧栏显双会话生成)。
**修法**:
- `confirmNewConversation` 去弹窗,直接 `store.newConversation`(决策e 新建=切到新 conv,旧后台继续,非中断)
- `newConversation``generatingConvs.clear()`(后台生成继续不清);`messages/queue/pendingApprovals/currentText` 是新 conv 本地视图复位,保留
- 核对:新建后旧 conv 后台事件路由正常(`generatingConvs` 含旧 conv,`isGenerating(旧)` 仍 true,侧栏双生成) + `AiCompleted`/`AiError` 按 conversation_id 正确收尾旧 conv
**优先级**:🟡 P1(决策e 体验破坏:用户期望并行被中断提示打断,且 clear 致后台生成前端丢失跟踪)
**关联**:F-09 决策e(CR-07 已审 PASS 后端真并发)/ B-260615-10 软复位(待废弃)/ F-260616-09 A→B 路线 / memory [[devflow-aichat-review-pending]]
**状态**:✅ 已实施(CR-260620-02 审查 PASS,confirmNewConversation 去弹窗 + newConversation 去 clear,2026-06-20)
---
### 🟡 MED-1 bind/create/update_project(Medium)AI 工具路径审批执行后 reload 未兑现(CR-260620-01 审查 MED)
**背景**:CR-260620-01 审查发现 — bind_directory/create_project/update_project 是 RiskLevel::Medium(tool_registry.rs:483/506/536),走 pending 审批,执行在 chat.rs `ai_approve`(非 process_tool_calls 内联)。bind_dir_to_project / repo.update_field 直写 DB 绕过 project.rs IPC,批3 三处 reload(create_with_binding/update_project/relocate)覆盖不到 AI 工具绑定路径。tool_registry.rs:545-549 注释承诺"reload 兜底"但代码空头。
**后果**:AI 用 bind_directory/create_project/update_project 工具绑定目录后,新目录未进白名单 → 后续文件工具仍误弹窗,需手动 Reload/重启。(用户手动绑定走 IPC project.rs 已修批3;AI 工具绑定路径漏)
**修法**(单点):chat.rs `ai_approve` 执行 Medium 工具成功后,若 tool_name ∈ {bind_directory, create_project, update_project},调 `state.reload_allowed_dirs()`。覆盖所有 Medium path 绑定工具审批执行路径。
**优先级**:🟡 MED(非安全,用户可手动 Reload;AI 主动 bind 场景,用户主诉 IPC 路径已解决)
**关联**:CR-260620-01 审查 MED-1 / 批3 project.rs reload(IPC 路径已修)/ agent3 问题5
**状态**:✅ 已实施(chat.rs ai_approve 两执行点 path 工具后 reload_allowed_dirs,cargo check EXIT 0,2026-06-20)
---
### ⚪ LOW F-09 stale 注释 + 死 i18n key 清理(CR-260620-02 审查 LOW)
**背景**:CR-260620-02 审查 — F-09 去中断弹窗后,3 处子组件 stale doc 注释仍写"带二次确认"(TopBar.vue:163 / ConversationSidebar.vue:226/242)+ i18n key `confirmNewConvWhileGenerating`(zh-CN settings:120 / en:119)成死 key(0 消费方)。
**修法**:删死 i18n key + 改 stale 注释为"决策e 直接新建"。
**优先级**:⚪ LOW(质量清理,可选)
**关联**:CR-260620-02 审查 LOW-1 / F-09 决策e
**状态**:✅ 已实施(删 aiChat.ts confirmNewConvWhileGenerating key zh/en + 改 TopBar/Sidebar 3 处 stale 注释,vue-tsc EXIT 0,2026-06-20)
---
### 🔍 2026-06-22 miniapp 功能走查(workflow wf_ddd49ac8 · 5 域并行核验 + 主代终态抽查)
> 用户 /review miniapp + workflow 汇总安排。5 agent 独立 grep/read 核验源码(不信注释/文档) + 主代终态核验(ws.ts 真路径 `src/api/ws.ts`、`relay.rs:255/355`、remote_bridge 命令数 10 非 11)。**18 findings → 去重 P1×5 / P2×3 / P3×1**,2 项 refuted。关联 F-260620-01 阶段4(真机联调前置)。**守 session-role-diagnose-only:本会话仅走查 + 登记,未实施代码。**
**P1(阻塞真机/可靠性·5 项)**:
- [ ] P1-D-260622 [P1🔴·真机分发最高阻断] — **token/deviceId 硬编码 + 无配对绑定 + 无 storage 持久化**`config.ts:35-39` 硬编码本机 device_id+token(注释自认「联调期硬编码,后续做配对绑定」),`:52-54` setConfig 仅内存赋值无 `uni.setStorageSync`。真机分发不能用本机硬编码 device_id。修:settings 页输入 + setConfig 持久化 storage + getConfig 读回退 default。— `apps/df-miniapp/src/config.ts`
- [ ] P1-A-260622 [P1🔴·可靠性] — **心跳 pong 闭环缺失**`ws.ts:226``{control_kind:'ping'}`,`relay.rs:355` miniapp 入站全标 `MessageKind::Command` 透传 device,但 ping 无 cmd 字段 → `remote_bridge.rs:143` match 无 ping 分支 → device 收不到有效命令无 pong 回;`useAiChat.ts:440` onControl 仅 console.log 无 pong 处理;`ws.ts:245-257` 重连仅靠 onClose/onError 无心跳超时。移动网络 TCP 半连接高发,device 静默挂死无感知。修:relay 解析 ping 构造 Control 类型 + device 响应 pong + miniapp 维护 lastPongTime 看门狗(3×interval 未收 pong 主动重连)。— `ws.ts` + `relay.rs:handle_inbound_text` + `useAiChat.ts`
- [x] P1-B-260622 [P1🔴·首屏连不上·✅2026-06-23核验已修] — **握手判定脆弱**`ws.ts:185` `text.includes('"error"')` 字符匹配判握手失败(真机业务消息含 error 字符误判);同时 `ws.ts:181` 注释「relay 无显式 ack」错——`relay.rs:255` 实发 `{control_kind:"hello_ack"}` 但代码漏检。修:handleHandshakeResponse 改结构化解析——判 `msg.kind==='control' && msg.control_kind==='hello_ack'` 为成功,error 判 `msg.error` 字段存在性。— `apps/df-miniapp/src/api/ws.ts`
- [x] P1-C-260622 [P1🔴·体验阻断·✅2026-06-23 核验已修] — **冷启动不恢复会话历史**`App.vue:8-10` onLaunch 仅 connect,`chat/index.vue:62-64` onShow 仅 scrollToBottom,`useAiChat.ts:61` activeConversationId 初始 null → 真机首次打开空白。修:连接成功后 refreshConversations;chat onShow 中 activeConversationId=null 且有会话时自动选最近会话 + switchConversation(load_messages)。— `App.vue` + `chat/index.vue` + `useAiChat.ts`
- [x] P1-E-260622 [P1🟡·多轮场景·✅2026-06-23] — **continueLoop/stopLoop 无 UI 入口**`useAiChat.ts:699-700` 已导出,`useAiChat.ts:241-250` AiMaxRoundsReached 仅 clearWatchdog + 系统气泡「请去桌面端处理」,chat/index.vue 全文无调用。多轮场景用户真机无法继续/停止。修:max rounds 时 chat 渲染继续/停止按钮调 continueLoop/stopLoop。— `apps/df-miniapp/src/pages/chat/index.vue`
**P2(UX/健壮性)**:
- [x] P2-B-260622 [P2·quickfix·✅2026-06-23核验已修] — **deviceOnline 离线不复位**`useAiChat.ts:76-77` 注释自认「MVP 不做超时复位」,全 src 无 `deviceOnline.value=false`,onStatus 断开/重连只 clearWatchdog。device 断线状态滞后。修:onStatus 回调 disconnected/reconnecting 加 `deviceOnline.value=false`(一行)。— `useAiChat.ts`
- [x] P2-C-260622 [P2·quickfix·✅2026-06-23] — **路由表注释陈旧(双侧)**`types/relay.ts:86` 称「8 条」实际 match 10 条;`remote_bridge.rs:13-25` 注释列 9 条实际 10(F-#95 扩展 list_conversations/load_messages 未同步注释)。修:双侧注释补 list_conversations/load_messages + 数字改 10。— `types/relay.ts:86-88` + `remote_bridge.rs:13-25`
**P3/defer**:
- [x] P2-A-260622 [P2·defer·✅2026-06-23] — **messages 内存态无上限(OOM)**`useAiChat.ts:56` reactive 无 MAX_MESSAGE。联调短会话无风险,长会话再做(MAX_MESSAGES=100 + truncate 保留最新 50)。— **已修**:watch trim MAX_STORE_MESSAGES=500(渲染另有 visibleMessages slice 200 兜底)。
- [x] P3-A-260622 [P3·defer·✅2026-06-23] — **输入框单行非 textarea**`chat/index.vue:176-184` `<input type="text">`。MVP 单行可接受,多行场景再升级 textarea auto-height。— **已改 input→textarea auto-height**(max-height 120px + flex-end 对齐;⚠️ 原生层级遮联想浮层风险待用户实测,fallback cover-view)。
**已驳回(refuted·不记 todo)**:regenerate 双 user 气泡(UI 展示层非逻辑错,switchConversation 即清)/ 看门狗 130s(实现正确无需修)/ 审批卡+流式光标(完成项核对,非问题)。
**真机分发前置序(P1 按依赖)**:P1-D(token/配对)→ P1-A(心跳闭环)→ P1-B(握手解析)→ P1-C(冷启动恢复)→ P1-E(max UI)。P2-B/C 可同批 quickfix(注释对齐 + 一行复位)。
**用户补充核验(2026-06-22 用户详读源码+设计文档,主代逐项核源码判真伪)**:
> ⚠️ 用户分析 P0 三项(协议三重不匹配 / EventBus 未接 / 桥接层占位)**经源码核验全部失实**,系基于 Phase3 阶段1-3 落地前的设计文档「计划态」描述,未核当前代码。证据:
> - **P0-1 协议**:`crates/df-tunnel/src/tunnel.rs:457/327/424` tunnel 纯透传 payload `Value` 上抛 on_command,**不**做 `from_value::<TunnelCommand>` 强类型反序列化(方案A 阶段1 已落地 commit 0fb5af5)→ miniapp `{cmd,args}` 反序列化在桥接层 `remote_bridge.rs:96 from_payload` 成功,非阻断。tunnel.rs:457 注释自证「不反序列化为 TunnelCommand 强类型」。
> - **P0-2 EventBus**:`src-tauri/src/lib.rs:128` `state.ai_event_bus.subscribe()`(tunnel subscriber 已接)+ `remote_bridge.rs:303/332/358/388` + `commands/ai/mod.rs:209/225` `publish_event` 跨端透传。**已双写接入**(todo line 760 commit 0fb5af5/612de4c emit 双写 chat19/audit12/guard3/stream_recv2/agentic18)。`event_bus.rs:53/115` 的 `#![allow(dead_code)]`+「骨架阶段」注释是**过时历史标注**(publish_event 现有调用方 remote_bridge/mod),非未接入。
> - **P0-3 桥接层**:`remote_bridge.rs` 完整实装(`handle_remote_command`:121 + `MiniCommand`:83 + `from_payload`:96 + match 路由 10 条 + `route_list/load_messages` publish_event)+ **已 wire** `lib.rs:150-154` `on_command = Arc::new(...handle_remote_command...)` + `:171` `.connect(&relay_url,&device_id,&token,on_command.clone())`。非占位/设计态。`remote_bridge.rs:40-42` 的 `#![allow(dead_code)]`+「零调用方」注释写于联调前,现 lib.rs 已调(过时标注,待清)。
**属实补充项(用户提出 + 主代核源码确认,追加 todo)**:
- [x] P1-F-260622 [P1🟡·✅2026-06-23] — **消息操作缺失(复制 / regenerate UI 入口)**`regenerate()`(`useAiChat.ts:520`)已实现但 chat 页无入口;复制消息功能全缺(桌面端有 hover 操作栏)。修:消息气泡加长按/点击操作(复制全文 / regenerate)。— `apps/df-miniapp/src/pages/chat/index.vue`
- [x] P1-G-260622 [P1🟡·体验·✅2026-06-23] — **Markdown 渲染简陋**`MdView.vue` 用 rich-text 无代码高亮 / 图片预览 / 链接拦截(MdView.vue 注释自认弱点);流式无节流(注释承认「长回复卡顿后续 P1 加 setTimeout 节流」)。修:评估 mp-html CLI local import `<node>` 子组件不渲染问题的修复,或换方案 + 流式 setTimeout 节流。— `apps/df-miniapp/src/components/MdView.vue`
- [x] P2-D-260622 [P2·健壮·✅2026-06-23核验已修] — **断连期间操作静默丢失**`wsClient.send()` 失败仅 console.warn + 返 false,但 `stop()/approve()/authorizeDir()` 忽略返回值 → 用户点「批准」UI 已乐观移除 pending 但命令未发出。修:这些方法检查 send 返回值,失败 toast 提示 + 不移除 pending。— `apps/df-miniapp/src/composables/useAiChat.ts`
- [x] P2-E-260622 [P2·健壮·✅2026-06-23] — **审批乐观移除无失败回退**。approve() 即 splice pending,若 device 端 tool_call_id 已被桌面端处理致审批失败,miniapp 已移除 UI 误导用户以为成功。修:approve 失败(超时 / AiError 事件)回填 pending。— `useAiChat.ts`
- [ ] P3-B-260622 [P3·真机前置] — **manifest appid 占位**`PLACEHOLDER` 无法真机调试,需微信公众平台注册的真 appid。— `apps/df-miniapp/src/manifest.json`
- [x] P3-C-260622 [P3·清理·✅2026-06-23 核验已清] — **mp-html 死依赖残留**。package.json 仍引 `mp-html@^2.5.2` 但 MdView 已改 rich-text 弃用。确认不回退 mp-html 则移除依赖(当前 `src/components/mp-html/` 预留保留,依 [[dead-code-reserve-keep]] 待回退决策)。— `apps/df-miniapp/package.json`**核验:package.json 已无 mp-html + grep 全 src 零引用,已清(陈旧 todo)**
- [x] P3-D-260622 [P3·体验·✅2026-06-23] — **token 用量 UI 缺失**`tokenUsage` 状态有但页面未展示。— `apps/df-miniapp/src/pages/chat/index.vue`
### ✅ 2026-06-23 miniapp 全功能审查加固(workflow wll7qabgr + 主代串行)
> 6 区域并行审查(chat/会话页/useAiChat/连接层/组件/工程;2 区域限流主代补审)。**自主实施**(逐批 vue-tsc 0 + build DONE 核验):
> - **F1** `AiConvStateChanged` idle/error 终态漏 clearWatchdog → 补 clearWatchdog(兑现注释承诺,防 timer 泄漏假超时)
> - **F2** regenerate() 无 generating 守卫 → 加守卫 + toast(防 send 静默 return 无反馈)
> - **F3** flushCurrentText 启发式回填错位 → 引入 currentAssistantMsgId 按 id 精确回填(治 retry/error/compressed 气泡穿插污染)
> - **F9** stop() 无终态兜底 + **chat 页无停止按钮** → stopping 标志乐观复位 + 忽略迟到 delta + 生成中显「停止」按钮(原 generating 时 send 禁用且无停止入口,用户无法中断生成)
> - **F21** 连接状态裸枚举字符串 → 中文文案 + 断开/重连中点状态栏手动重连
> - switch 加 default warn / ws.ts 二进制帧 warn / scheduleReconnect 加 MAX_RECONNECT_ATTEMPTS=20 + 手动重连复位计数
> - 删 pages/test 死页(uniapp alpha tap 排查探针,grep 零引用)
>
> **06-22 走查项核验销账**(源码已修,标 [x]):P2-B deviceOnline 复位 ✅ / P2-D 断连操作 send 返回值检查 ✅ / P1-B 握手结构化解析 ✅
>
> **06-22 项部分缓解**(alternative approach):P1-A 心跳 pong 闭环 → 当前用「任意入站消息」活性检测(HEARTBEAT_DEAD_FACTOR)替代 pong 往返,半开死连接已有兜底;pong 精确闭环见 MINIDEC-260623-06 ⏸️ / P1-C 冷启动恢复 → active conv id 已持久化 + 握手发 list_conversations;冷启动自动 load_messages 待核
>
> **剩余开放项 → 待决策**(见 [待决策.md](./待决策.md) df-miniapp 组):MINIDEC-260623-01 配置层(=旧 P1-D)/ 02 配对 / 06 心跳历史协议 / 07 regenerate+备份组件漂移。(03 重连续流 / 04 审批双源 / 05 会话页重命名 已实施,见下)。06-22 走查剩余 [ ]:P1-A(缓解)/P1-E(MaxRounds UI)/P1-F(复制+regenerate UI)/P1-G(Markdown 渲染)/P2-C(注释)/P2-E(审批回填)/P3-A/B/C/D。
### ✅ 2026-06-23 miniapp 用户需求批(req1/2/3 · 用户决策后实施)
> 用户 3 要求:(1) 会话重命名对齐桌面端 (2) 断网消息不丢失+从远端拉完整 (3) 审批断网重连恢复卡片状态。逐批 cargo check 0 + vue-tsc 0 + build DONE 核验。
> - **req1 会话重命名**:remote_bridge `rename_conversation` 路由(调 ai_conversation_rename + 推 AiConversationList 刷新)+ useAiChat `renameConversation`(乐观本地更新)+ conversations/index.vue 长按 `uni.showModal` 编辑。
> - **req2 断网不丢消息**:useAiChat `syncOnConnect` —— ws 'connected' 触发发 load_messages 拉当前会话完整历史(断网 missed 消息恢复 + 兼修冷启动空白 P1-C-260622)。
> - **req3 审批重连恢复**:remote_bridge `sync_pending` 路由(读 AiSession.pending_approvals 按 conv 过滤,重发 AiApprovalRequired/AiDirAuthRequired)+ useAiChat onStatus 'connected' 清 pendingApprovals + 发 sync_pending 重建 + handleEvent 同 id 去重防竞态。**审批卡改从 pendingApprovals 独立面板渲染**(与 messages 解耦,避重连 load_messages 替换 messages 的竞态;落地 MINIDEC-04 单一渲染源决策 a),内联工具卡保留状态徽标/参数/结果。
> - **P1-C-260622 冷启动恢复** 顺带修复(syncOnConnect 初始连接也拉历史 + active conv id 已持久化)。
### ✅ 2026-06-23 miniapp 🟡 收尾批(P1-E/F + P2-C/A + P3-D/C · 自主推进)
> 用户定「自用」后低风险收尾项一次性做完。逐项 vue-tsc 0 + build DONE 核验。
> - **P1-E MaxRounds 继续/停止**:useAiChat `maxRoundsActive` ref(AiMaxRoundsReached 置 true,continueLoop/stopLoop/send/switch/new 清)+ chat 「已达最大轮次」面板(继续一轮/停止按钮)。
> - **P1-F 重发 + 复制**:chat 输入栏「重发」按钮(调 regenerate,非生成中且有消息时显)+ 消息 @longpress 复制全文(uni.setClipboardData)。
> - **P3-D token 用量**:顶栏显 🪙 total(ai.tokenUsage)。
> - **P2-C 注释陈旧**:relay.ts 路由表 12→14(+ rename/sync_pending)+ remote_bridge.rs 表头补两行。
> - **P2-A messages 内存上限**:useAiChat watch trim MAX_STORE_MESSAGES=500(渲染另有 visibleMessages slice 200)。
> - **P3-C mp-html 死依赖**:核验 package.json 已无 + grep 全 src 零引用(陈旧 todo,无需改)。
> - **MINIDEC-07** 随 P1-F 选 a(regenerate 加 UI 不删)+ 备份组件保留预留,已落。
>
> **剩余 [ ](非阻塞)**:P1-A 心跳 pong(已缓解=MINIDEC-06 暂缓)/ P3-B manifest appid(真机前置,需注册)。(P1-G/P2-E/P3-A 见下 polish 批已落)
### ✅ 2026-06-23 miniapp polish 批(workflow wll377z2c 设计 → 串行实施)
> workflow 3 agent 并行设计(无限流)→ 汇总排序 → 主代串行实施,逐项 vue-tsc 0 + build DONE。
> - **P2-E 审批失败回填**(low):useAiChat `lastApprovedTc` ref —— approve(approved=true) 记录,device 返 AiError 回填 pendingApprovals + toast「审批操作未生效,请重试」让用户重试;AiApprovalResult/AiToolCallCompleted 确认 → 清;切会话/新建/断连 → 清。
> - **P1-G Markdown 渲染**(medium):新建 `utils/mdRenderer.ts` —— marked HTML 后处理注入 inline style(pre 深底等宽/code 橙色/a 蓝下划线/table border),规避 marked renderer v18 签名风险(rich-text 不认 class)。chat/index.vue renderMd + MdView.vue 接入。**链接跳转 rich-text 不可行**(<a> 节点事件屏蔽)降级展示+长按复制,跳转留 mp-html 后续。
> - **P3-A textarea 多行**(high):input→textarea auto-height(max-height 120px + .bar flex-end 对齐)。⚠️ **原生层级遮联想浮层风险待用户实测**(textarea 原生组件渲染在 WebView 之上,可能遮盖 .mention-pop);若遮挡严重,fallback 联想浮层改 cover-view(单独任务,本次未做)。
>
> **miniapp 自用阶段功能全部闭环**。剩 P1-A(暂缓)/P3-B(appid 真机前置,需你注册)。
**与主走查重复(用户项 = 已登记,跳过)**:用户 P1-5(配置硬编码)= P1-D / P1-6(MaxRounds UI)= P1-E / P2-11(deviceOnline 不复位)= P2-B / P2-9(R1 并发防护)= `remote_bridge.rs:27-33` `check_generating_reject` **已实现**(send_message 路由前置 generating 检查)。
---
### ✅ 2026-06-24 aichat 可靠性修复(消息重叠×5 + 授权弹窗卡死 + 工具执行心跳误报 · 诊断workflow→论证workflow)
> 用户「从历史消息找需求逐一比对」→ 捞出反复报告未根治的可靠性 bug。**诊断 workflow(4 agent 只读核验)** 定根因 → **论证 workflow(4 角度:正确性/回归/完整性/边界)** 验正确性+完整性 → 补 2 处(完整性高优+回归中优)。vue-tsc 0。
>
> **BUG-260624-01 消息重叠/堆叠根治**(用户 2d89921a×3/d056b5ad×2 共 5 次报告):
> - 根因:currentText 全局单例 + flushCurrentText 末尾盲写 + MessageList streamingBlocks(:69 实例级)跨轮/跨会话残留 + 渲染 isLastAi&&currentText 非 streaming 态也驱动。
> - 修复(4 处协同,治本非补丁):① useAiEvents `flushCurrentText` 回填后自清 currentText(消除调用方清空顺序依赖)② MessageList currentText watch 归零清 streamingBlocks+lastStreamText(新轮)③ 1011 v-if 加 streaming(堵非 streaming 残留)④ activeConversationId watch 切会话清 streamingBlocks+lastStreamText+cancelAnimationFrame(治 switchConversation/restoreGeneratingState 残留根因,论证完整性高优)。
>
> **BUG-260624-02 授权弹窗卡死根治**(用户 8e4944b7/b1f644a7「没审批直接死了」):
> - 根因:统一审批开关 `df-ai-unified-approval` 默认开 → path 审批走 ToolCard 内联,但同名工具≥2 分组默认折叠(group-hidden display:none)→ 审批卡不可见 → 5min 超时(APPROVAL_TIMEOUT_MS)静默 deny → 误显「用户拒绝」卡死。ToolCard 根 :5 动态拼 `ai-tool-card--'+tc.status` 已挂 pending_approval class(scrollToFirstPending 可命中)。
> - 修复:useAiEvents AiDirAuthRequired/AiApprovalRequired case 内 `emit('ai-pending-arrived')`;ToolCardList onMounted listen → scrollToFirstPending(自动展开折叠组+scroll,原仅手动徽标触发)+ 卸载竞态 disposed 标志(论证回归中优)。
>
> **诊断推翻的误判**(anti-contamination 印证):压缩「未降prompt」= 指标口径(accumulate_tokens 跨轮累计非单轮;压缩真省,is_active 白名单剔除 compressed)✅ / generating 状态机 = 已落地(RAII guard guard.rs + enum 视图 session_state + per_conv,原创建对话硬拦已删)✅。task #120 关闭。
>
> **BUG-260624-03 工具执行心跳误报根治**(用户 b1f644a7「一边 aichat 回复着内容,一边抛"工具已执行完成,后续回复中断"」):
> - 根因(论证 workflow 4 维度·7 条源码证据闭环):前端 watchdog 130s 无活跃事件→判断断流→抛错;后端 AiHeartbeat 30s 静默期 emit 让前端 reset,但**心跳只在 `stream_llm`(provider 读 chunk 的 select! 循环,stream_recv.rs:333)内,工具执行在 audit/mod.rs(stream_llm 外)无心跳**。单次工具执行>130s(bash 跑 cargo/测试、read 大文件、全盘 search)→ watchdog 到期→该 conv 仍 generatingConvs→守卫放行→前轮有 completed 工具即命中"工具已执行完成后续中断"文案;前一轮 delta 文本在屏 = "一边流一边抛"。**反复修未根治因都在前端守卫打补丁(B-260615-03 文案/BUG-260623-06 MaxRounds clear/TD-260621-01 残留 timer),没人补工具执行阶段的心跳缺口**。
> - 修复(A 根治 + B 兜底,论证质量 A 可发布):
> - **A 后端根治**(audit/mod.rs):新增 `execute_with_heartbeat` helper(execute 期间 30s interval emit AiHeartbeat,弃首 tick 对齐 stream_recv:174;**RAII HeartbeatGuard** drop 自动 stop+abort,panic unwind 也不泄漏 task),替换 trust_hits/low_risk 两处 `tools.execute()`。补工具执行阶段心跳缺口。
> - **B 前端兜底**(useAiStream onStreamTimeout):stillGenerating 守卫后扫 messages running 工具卡→resetStreamWatchdog 续等不抛错(防心跳丢失/老后端/审批后路径误杀)。审批路径(chat.rs:575/877)非盲区——已包 60s timeout<130s watchdog 互补治理(论证勘误维度3 误判)。
> - 验证:cargo check exit 0(RAII 编译过)+ vue-tsc 0。B 维度因网络错未跑,已自核验 ToolCard approving 计时器仅复位按钮 loading 非 running(running 由后端 AiToolCallCompleted 驱动),真死锁续等优于误报(用户可 stop),A 心跳覆盖正常执行。
>
> **BUG-260624-04 搜索工具误授权根治**(用户 b1f644a7「搜索代码也要授权,检查这样的会话搜索为什么需要授权」):
> - 根因:`grep` 工具被标 `RiskLevel::Medium`(tool_registry.rs:1682),而同类只读 `read_file`/`search_files`/`read_symbol` 均 Low。注释 line 1661 "risk:Med(读文件内容,授权目录内放行/外申请)" 把"读内容需 path_auth 授权"误塞 risk_level —— path_auth 是独立安全层(read_file Low 也走),与 risk_level(只读/写/删)混淆。默认 low 模式下 Medium 需审批 → 每次搜索弹审批。叠加 `tool_display_hint`(tool_registry:2116)无 grep 条目 → reason.rs:158 fallback default `"创建操作,请确认是否执行"` → 只读搜索显示"创建操作"误导。
> - 修复:`grep` Medium→**Low**(对齐只读同类)+ tool_display_hint 加 `"grep" => Some("搜索代码内容")`(文案准确)+ 注释更正。cargo check exit 0。
> - 全量核对:28 工具 risk_level 分级,**除 `restore_project`: High 过保守(恢复非破坏,对齐 create/update 该 Medium;但 High 安全方向不算错,未改)** 外全合理。
>
> **BUG-260624-05 压缩后每轮停止根治**(用户「上下文压缩后,后续所有对话都提示已压缩然后停止」):
> - 根因(诊断 workflow 4 维度·DB 铁证 top.1216.devflow 库 565 compressed/4246 active,ead0f7e5 seq=0 是 active 兜底摘要,12 会话前7条有 active system):
> - **#1(主因·无限循环)**:压缩摘要 `insert_at(0, ChatMessage::system(summary))`(agentic/mod.rs:923)→ status=None → is_active=true,落在可压缩区 [0..protect_start) → 每轮 `has_compressible_messages` 恒 true → 无限循环压缩。topic-switch system(DB 3eafdb85 三条堆 seq 0/1/2)同理。
> - **#2(次因·前端停止)**:压缩失败 emit AiError(useAiEvents:648 无条件 setStreaming(false))→ 前端误判生成结束(后端 loop 实际继续)。
> - 修复(P0 根治循环 + P1 前端停止):
> - **P0**(context.rs:803 `has_compressible_messages` 排除 system 角色,用 `!matches!(role, System)` 因 MessageRole 无 derive PartialEq):摘要/话题锚点是上下文锚点非压缩目标,排除后不再因 system 触发。compress_old_messages 不改(被调用时仍标旧 system 摘要 compressed 被新摘要替代,防堆积)。DB 自愈:排除 system 不限 status,旧 active 摘要也被排除,**无需 migration**。
> - **P1**(agentic/mod.rs:985 压缩失败不发 AiError):前端 AiError 无条件停流,降级 warn + build_for_request 裁剪兜底,loop 继续 stream_llm。
> - **纠正 workflow 方案 A 矛盾**:workflow 建议 is_active 排除 summarized,但会致 build_for_request 不送摘要进 LLM 丢历史;改用 has_compressible 排除 system(摘要仍 active 进 LLM)。
>
> **BUG-260625-01 grep 传单文件路径报「目录名称无效」根治**(用户「grep path 需要传目录不能传单文件·怎么总是失败」):
> - 根因(DB 铁证 ai_tool_executions fb549a0b):grep handler 直接 `grep_recursive(root)` → 内部 `read_dir(root)`,root 是单文件时报 os error 267「目录名称无效」。grep 命令行原生支持单文件,LLM 常传文件路径(64 次 grep 调用多次传文件),工具只认目录 →「总是失败」。
> - 修复(tool_registry.rs):提取 `grep_one_file`(从 grep_recursive 循环体搬移,continue→return Ok),grep_recursive 改调它(DRY 复用),handler 判 `root_meta.is_file()` → 单文件走 grep_one_file,is_dir → 递归。描述加「path 文件或目录均可」。
> - 验证:cargo test grep **13 passed**(9 个 grep_recursive 测试全过 = 提取零回归)+ audit 3 grep 测试。
>
> **BUG-260625-02 read_file search 模式显示「0 行」根治**(用户「读取 MessageList.vue 0 行·65.2KB·怎么总是失败」):
> - 根因(DB 铁证 ai_tool_executions cad747e9):read_file 带 `search` 参数返 `{matches:[{line,content}],total,has_more}` **无 content/lines/returned_lines**。ToolResultBody.vue read_file 分支只认 content/lines → 显示「0 行 · 65.2KB」+ 空 content 区。LLM 拿到 matches 能继续,但**用户看 UI 以为读取失败/空**。
> - 修复(ToolResultBody.vue + useToolCard.ts):read_file 分支识别 search 模式(parsed.search),header 显「『search』 命中 N 处」(复用 grepHitsN)+ 命中行渲染(复用 grep content 样式,read_file matches 仅 {line,content} 无 file);ToolResult 加 `search?: string` 字段。
> - 验证:vue-tsc EXIT 0。
> - 附:state.rs/tool_registry.rs test 的 AllowedDirs once 字段初始化是工作区被改掉的预存状态,本次恢复到 HEAD(once 功能早已提交),**未引入新改动**。
>
> **剩余低优 [ ](非阻断)**:
> - [x] ✅(2026-06-24·ebba973) BUG-260624-01 残留:① MessageList:503 scrollToFirstPending return 过早修复(遍历全实例调,各内部 querySelector 无 pending 则 no-op,治 pending 落后续实例滚不到)+ ② ToolCardList listener 加 props.toolCalls.some(pending) 过滤短路(降大列表 N 实例×M emit 的 DOM 查询)。**scrollToFirstPending 时间窗去抖评估后不做**:同 id 连发被上游守卫拦截(AiDirAuthRequired/AiApprovalRequired useAiEvents:359/472 some 去重 + AiToolCallStarted:427 findToolCall 守卫),不同 id 连发是多 pending 合理场景每次滚到首个合理,vue-tsc EXIT 0。
> - [ ] BUG-260624-02 残留:审批超时倒计时 UI(5min 无倒计时,ToolCard pending 态显 mm:ss 到期前 60s 变红)+ 核 detached 窗口 emit 作用域(分离窗口是否独立 startListener,否则 ai-pending-arrived 收不到)
> - [x] ✅(2026-06-24·ebba973) MessageList:503 scrollToFirstPending return 过早已修(遍历全实例调,各内部 querySelector 无 pending 则 no-op,治 pending 落后续实例滚不到)
> - [ ] F-09 根因:streaming/currentText 全局单例改 per-conv(根治多会话串扰,见 docs/待决策.md)
> - [x] ✅(2026-06-24 核验伪问题销账) CR-260622-01-P2-1 — **ChatMessage.timestamp 打戳"致时序错乱"核验为伪**。grep 全 src `.sort(` 零消息按 timestamp 排序:ai.ts:188 会话列表 updated_at / ConversationSidebar pinned / TopBar weight / MessageList:231 mention span start。**消息渲染顺序由 renderItems(store.state.messages 数组顺序)= DB list_by_conversation ORDER BY seq,不依赖 ChatMessage.timestamp**。timestamp 仅 formatRelative 展示(system 摘要显示插入时刻=压缩时刻,语义合理)。agent A 假设"前端按 timestamp 排序"不成立,非真实缺陷。
> - [ ] CR-260622-01-P2-2 [P2低优·评估降级] — **HTML/markdown/JSON 等非代码文件无 session 级缓存**。**2026-06-24 评估**:已有 `TOOL_RESULT_COMPRESS_ENABLED`(mod.rs:98/1029-1078 view-only 摘要,>2KB tool_result 压缩,LLM 视图摘要非全文回灌 prompt)部分缓解;read_symbol 治代码文件(主场景,降 24.4x)。非代码文件完整 session 缓存(path→hash+content+patch失效)设计复杂 + LLM patch 后重读确认行为不确定(缓存命中提示可能不够 LLM 仍重读),归 B 路线 prompt 策略(约束 patch 后不重读)更合适,工具侧完整缓存低优暂缓。
---
### ✅ 2026-06-25 generating 状态机双轨收口 + 自动压缩路径残留修复
- [x] ✅(2026-06-25) **Task#1 自动压缩成功路径残留**(BUG-260624-05 衍生)— **AiCompressed 单事件被自动压缩路径误用 → 桌面端每次发送误弹 toast + 误刷整会话**。治法:`AiCompressed``AiManualCompressed`(手动 IPC,3 处 emit 点,前端弹 toast)+ `AiAutoCompressed`(loop 自动,桌面静默仅复位 isCompressing 防按钮卡死,miniapp 仍插摘要气泡)。按语义拆事件而非按字段。— `src-tauri/src/commands/ai/mod.rs:207/212`
- [x] ✅(2026-06-25) **Task#2 双轨状态机收口**`generating: bool` 字段(顶层单例 + PerConvState)与 `CONV_STATE_ENABLED` 灰度开关双轨全部删除,`ConvState` enum(`conv_state.rs`,5 态)成唯一真相源:写侧经 `transition_to` 守卫(非法转换拒绝),`GeneratingGuard` new/reset/drop 无条件迁移 + emit;读侧 `can_accept_request()` 接入 chat 域 4 入口(ai_chat_send/ai_regenerate/ai_chat_edit/ai_is_generating),比 `is_active()` 更严谨(覆盖 Stopping 态竞态)。开关常量已删,代码内 `CONV_STATE_ENABLED off 回退分支` 注释为说明性遗留(实际不可触发,待清理)。详案见 [generating状态机加固-2026-06-15.md §收口记录](./02-架构设计/专项设计/generating状态机加固-2026-06-15.md)。— `src-tauri/src/commands/ai/agentic/{conv_state.rs,guard.rs}` + `commands/ai/{mod.rs,commands/chat.rs}`
---
### 🔍 2026-06-26 灵感模块诊断待办(合并推进·单任务)
> ✅ 全部修复完成2026-06-27 核验)
- [x] **IDEA-FIX-01 [P0🔴]****ideas 表无软删除**。已对标 tasks/projects 的软删除全套模板实施。
- [x] **IDEA-FIX-02 [P0🔴]****ideas 白名单含 id/created_at**。已对标 B-260616-16 移除。
- [x] **IDEA-FIX-03 [P0🔴]****priority 无值域校验**。已加 `parse::<i32>() ∈ 0..=3` 校验。
- [x] **IDEA-FIX-04 [P1🟡]****record_to_idea 有损转换**。已从 IdeaRecord 读真实 status/related_ids。
- [x] **IDEA-FIX-05 [P1🟡]****评分关键词硬编码**。已拆到独立 `scoring_keywords.rs` const 文件。
- [x] **IDEA-FIX-06 [P1🟡]****promote 补偿用 purge不可恢复**。已改为 soft_delete。
- [x] **IDEA-FIX-07 [P1🟡]****关联单向**。已实现双向同步(关联时补入对方,解绑时同步移除)。
- [x] **IDEA-FIX-08 [P1🟡]****创建表单无 tags 输入**。捕捉表单已加 tags 输入。
- [x] **IDEA-FIX-09 [P2🟠]****创建表单缺 priority/source**。捕捉模态框已补 priority 下拉和 source 输入。
- [~] **IDEA-FIX-10 [P2🟠]****前端 filter + 后端分页漏数据**。团队决策:`hot`/`pending` 保留前端 filter扩多值属性过度设计keyword/order_by 已下沉后端。
- [x] **IDEA-FIX-11 [P2🟠]****假雷达图**。i18n key 和 CSS class 已正名为 "score-bar"。