优化: 波5待办销账(F-15阶段1compress基础+F-01阶段6模型下拉)+CR-49登记

This commit is contained in:
2026-06-17 00:53:03 +08:00
parent 4c84fbc1b6
commit c191d122bd
2 changed files with 81 additions and 26 deletions

View File

@@ -102,7 +102,7 @@
- [x] ✅(波2·2026-06-16·w76d81dno+主代核查,vue-tsc,决策a逐条续发) UX-260616-05 [P1] — **打断按钮只停当前回复 + 队列保留续发(不清队列)**。用户需求:发送队列有消息时,打断按钮应只打断当前回复,并把队列中的多条消息一并发送给后续对话。现状:`stopChat`(useAiSend.ts:365-370) L368 `state.queue = []` **清空队列** → 用户生成中排队的消息随打断被**静默丢弃**。技术底座已就绪(已核后端):`stopChat``aiApi.stopChat`→后端 `stop_flag.load`(agentic.rs:178)→收尾 `save_conversation`(:185 已生成文本入库)+emit **`AiCompleted`**(agentic.rs:190注释明确「保证前端收事件时后端已可接下一条发送队列续发不被拒」)→前端 `ai-drain-queue``drainQueue`(useAiSend.ts:228) shift 队首续发。**最小修复(确定)**:删 stopChat L368 `state.queue=[]`,打断后 AiCompleted 自然触发 drainQueue 续发队首;当前未完成回复已入库(:185)保留为截断回复,无需额外处理。**决策点(待定)——「队列多条一并发送」语义****(a) 逐条续发**(删清队列即可,零改 drainQueueAiCompleted 链式触发每条保各条上下文独立vs **(b) 合并成一条消息**(队列多条 text 拼接送,需改 drainQueue 为 bulkDrain 或合并 text 调一次 sendMessage。**倾向 (a)**(逐条保上下文独立+零改链路;"一并"理解为「都送出不丢弃」非字面合并),**待用户确认**。— src/composables/ai/useAiSend.ts(stopChat:365 + drainQueue:228) + 后端 agentic.rs(:178/:190 stop 收尾 emit AiCompleted 已就绪)。 - [x] ✅(波2·2026-06-16·w76d81dno+主代核查,vue-tsc,决策a逐条续发) UX-260616-05 [P1] — **打断按钮只停当前回复 + 队列保留续发(不清队列)**。用户需求:发送队列有消息时,打断按钮应只打断当前回复,并把队列中的多条消息一并发送给后续对话。现状:`stopChat`(useAiSend.ts:365-370) L368 `state.queue = []` **清空队列** → 用户生成中排队的消息随打断被**静默丢弃**。技术底座已就绪(已核后端):`stopChat``aiApi.stopChat`→后端 `stop_flag.load`(agentic.rs:178)→收尾 `save_conversation`(:185 已生成文本入库)+emit **`AiCompleted`**(agentic.rs:190注释明确「保证前端收事件时后端已可接下一条发送队列续发不被拒」)→前端 `ai-drain-queue``drainQueue`(useAiSend.ts:228) shift 队首续发。**最小修复(确定)**:删 stopChat L368 `state.queue=[]`,打断后 AiCompleted 自然触发 drainQueue 续发队首;当前未完成回复已入库(:185)保留为截断回复,无需额外处理。**决策点(待定)——「队列多条一并发送」语义****(a) 逐条续发**(删清队列即可,零改 drainQueueAiCompleted 链式触发每条保各条上下文独立vs **(b) 合并成一条消息**(队列多条 text 拼接送,需改 drainQueue 为 bulkDrain 或合并 text 调一次 sendMessage。**倾向 (a)**(逐条保上下文独立+零改链路;"一并"理解为「都送出不丢弃」非字面合并),**待用户确认**。— src/composables/ai/useAiSend.ts(stopChat:365 + drainQueue:228) + 后端 agentic.rs(:178/:190 stop 收尾 emit AiCompleted 已就绪)。
- [x] ✅(波2·2026-06-16·w76d81dno+主代核查,vue-tsc,决策只编text) UX-260616-06 [P2] — **队列消息支持编辑**。用户需求:队列中的消息支持编辑。现状:队列 UI(AiChat.vue:511-516) 每项只有 × 删除(cancelQueued:353),无编辑入口;队列项结构 `{ text, skill?, enqueuedAt }`(stores/ai.ts:70)。**改动(纯前端低风险)**:队列项加「编辑」按钮 → 点击切 inline input单行 textarea→ 回车/失焦写回 `state.queue[idx].text`、ESC 取消;新增 `editQueued(index, newText)`(useAiSend.ts) splice 写回 + store 导出 + composable return。**决策点(待定)**skill 是否可编辑(**倾向只编 text**——skill 来自发送时技能联想,编辑态不暴露 skill 改简化交互。i18n 补 edit/save/cancel key。— src/components/AiChat.vue(:511-516 队列项 UI + .ai-queue-item CSS:2856) + src/composables/ai/useAiSend.ts(editQueued 新增) + stores/ai.ts + i18n。 - [x] ✅(波2·2026-06-16·w76d81dno+主代核查,vue-tsc,决策只编text) UX-260616-06 [P2] — **队列消息支持编辑**。用户需求:队列中的消息支持编辑。现状:队列 UI(AiChat.vue:511-516) 每项只有 × 删除(cancelQueued:353),无编辑入口;队列项结构 `{ text, skill?, enqueuedAt }`(stores/ai.ts:70)。**改动(纯前端低风险)**:队列项加「编辑」按钮 → 点击切 inline input单行 textarea→ 回车/失焦写回 `state.queue[idx].text`、ESC 取消;新增 `editQueued(index, newText)`(useAiSend.ts) splice 写回 + store 导出 + composable return。**决策点(待定)**skill 是否可编辑(**倾向只编 text**——skill 来自发送时技能联想,编辑态不暴露 skill 改简化交互。i18n 补 edit/save/cancel key。— src/components/AiChat.vue(:511-516 队列项 UI + .ai-queue-item CSS:2856) + src/composables/ai/useAiSend.ts(editQueued 新增) + stores/ai.ts + i18n。
- [x] ✅(波2·2026-06-16·w76d81dno+主代核查,vue-tsc,决策a插队,边界:stop_flag复位降级可接受) UX-260616-07 [P2/依赖 UX-05] — **队列消息支持立即发送**。用户需求:队列中的消息支持立即发送。现状:队列项只能 × 删除或等 drainQueue 串行轮到,无插队。**决策点(待定)——「立即发送」语义****(a) 插队=打断当前+立即发这条**splice 出该项→stopChat→sendMessage 该条;复用 UX-05 stop→AiCompleted→drain 链路,当前未完成回复走 stop 截断保留vs **(b) 移到队首等当前完成优先**splice 出→unshift 队首drainQueue 下一轮先发它,不打断当前)。**倾向 (a)**"立即"字面=马上发,且复用 UX-05 链路;但须注意 splice 后调 stopChat 已不清队列(UX-05),避免已发送项重复入队)。**待 UX-05 打断语义定后同批设计**(共享 stop+drain 链路)。— src/components/AiChat.vue(:511-516 队列项加「立即发送」按钮) + src/composables/ai/useAiSend.ts(sendQueuedNow 新增)。 - [x] ✅(波2·2026-06-16·w76d81dno+主代核查,vue-tsc,决策a插队,边界:stop_flag复位降级可接受) UX-260616-07 [P2/依赖 UX-05] — **队列消息支持立即发送**。用户需求:队列中的消息支持立即发送。现状:队列项只能 × 删除或等 drainQueue 串行轮到,无插队。**决策点(待定)——「立即发送」语义****(a) 插队=打断当前+立即发这条**splice 出该项→stopChat→sendMessage 该条;复用 UX-05 stop→AiCompleted→drain 链路,当前未完成回复走 stop 截断保留vs **(b) 移到队首等当前完成优先**splice 出→unshift 队首drainQueue 下一轮先发它,不打断当前)。**倾向 (a)**"立即"字面=马上发,且复用 UX-05 链路;但须注意 splice 后调 stopChat 已不清队列(UX-05),避免已发送项重复入队)。**待 UX-05 打断语义定后同批设计**(共享 stop+drain 链路)。— src/components/AiChat.vue(:511-516 队列项加「立即发送」按钮) + src/composables/ai/useAiSend.ts(sendQueuedNow 新增)。
- [ ] 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 剩余(compress_prompt+compress_via_llm+辅助方法)待波5阶段2 手动功能(2 IPC+前端)待波6。 - [ ] 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 手动功能(2 IPC+前端)待波6。
**核心问题**:① `ai_chat_clear` 全删 DB 消息不可回溯 ② `build_for_request` 超预算直接丢弃旧消息零保留(`agentic.rs:226` `_trimmed` 被忽略) ③ 无压缩能力。 **核心问题**:① `ai_chat_clear` 全删 DB 消息不可回溯 ② `build_for_request` 超预算直接丢弃旧消息零保留(`agentic.rs:226` `_trimmed` 被忽略) ③ 无压缩能力。
@@ -468,7 +468,7 @@
- [ ] 阶段3:厂商模型列表拉取 — df-ai 新建 model_fetch.rs + openai_compat/anthropic_compat 分派 + URL 拼接 + 噪音过滤 - [ ] 阶段3:厂商模型列表拉取 — df-ai 新建 model_fetch.rs + openai_compat/anthropic_compat 分派 + URL 拼接 + 噪音过滤
- [ ] 阶段4:路由器 — df-ai 新建 router.rs + TaskRequirements + select 逻辑 - [ ] 阶段4:路由器 — df-ai 新建 router.rs + TaskRequirements + select 逻辑
- [x] ✅(波3 IPC a26b86e + 波4调用点 2ddfea6) 阶段5:6 调用点接入 — agentic/title/knowledge_inject(2)/project(2)/df-ideas/df-nodes + IPC ai_fetch_models/ai_probe_model + router.rs select_model_id helper(池空兜底 default_model 行为不变) - [x] ✅(波3 IPC a26b86e + 波4调用点 2ddfea6) 阶段5:6 调用点接入 — agentic/title/knowledge_inject(2)/project(2)/df-ideas/df-nodes + IPC ai_fetch_models/ai_probe_model + router.rs select_model_id helper(池空兜底 default_model 行为不变)
- [ ] 阶段6:前端 — types.ts 类型对齐(✅波3) + Settings 模型池 UI(✅波3) + AiChat 模型下拉(波5待做) - [x] ✅(波5·4c84fbc+主代核查,cargo0err+vue-tsc0+df-ai107pass+compress3pass) 阶段6:前端 — types.ts 类型对齐(✅波3) + Settings 模型池 UI(✅波3) + AiChat 模型下拉+override穿透(✅波5·4c84fbc,override空/不在池落回路由零行为变化)
### P1 — Sprint 19 遗留 ### P1 — Sprint 19 遗留

View File

@@ -213,43 +213,98 @@
--- ---
### CR-260616-45 波1 F-01阶段3厂商模型列表拉取 + UX-01错误气泡Fatal区分 + UX-03工具卡按轮次收起 — 🟡 待审 ### CR-260616-45 波1 F-01阶段3厂商模型列表拉取 + UX-01错误气泡Fatal区分 + UX-03工具卡按轮次收起 — ✅ 已审(PASS)
- **范围**: 波1 workflow(wef0eqdwp,2 agent 并行不同域)。①**F-01 阶段3**(107b848):df-ai 新建 model_fetch.rs(545 行)+ lib.rs:7 注册 pub mod。`fetch_model_names(provider_type,base_url,api_key)->Result<Vec<String>>` 协议分派(openai_compat GET /v1/models Bearer / anthropic_compat x-api-key+anthropic-version 双 header / 其他报错)+ `build_models_url` 智能拼接(尾 /、/v1→/models、/v4、/models 原样、chat/completions→models、/api→/models、根 base→/v1/models)+ `is_non_chat_model` 噪音过滤(13 类:dall-e/midjourney/sd/imagen/tts/whisper/audio/speech/voice/realtime/transcribe/moderation/-search;**embedding 按 §5.4 保留**)+ `filter_chat_models` 去重去空 trim+ `ModelsList` 反序列化(data/models alias + id/name alias + serde default 空兜底)+ `fetch_and_probe` 探测组合(probe 纯函数 map collect)+ 错误映射(timeout→超时提示 / 401/403→鉴权失败 / 404→端点不存在 / 5xx→厂商异常,全含 provider_type+url)。②**UX-01**(bebf2ec):AiChat.vue `canRetry(msg)`(L1940,errorType auth/provider_config=Fatal 隐藏「重试」按钮)+ notRetryable 提示 span(L413)+ v-if="canRetry(msg)" 守卫(L415)。③**UX-03**(bebf2ec):AiChat.vue 新增 `watch(agentRound)`(L1684-1690,cur>prev 收起上一轮工具卡)+ `buildActiveToolIds` 复用(L1644)+ ToolCardList.vue 注释说明 CSS 动画**不做**(flex gap:8px 对零高 item 仍贡献 gap 致双倍间距;display:none 才移出流无 gap;高度 auto→0 不可纯 CSS transition 需 `<Transition>` JS 钩子,本轮野人 lite 不重构 ToolCard 单卡级折叠)。④i18n 4 文件(zh-CN/en × ai.ts/aiChat.ts)notRetryable 等新增 key。 - **复审结论(2026-06-17·CR-45 agent 亲跑 cargo+test+vue-tsc+独立 grep/read 核验)**: ✅ **PASS** — 🔴0 🟡0 ⚪2
- **维度**: ①build_models_url 7 分支完备(尾//v1/v4/models/chat-completions/api/根 base,断言无 /v1/v1/models 重复) ②噪音过滤 13 类 + embedding 保留边界(§5.4) ③反序列化多 alias 兜底(data/models+id/name+serde default 空对象不 panic) ④协议分派(openai_compat Bearer/anthropic_compat 双 header) ⑤fetch_and_probe probe 兜底不中断整体 ⑥错误映射友好提示含上下文 ⑦UX-01 canRetry Fatal 分类(auth/provider_config;**边界:404/参数错映射 unknown 仍可重试**,受 error_type 分类粒度限制,后端下批补 retryable 字段后精确化) ⑧UX-03 agentRound watch 触发时机 + CSS 不动决策合理性(flex gap) ⑨i18n 转义(新增 key 无 @/|/裸 {}) ⑩主代亲跑 `cargo test -p df-ai model_fetch` **24 passed** + `vue-tsc` exit 0。 - **验证**: `cargo check --workspace` EXIT 0(5 warning 全 pre-existing dead_code)/ `cargo test -p df-ai model_fetch` **24 passed** 全过/ `npx vue-tsc --noEmit` EXIT 0。
- **commit**: 107b848(F-01 阶段3) + bebf2ec(UX-01+UX-03) - **①build_models_url 7 分支完备 PASS**:model_fetch.rs:165-178 尾 trim/|v1|v4/|models/chat/completions/api/根 base 7 分支全覆盖;断言成立(无 /v1/v1/models 重复,trim 后单次 suffix 判断);测试 build_url_v1_suffix_appends_models:303 `assert!(!...contains("/v1/v1"))` 编译期证
- **主代独立核查**: ✅ 全过(model_fetch.rs 545 行真实存在 + lib.rs:7 `pub mod model_fetch` 注册 / cargo test model_fetch **24 passed** 亲跑 EXIT 0 / 前 120 行结构清晰:公共入口 fetch_model_names/fetch_and_probe + 协议分派 fetch_openai_compat/fetch_anthropic_compat / AiChat.vue canRetry L1940 + notRetryable L413 + v-if L415 / agentRound watch L1684-1690(cur<=prev return 早退) / ToolCardList.vue 注释 flex gap 说明 CSS 不动决策 / vue-tsc exit 0 亲跑 EXIT 0) - **②噪音过滤 13 类 + embedding 保留 PASS**:is_non_chat_model:196-219 dall-e/midjourney/sd/imagen/tts/whisper/audio/speech/voice/realtime/transcribe/moderation/-search 13 类;embedding_kept_per_design:410-414 单测证保留;注释 :194 明确按 §5.4 设计
- **审查 agent 待复审重点**: ①model_fetch URL 拼接 7 分支边界(尤其根 base→/v1/models 对 GLM v4/DeepSeek/通义根 base 是否正确;`-search` 子串 contains 匹配是否误伤含 -search 的合法 chat 模型名) ②Anthropic 响应格式 GLM 订阅端点同构性(serde 仅取 id 容忍额外字段,留阶段5 IPC 实测) ③fetch_and_probe 无分页(Anthropic has_more 忽略,超大列表>500 可能截断) ④embedding 保留出现在 chat 下拉是否需前端分组(阶段6 前端按 capabilities 含 Embedding 折叠) ⑤UX-01 404/参数错走 unknown 仍可重试(后端补 AiStreamRetry/AiError retryable 字段后精确化,下批 df-ai 串行) ⑥真实厂商 API 联调(401/403/404/超时/解析失败各分支)留阶段5 IPC `ai_fetch_models`(Settings 点「测试连接」触发)端到端验证 ⑦UX-03 CSS 不做是否需后续补 `<Transition>` 动画(P3 体验项) - **③反序列化多 alias 兜底 PASS**:ModelsList:248-253 serde(default) + data alias="models";ModelEntry:256-259 id alias="name";into_ids:263-269 filter_map 丢弃 None;parse_openai_format/parse_anthropic_format/parse_ollama_format/parse_missing_data_field_yields_empty/parse_entry_missing_id_skipped 5 单测全覆盖
- **④协议分派 PASS**:fetch_openai_compat:66-89 Bearer 鉴权 + /v1/models;fetch_anthropic_compat:92-119 x-api-key+anthropic-version 双 header + /v1/models;fetch_model_names:40-46 其他报 UnsupportedProvider;三路径完备。
- **⑤错误映射友好提示 PASS**:map_network_error:132-138 timeout→超时提示/其他→网络错误;map_status_error:141-151 401/403→鉴权失败/404→端点不存在/5xx→厂商异常;全含 provider_type+url 上下文。
- **⑥fetch_and_probe probe 兜底 PASS**:fetch_and_probe:52-59 探测为纯 CPU map collect,单模型探测失败不中断整体(probe 本身有兜底)。
- **⑦UX-01 canRetry Fatal 分类 PASS**:AiChat.vue:1994-1999 auth/provider_config=Fatal 隐藏重试;notRetryable 提示 L413;v-if L415 守卫;边界说明正确(404/参数错仍可重试,待后端 retryable 字段精确化)。
- **⑧UX-03 agentRound watch 触发时机 + CSS 不动决策 PASS**:watch agentRound:1738-1744 cur<=prev 早退,collapseAllToolLists(buildActiveToolIds(...));ToolCardList.vue:353-357 flex gap 原理说明明确,本轮不重构 JS 钩子决策合理。
- **⑨i18n 转义 PASS**:ai.ts/aiChat.ts zh-CN/en 8 key 全纯文本,无 @/|/裸 {} 特殊字符;notRetryable/queueEdit/queueSave/queueCancel/queueSendNow 双语对称。
- **⚪ CR-45-1(low,`-search` 子串匹配可能误伤)**:is_non_chat_model:218 `id.contains("-search")` 可能误伤含 `-search` 后缀的合法 chat 模型名(如厂商未来发布 `gpt-next-search-assistant` 模型实际支持 chat);当前单测覆盖 `text-davinci-search-001` 旧 GPT 搜索变种合法剔除,但 `contains` 子串匹配设计粒度粗。已知影响面小(主流厂商暂无含 `-search` 的 chat 模型),非阻断;后续可按需收紧为正则 `r#"-(search|search-[a-z]+)$"#` 或加入白名单例外。
- **⚪ CR-45-2(low,fetch_and_probe 无分页)**:fetch_anthropic_compat:92-119 调 `build_models_url``/v1/models`,Anthropic 响应含 `has_more` 字段但当前 serde 反序列化 ModelsList:248 未取 has_more(serde 仅取 id);超大列表>500 可能截断。当前阶段3 聚焦拉取主流模型,截断影响可控(头部 500 模型足够覆盖);非阻断,留阶段5 IPC 实测后按需补分页。
- **待修项回流 todo**: **无**(2 low 非阻断:子串匹配可收紧/分页可后续补)。
--- ---
### CR-260616-46 波2 F-01阶段4路由器 + UX-05/06/07队列三件套 — 🟡 待审 ### CR-260616-46 波2 F-01阶段4路由器 + UX-05/06/07队列三件套 — ✅ 已审(PASS)
- **范围**: 波2 workflow(w76d81dno,2 agent 并行不同域)。①**F-01 阶段4**(8dad15e):df-ai 新建 router.rs(329 行)+ lib.rs 注册 pub mod router。TaskRequirements 5 字段(modalities/needs_tool_use/min_intelligence/max_cost/estimated_context)+ ModelRouter 单元结构 + select 关联函数 `select<'a>(req,pool)->Option<&'a ModelConfig>` 7 步过滤链(enabled→模态全子集→needs_tool_use 含 ToolUse→intelligence>=min→max_cost<=→context_window>=→max_by_key (weight, Reverse(cost_tier)))。**设计偏离(已对齐任务规格)**:select 关联函数非 &self 方法(router.rs:9 注释注明);max_by_key 用 std::cmp::Reverse 取反(任务规格)非设计原文 -(as i32)。②**UX-05**(313459b):useAiSend.ts stopChat 删 state.queue=[](决策 a 逐条续发,后端 agentic.rs:190 AiCompleted 已保 drainQueue 续发)。③**UX-06**(313459b):editQueued(index,newText) 边界检查+trim+直接赋值 text(决策只编 text)+ AiChat.vue 队列项 inline input(editingQueueIdx/editingQueueText ref)+ 编辑/保存(@mousedown.prevent 防 blur)/取消按钮。④**UX-07**(313459b):sendQueuedNow(index) splice 先→stopChat→sendMessage 顺序关键(splice 前置避重复发,复用 UX-05 stop 链路)+ AiChat.vue 立即发按钮。⑤i18n 4 key(queueEdit/Save/Cancel/SendNow)。 - **复审结论(2026-06-17·审查 agent 亲跑 cargo+vue-tsc,87s)**: ✅ **PASS** — 🔴0 🟡0 ⚪1
- **维度**: ①select 7 步过滤链严格对齐设计 §6.1 L469-478 ②Reverse(cost_tier) 取反正确性(Free 胜 High,CostTier Ord Free<Low<Medium<High) ③max_by_key 元组排序(weight 优先,同 weight Reverse(cost) 次序) ④TaskRequirements 5 字段完备 ⑤13 单测覆盖(每步独立 + 组合 all_dimensions_match_picks_best 5 候选参差) ⑥UX-05 决策 a 落地(删 queue=[] 零改 drainQueue,后端就绪核验) ⑦UX-06 editQueued 边界(越界/空白 no-op)+ inline input 三路保存一致(回车/blur/@mousedown.prevent) ⑧UX-07 sendQueuedNow splice 前置顺序(避重复发)+ 决策 a 复用 stop 链路 ⑨i18n 转义(4 key 纯文本无 @/|/裸 {}) ⑩主代亲跑 cargo test df-ai router:: **13 passed** + vue-tsc exit 0。 - **验证**: `cargo check --workspace` EXIT 0(5 warning 全 pre-existing dead_code)/ `cargo test -p df-ai router::` **15 passed**(13 原+2 新)/ `npx vue-tsc --noEmit` EXIT 0。
- **commit**: 8dad15e(F-01 阶段4) + 313459b(UX 队列三件套) - **①select 7 步过滤链 PASS**:router.rs:57-66 严格对齐设计 §6.1 L470-478(enabled→模态全子集 L60→needs_tool_use 含 ToolUse L61→intelligence>=min L62→max_cost<= L63→context_window>= L64→max_by_key L65)。单测 full_dimensions_match_picks_best:L310-360 5 候选参差验证全链
- **主代独立核查**: ✅ 全过(router.rs 329 行真实 + select 7 步 L55-63 严格对齐 §6.1 + Reverse(cost_tier) L62 + 关联函数 L54 + 13 单测 model()/req() 辅助 L71-95 / cargo test router:: **13 passed** 亲跑 EXIT 0 / useAiSend editQueued 边界+trim / sendQueuedNow splice→stop→send 顺序 / stopChat 删 queue=[] 决策 a / AiChat 队列 UI editingQueueIdx L1567 inline+4 按钮 L529-538+CSS L2977 / vue-tsc exit 0 亲跑 EXIT 0) - **②Reverse(cost_tier) 取反正确性 PASS**:router.rs:11 `use std::cmp::Reverse`+L65 `max_by_key(|m| (m.weight, Reverse(m.cost_tier)))`.CostTier Ord Free<Low<Medium<High,Reverse 让小值变"大"选便宜(设计原文 -(as i32) 等价但更 idiomatic)。单测 same_weight_picks_cheaper_cost_tier:L288-307 验证 Free 胜 High
- **审查 agent 待复审重点**: ①select 关联函数 vs &self 方法偏离是否需对齐设计文档(任务规格允许,router.rs:9 注明;阶段5 调用点用 ModelRouter::select(&req,&pool) 适配) ②Reverse(cost_tier) 与设计原文 -(as i32) 行为等价性(同 weight 选便宜,CostTier 4 值枚举 Reverse 安全) ③max_by_key 元组 (weight, Reverse(cost)) 同 weight 同 cost 时返回哪个(Rust max_by_key 返回最后最大,不影响路由正确性) ④UX-05 行为变更(打断不清队列)用户感知验证(后端 AiCompleted 就绪,前端实测 tauri dev:打断后队列续发) ⑤UX-07 sendQueuedNow 边界:stop_flag 复位速度,sendMessage 时后端仍 generating 走入队降级(可接受非阻塞) ⑥UX-06 inline input @mousedown.prevent 防 blur 三路保存路径覆盖(回车/blur/按钮) ⑦三件套共享 useAiSend/AiChat 队列域,无越界改后端 .rs - **③select_model_id helper PASS**:router.rs:79-81 纯函数 `select_model_id(req,pool)->Option<String>` 调 select 映射 model_id。单测 select_model_id_*:L117-128 池空/命中全过
- **④关联函数 vs &self 方法偏离 PASS**:router.rs:9 注释注明「对齐任务规格,非设计文档的 &self 方法」。关联函数 `ModelRouter::select(&req,&pool)`(L57)适配阶段5 调用点 `ModelRouter::select(&req,&pool)`,零行为差异(无状态单元结构,无需 self)。
- **⑤pub use re-export PASS**:router.rs:16 `pub use df_ai_core::model::{Capability,CostTier,IntelligenceTier,Modality,ModelConfig}`。阶段5 调用点经 `df_ai::router::{...}` 直接 import 维度枚举构造 TaskRequirements,零跨 crate 路径冗长。
- **⑥UX-05 决策 a 落地 PASS**:useAiSend.ts:397-404 stopChat 删 `state.queue=[]` 决策 a(逐条续发)。后端 agentic.rs:209 注释「保证前端收事件时后端已可接下一条,队列续发不被拒绝」+drainQueue(useAiSend.ts:228-232)由 AiCompleted 触发链式续发。
- **⑦UX-06 editQueued PASS**:useAiSend.ts:370-375 边界(index 越界/空白 no-op)+trim+直接赋值 text。AiChat.vue:1567 editingQueueIdx/1569 editingQueueText ref+L519-531 inline input 三路保存一致(回车/blur/@mousedown.prevent 防 blur)。
- **⑧UX-07 sendQueuedNow 顺序 PASS**:useAiSend.ts:387-392 splice 先→stopChat→sendMessage 顺序关键(splice 前置避重复发,stopChat UX-05 后已不清队列故 splice 必须前置)。复用 UX-05 stop 链路(agentic.rs:209 AiCompleted 就绪核验)。
- **⑨i18n 双语 PASS**:queueEdit/Save/Cancel/SendNow 4 key 纯文本无特殊字符,zh-CN/en aiChat.ts:L68-71 对称。
- **⚪ low 清单**:①router.rs:77 select_model_id 注释「池空/无匹配→None→兜底 default_model」。阶段5 调用点 unwrap_or(default_model) 确认行为不变,当前测试见 CR-48 调用点接入验证。
- **待修项回流 todo**: **无**(9 重点全 PASS,1 low 注释清晰非阻断)。
--- ---
### CR-260616-47 波3 F-15上下文基础(is_active白名单+push token) + F-01模型拉取IPC+Settings UI — 🟡 待审 ### CR-260616-47 波3 F-15上下文基础(is_active白名单+push token) + F-01模型拉取IPC+Settings UI — ✅ 已审(PASS)
- **范围**: 波3 workflow(wvxn5w1pf,2 agent 并行不同域)。①**F-15 阶段1 基础**(8a142c2):df-ai-core/provider.rs:82 is_active 改正面白名单 `matches!(self.status.as_deref(), None | Some("active"))`(旧反面排除 !matches truncated,零行为变化:None/active=true truncated=false 等价;新状态 archived_segment/compressed 阶段2 引入自动隔离)+ df-ai/context.rs:179 push token 修正 `if message.is_active() { self.history_tokens += tokens }`(!active 仍 push 到 messages 全量保留持久化,仅不计 token,修复 restore_from_messages 后 token 虚高致 build_for_request 误判超预算)+ 2 单测(is_active_whitelist 5 状态 / push_token_only_active 直接 push+restore 双路径)。②**F-01 阶段5 IPC + 阶段6 前端**(a26b86e):src-tauri commands.rs ai_fetch_models(provider_id)(FR-S1 resolve_provider_secret 内存解析 api_key 不外泄 + normalize_provider_type anthropic→anthropic_compat + fetch_and_probe + update_full 写回 model_configs)+ ai_probe_model(model_id)(probe 纯 CPU)+ lib.rs 注册 + types.ts ModelConfig+4 维度枚举(snake_case 对齐 df-ai-core model.rs serde)+ AiProviderRecord.model_configs + ai.ts fetchModels/probeModel + Settings.vue 拉取按钮+模型列表 UI(4 维度标签+enabled toggle+weight)+ fetchModels 错误分支 toast(超时/鉴权/端点不存在)+ i18n settings.ts 18+ key。**决策**:新建态 provider 无 id,采用「先保存落库拿 id 再 fetch」(非临时 IPC,DRY 不重写密钥解析逻辑)。③**todo 登记**(5b7f342):F-260617-01 patch_file 三模式增强需求。 - **复审结论(2026-06-17·审查 agent 亲跑 cargo+test+vue-tsc,62s)**: ✅ **PASS** — 🔴0 🟡0 ⚪2
- **维度**: ①is_active 白名单零行为变化(None/active/truncated 三档全等) ②push token 仅 active 计入(!active 全量保留持久化,all_messages_clone 不变) ③restore_from_messages 自动同步(调 push) ④2 单测覆盖(5 状态+直接/restore 双路径) ⑤ai_fetch_models FR-S1 闭环(api_key 仅 resolve_provider_secret 内存,不进返回值/日志/错误;ModelConfig 无 api_key 字段) ⑥normalize_provider_type 归一(anthropic→anthropic_compat 对齐 fetch_and_probe) ⑦fetch_and_probe + update_full 写回 ⑧ai_probe_model 纯 CPU probe ⑨types.ts 4 维度枚举 snake_case 对齐 df-ai-core serde ⑩Settings 拉取 UI(4 维度标签+enabled/weight 本地态)+错误分支 toast ⑪i18n 转义(命名插值 {count}/{msg} 合法) ⑫主代亲跑 cargo test df-ai-core is_active **1 passed** + df-ai context:: **14 passed**(零回归)+ workspace check EXIT 0 + vue-tsc exit 0。 - **验证**: `cargo check --workspace` EXIT 0(5 warning 全 pre-existing dead_code)/ `cargo test -p df-ai-core is_active` **1 passed** / `cargo test -p df-ai context::` **14 passed**(零回归)/ `npx vue-tsc --noEmit` EXIT 0。
- **commit**: 8a142c2(F-15 基础) + a26b86e(F-01 IPC+前端) + 5b7f342(todo F-260617-01 登记) - **①is_active 白名单零行为变化 PASS**:provider.rs:85-86 `matches!(self.status.as_deref(), None | Some("active"))` 与旧 `!matches!(self.status.as_deref(), Some("truncated"))` 等价(当前仅 None/active/truncated 三态)。**核验 truncated 时序**:context.rs:484-496 truncate_after_user_message 在 push 之后标记(:496 `t.message.status = Some("truncated")`),push 时刻 is_active 恒 true,history_tokens 累加不变,零行为变化证成
- **主代独立核查**: ✅ 全过(provider.rs:82 is_active matches!(None|Some("active")) + 注释零行为变化 / context.rs:179 push if is_active 计 token / 2 单测 / commands.rs ai_fetch_models FR-S1 resolve_provider_secret + normalize + fetch_and_probe + update_full / ai_probe_model probe / types.ts ModelConfig+4 枚举 snake_case / Settings 拉取 UI / **workspace check EXIT 0 亲跑确认无 error(B agent 报 project.rs:9 E0433 error 经核验为幻觉/抓中间态,实际 L9 是 use df_core::types::new_id,workspace 5 warning 全 pre-existing 无 error)** / cargo test is_active 1 passed + context 14 passed 亲跑 / vue-tsc exit 0 亲跑) - **②push token 修正 PASS**:context.rs:179-185 `if message.is_active() { self.history_tokens += tokens }`。!active 消息仍 push 到 self.messages 全量保留(:186 `self.messages.push(...)`),仅不计 token。修复 restore_from_messages 后 token 虚高致 build_for_request 误判超预算
- **审查 agent 待复审重点**: ①is_active 白名单零行为变化论证(truncated 是 push 后 truncate_after_user_message 二次标记,push 时刻 is_active 恒 true,history_tokens 累加不变) ②push token 修正 restore_from_messages 路径(mgr2 验证 active/archived/compressed 三条 restore 后 token 只含 active) ③ai_fetch_models FR-S1 闭环(resolve_provider_secret 不外泄,错误信息 err_str 是否含密钥片段) ④normalize_provider_type 双命名归一(后续若统一删 normalize) ⑤Settings enabled/weight 本地态非持久化(modelListHint 文案明示,持久化需 ai_save_provider 扩展或新 IPC,阶段6 后续) ⑥ai_probe_model UI 未接(无单模型探测按钮,留阶段6 AiChat 模型下拉) ⑦ModelConfig 5 CSS 类未润色(功能正常视觉待润色) ⑧types.ts 枚举与 df-ai-core serde rename_all snake_case 严格对齐核验 - **③restore_from_messages 路径 PASS**:context.rs:420-425 遍历调 push,token 同步仅 active。单测 push_token_only_active:860-910 验证直接 push+restore 双路径,active/archived/compressed 三条 restore 后 token 只含 active 一条(:902-910 断言通过)
- **④ai_fetch_models FR-S1 闭环 PASS**:commands.rs:872 `df_storage::secret::resolve_provider_secret(&provider)` 内存解析,不进日志/返回值。ModelConfig(df-ai-core/model.rs:87-125)无 api_key 字段,:902 返回值 `Vec<ModelConfig>` 天然不含密钥。
- **⑤normalize_provider_type 归一 PASS**:commands.rs:841-846 anthropic→anthropic_compat 归一(:873 调用)。对齐 model_fetch.rs:40-42 fetch_and_probe 协议分派,双命名收敛。
- **⑥fetch_and_probe + update_full 写回 PASS**:commands.rs:875 `fetch_and_probe` 调用 + :883-887 `update_full` 写回 model_configs + updated_at。
- **⑦ai_probe_model 纯 CPU probe PASS**:commands.rs:897-902 调 `df_ai::model_probe::probe(&model_id)`,无网络。
- **⑧types.ts 4 维度枚举 snake_case 对齐 PASS**:types.ts:200-206 Modality/Capability/CostTier/IntelligenceTier 字面量(df-ai-core/model.rs:20-72 serde `rename_all = "snake_case"` 严格对齐)。
- **⑨Settings 拉取 UI PASS**:Settings.vue:95 拉取按钮(:96 disabled 非新建态提示先保存)+:102-103 模型列表展示(4 维度标签+enabled toggle+weight input)+:519-536 fetchModels 函数错误分支 toast(超时/鉴权/404/厂商异常)。
- **⑩i18n 双语 PASS**:zh-CN/en settings.ts:33-37 testConnection/fetchingModels/fetchHintSaveFirst/labelWeight/modelListTitle 对称,无特殊字符。
- **⚪ CR-47-1(low,Settings enabled/weight 本地态非持久化)**:Settings.vue:525 `providerForm.models = models.map(...)` 仅本地会话态,未存回 model_configs。文案 "下次「测试连接」会以最新探测结果覆盖"(:516-518 注释)明示,阶段6 后续持久化另见 issues。非阻断(本批目标拉取展示 UI,本地编辑态可接受)。
- **⚪ CR-47-2(low,ai_probe_model UI 未接)**:无单模型探测按钮,留阶段6 AiChat 模型下拉。非阻断(probe 纯 CPU,按需补 UI)。
- **待修项回流 todo**: **无**(10 重点全 PASS,2 low UI 增强/持久化非阻断)。
--- ---
### CR-260616-48 波4 F-01阶段5调用点接入(select_model_id helper+6点) + F-260617-01 patch_file三模式编辑 — 🟡 待审 ### CR-260616-48 波4 F-01阶段5调用点接入(select_model_id helper+6点) + F-260617-01 patch_file三模式编辑 — ✅ 已审(PASS)
- **范围**: 波4 workflow(wd5a1dgqz,2 agent 并行不同域)。①**F-01 阶段5 调用点接入**(2ddfea6):router.rs L76 加 `select_model_id(req,pool)->Option<String>` 纯函数 helper(调 ModelRouter::select + map model_id)+ L16 `pub use df_ai_core::model::{Capability,CostTier,IntelligenceTier,Modality,ModelConfig}` re-export(src-tauri/df-ideas/df-nodes 经 df_ai::router::{...} 直接 import 维度枚举)+ 6 调用点统一兜底 `select_model_id(&req,&provider.model_configs).unwrap_or(default_model)`:agentic.rs 主对话(TaskRequirements{Text,tool_use:true,Standard},resolved_model 替 default_model 进 CompletionRequest+消息元数据+save_conversation 审计)+ title.rs(Lite,max_cost:Low)+ knowledge_inject.rs 提炼(Standard)+嵌入(Lite,resolve_embed_provider 对 embedding provider 池路由,池空兜底 config.embedding_model)+ project.rs 项目扫描 2 点(Standard)+ df-ideas adversarial.rs(model_pool 字段+with_pool 构造+evaluate_with_llm 路由,池空 unwrap_or_default 留空)+ df-nodes ai_node.rs(ResolvedProvider.model_pool 字段+parse_params config.model 显式优先否则路由 Standard+tool_use)+ df-ideas Cargo.toml 加 df-ai 依赖 + idea.rs build_default_provider 返 (provider,pc.model_configs) 元组透传池。②**F-260617-01 patch_file 三模式**(2446d1b):tool_registry.rs 新增 apply_line_range(content,start,end,new_text)(1-based 含首尾 splice,越界 Err)+ resolve_anchor_to_lines(content,start,end)(子串大小写敏感匹配找含 start 首行→其后第一个含 end 行)+ 三模式互斥运行时校验(has_old_text/has_replace_lines/has_anchor 计数 0=缺定位/>1=冲突)+ replace_lines/anchor 嵌套 schema 手工拼 JSON Schema + 共用底层(expected_hash mtime+size 指纹三模式通用+FILE_LOCKS+.bak 备份+原子 tmp→rename+generate_diff)+ 16 单测(apply_line_range 8+resolve_anchor_to_lines 8+e2e 1)。 - **复审结论(2026-06-17·CR-48 agent 亲跑 cargo+test+vue-tsc,342s)**: ✅ **PASS** — 🔴0 🟡0 ⚪2
- **维度**: ①select_model_id helper 纯函数正确性(调 select map model_id)+ pub use re-export 可见性(6 调用点 import df_ai::router::{Modality,IntelligenceTier,CostTier}) ②6 调用点兜底行为不变(池空/无匹配→None→unwrap_or default_model,与接入前一致) ③agentic served-model 审计字段统一改 resolved_model(行为变更语义更正确:记实际服务模型;池空时 ==default_model 零差异) ④knowledge_inject 嵌入降级 needs_tool_use=false 宽松过滤(TaskRequirements 无 capabilities 字段,设计偏离;embedding provider 池通常即嵌入模型实践中命中) ⑤df-ideas 新增 df-ai 重依赖(reqwest/eventsource-stream,tree-shaking 编译期不链入 HTTP 代码,仅引用 router 符号) ⑥df-nodes ResolvedProvider.model_pool 字段(record 路径填 record.model_configs.clone,老明文路径填空)+ config.model 显式优先级 ⑦patch_file 三模式互斥运行时校验完备(0/>1 两种)+ expected_hash mtime+size 非 sha256(read_file 返回 file_hash)三模式通用 ⑧apply_line_range trailing newline 语义(尾换行不误删)+ resolve_anchor 子串行内命中(start==end 同行/first-end-after-start/大小写敏感) ⑨16 单测覆盖三模式+互斥+边界 ⑩主代亲跑 cargo check --workspace EXIT 0 + cargo test df-ai router **15 passed**(13 原+2 新)+ df-ideas **20 passed** + df-nodes **79 passed** + devflow tool_registry **76 passed**(三模式单测全过) - **验证**: `cargo check --workspace` EXIT 0(5 warning 全 pre-existing dead_code)/ `cargo test -p df-ai router` **15 passed**(2 新增:select_model_id_empty_pool_returns_none/select_model_id_hit_returns_model_id)/ `cargo test -p df-nodes --lib` **79 passed; 1 ignored**/ `cargo test -p df-ideas adversarial` **12 passed**(含 a12 新增 llm_mock_fenced_not_at_start)/ `cargo test -p devflow tool_registry` **24 passed**(16 三模式单测)/ `npx vue-tsc --noEmit` EXIT 0
- **commit**: 2ddfea6(F-01 阶段5 调用点) + 2446d1b(patch_file 三模式) - **①select_model_id helper 纯函数正确性 PASS**:router.rs:79-81 纯函数调用 ModelRouter::select + map model_id,单测:117-127 覆盖池空 None/命中 model_id 返回字符串,编译期证类型匹配
- **主代独立核查**: ✅ 全过(router.rs L16 pub use df_ai_core re-export(初 Read 误判私有 use,grep L16 确认 pub use)+L76 select_model_id helper / 6 调用点 import df_ai::router::{select_model_id,Modality,IntelligenceTier,...} 形态正确(agentic:19/project:11/title:11/knowledge_inject:12/ai_node:17/adversarial:165 全限定) / **cargo check --workspace EXIT 0 亲跑(5 warning 全 pre-existing dead_code 无 error)**(B agent 报 select_model_id E0425/Modality E0603 经核验为攒批中间态 A router.rs in-flight 时未定义,review-batching-worktree-transient 教训) / cargo test df-ai router **15 passed**(select_model_id_empty_pool_returns_none/select_model_id_hit_returns_model_id 新增) / df-ideas **20 passed** 零回归 / df-nodes **79 passed,1 ignored** 零回归 / devflow tool_registry **76 passed**(apply_line_range_*+resolve_anchor_*+e2e+互斥) / tool_registry apply_line_range L133+resolve_anchor_to_lines L182+互斥校验 L935+嵌套 schema L892/L901+共用底层 expected_hash/FILE_LOCKS/.bak) - **②pub use re-export 可见性 PASS**:router.rs:16 `pub use df_ai_core::model::{Capability,CostTier,IntelligenceTier,Modality,ModelConfig}`;6 调用点全经 `df_ai::router::{...}` 全限定导入(agentic.rs:19/project.rs:11/title.rs:11/knowledge_inject.rs:12/ai_node.rs:17/adversarial.rs:165),编译期证零断裂
- **审查 agent 待复审重点**:agentic served-model 审计字段改 resolved_model 行为变更(池空==default_model 零差异核验,池非空命中才是路由目标) ②knowledge_inject 嵌入 needs_tool_use=false 降级(如需严格 Embedding 过滤需扩 TaskRequirements capabilities 字段+router 加过滤步,行为不变优先本批未做) ③df-ideas 新增 df-ai 重依赖(df-ideas 原仅 df-ai-core,现 reqwest 等;若需严格轻量可将 router 下沉 df-ai-core) ④patch_file expected_hash 非 sha256(mtime+size 指纹,三模式通用语义与 old_text 模式一致) ⑤三模式互斥运行时校验(schema 层 object_schema 无法静态互斥,运行时 has_* 计数兜底) ⑥agentic 主对话含图 Vision 检测未实现(ChatMessage 纯文本无图像 Part,modalities 仅 [Text],多模态类型接入后追加 TODO) ⑦build_provider/诊断日志仍用 default_model(provider trait 不暴露改 default_model,CompletionRequest.model 走路由 provider impl 适配) ⑧实测类待用户(tauri dev 调用点端到端:Settings 拉取模型→池非空→主对话/标题/扫描/灵感/ai_node 各走路由) - **③6 调用点兜底行为不变 PASS**:agentic.rs:175-176 `select_model_id(...).unwrap_or_else(|| provider_config.default_model.clone())` 池空/无匹配→None→兜底 default_model;title.rs:86-87/project.rs:539-540/knowledge_inject.rs:350-351/ai_node.rs:214/adversarial.rs:165 同模式;池空时 resolved_model==default_model 零差异
- **④agentic resolved_model 审计字段统一 PASS**:agentic.rs:288/410/414/419/465/469 原用 default_model 处统一改 resolved_model,语义更正确(记实际服务模型);池空时 resolved_model==default_model 无行为变更。
- **⑤knowledge_inject 嵌入降级 needs_tool_use=false PASS**:knowledge_inject.rs:58-65 TaskRequirements(needs_tool_use=false,min_intelligence=Lite),宽松过滤允许嵌入模型入选;TaskRequirements 无 capabilities 字段设计偏离,embedding provider 池通常即嵌入模型实践中命中,可接受。
- **⑥df-ideas 新增 df-ai 重依赖 PASS**:df-ideas/Cargo.toml:12 df-ai 依赖引入;注释:9-11 说明 router 模块仅依赖 df-ai-core::model,编译期 tree-shaking 零 HTTP 代码链入(仅引用符号),实测 cargo check EXIT 0 证无链接错误;后续若严格轻量可将 router 下沉 df-ai-core 非阻断。
- **⑦df-nodes ResolvedProvider.model_pool 字段 PASS**:ai_node.rs:54 model_pool 字段+ :205 record 路径填 record.model_configs.clone + :143 老明文路径填空池;config.model 显式优先(:197-203 有则用 model_id 否则路由),单测覆盖。
- **⑧patch_file 三模式互斥运行时校验 PASS**:tool_registry.rs:932-940 has_old_text/has_replace_lines/has_anchor 计数,0=缺定位 bail/:937 >1=冲突 bail/:939,两边界完备;schema 层 object_schema 无法静态互斥,运行时兜底合理。
- **⑨patch_file expected_hash mtime+size 非 sha256 PASS**:tool_registry.rs:971-981 `format!("{}_{}", modified.unwrap_or(0), file_meta.len())` 指纹(mtime+size),三模式通用,语义与 old_text 模式一致(read_file 返回 size 非 hash,注释:141 与 read_file 行计数一致),设计正确。
- **⑩apply_line_range trailing newline 语义 PASS**:tool_registry.rs:133-152 单测:1722-1729 尾换行保留(`\n` 结尾文件替换后仍以 `\n` 结尾),splice 循环 lines() 后单独处理 trailing_newline,边界正确。
- **⑪resolve_anchor 子串行内命中 PASS**:tool_registry.rs:182-206 子串大小写敏感匹配+start 后第一个含 end 行;单测:1783-1789 行内命中 / 1794-1798 start==end 同行 / 1803-1808 first-end-after-start,覆盖完备。
- **⑫16 单测覆盖 PASS**:tool_registry.rs:1701-1853 单测 apply_line_range 8 + resolve_anchor_to_lines 8 + e2e 1,亲跑 cargo test 24 passed 全过。
- **⚪ CR-48-1(low,agentic 主对话含图 Vision 检测未实现)**:agentic.rs:169 modalities 仅 `[Modality::Text]`,ChatMessage 纯文本无图像 Part,多模态类型接入后需检测 Part/Image 追加 Vision,当前非阻断(设计文档已标注:166-167)。
- **⚪ CR-48-2(low,build_provider/诊断日志仍用 default_model)**:provider trait 不暴露改 default_model,CompletionRequest.model 走路由 provider impl 适配(OpenAICompatProvider::convert_request:295-296 req.model.is_empty() 回退 default_model),诊断日志仍用 default_model 字段名,非阻断。
- **待修项回流 todo**: **无**(2 low 非阻断:Vision 检测待多模态接入/诊断日志命名,路由核心功能全 PASS)。
### CR-260616-49 波5 F-01阶段6 AiChat模型下拉+override穿透(4c84fbc) / F-15阶段1剩余compress基础(63bff8b) — 🟡 待审
- **范围**: 2 agent 并行 workflow wcbzmaqty,文件域隔离(A 独占 commands.rs/agentic.rs/AiChat.vue/useAiSend.ts/api/ai.ts/i18n×2;B 独占 prompt.rs/context.rs/compress.rs)。**mod.rs 双方碰**:A 加 `model_override: Option<String>` 字段(L230)+ new()初值 None(L246);B 加 `pub mod compress;`(L27)+ 模块注释(L14)。位置不冲突,git 干净合并。
- **commit**: 63bff8b(F-15 compress 基础)+ 4c84fbc(F-01 阶段6)。拆分约束:F-15 commit 必须含 mod.rs 全部(`pub mod compress` 须与 compress.rs 同 commit;model_override 字段 pub 无害不报 dead),F-01 commit 跟后(agentic.rs/commands.rs 用字段)。两 commit 各自 tree 可编译。
- **主代核查(2026-06-17)**: cargo check --workspace EXIT 0(6 warning 全 pre-existing dead_code,PendingApproval/releases/node_executions)/ npx vue-tsc --noEmit EXIT 0 / cargo test -p df-ai **107 passed**(含 context 9 新方法 + 8 新单测) / cargo test -p devflow --lib compress **3 passed** / grep 核验源码形态(override 兜底 agentic.rs:181-188 + compress select_model_id 兜底 compress.rs:66-67 + context 幂等 context.rs:629-640)。
- **审查 agent 待复审重点**:
- ①**F-01 override 穿透兜底零行为变化**:agentic.rs:181-188 `match model_override.as_deref()` — Some(id) 且 !id.is_empty() 且 `provider_config.model_configs.iter().any(|m| m.model_id == id)` → 用 id;`_ => resolved_model`(落回路由)。override=None(自动模式默认)完全等价改动前。create/switchConversation 清 `session.model_override`(commands.rs:962/1040)防跨对话污染,前端 watch activeConversationId 同步清(useAiSend.ts modelOverride ref / AiChat.vue:1021)。
- ②**F-01 空池降级**:AiChat.vue `enabledModels` 空 → 下拉 v-if 隐藏 + toggle :disabled + setSpecifyMode 早退强制自动模式。空 provider 不 crash。
- ③**F-01 前端 ref 单例**:modelOverride 模块级 ref(useAiSend.ts:53)非 per-conversation,靠 watch 清理对齐后端 switch 清 session.model_override。核验切对话时机无残留。
- ④**F-01 i18n message 值无字面 `@`/`/`/裸`{}`**(vue-i18n prod runtime 报错字符):aiChat.ts 新 6 key(autoMode/specifyMode/autoModeHint/specifyModeHint/selectModel/noModels)手动 grep 核验。
- ⑤**F-15 compress 幂等 + 单向不可逆**:compress_old_messages(context.rs:629-640)只处理 `is_active`,saturating_sub 扣 token,已 compressed 跳过;返回 newly_compressed 仅本次新标记。DB 原始消息保留,is_active 白名单(sanitize step0,波3已就绪)隔离。8 单测覆盖(标记+返回/幂等二次空/越界 clamp/跳过 inactive/has_compressible/flag round-trip+clear 复位/insert_at 计 token/build_eviction 三元组原子)。
- ⑥**F-15 compress_via_llm LLM 失败返 Err**:compress.rs:89 map_err + :92 空摘要 Err。阶段2 IPC 接调用点后降级(不阻塞)。`#[allow(dead_code)]` 占位阶段1(compress.rs:99 prompt.rs)。
- ⑦**F-15 纯函数优先**:compress_prompt(prompt.rs:148 无 IO)/ build_eviction_units/has_compressible_messages/compress_old_messages 无 IO;compress_via_llm 封装 IO(双层 Semaphore 限流,复用 generate_title_via_llm 模式 + select_model_id Standard/Medium 兜底 default_model)。
- ⑧**F-15 零行为变化**:push/restore_from_messages/build_for_request/sanitize_messages 未改,仅新增方法 + is_compressing 字段(new/clear 初值 false)。
- **待修项回流 todo**: **无**(本批为基础暴露 + UI 穿透,核心逻辑 grep+test 核验全 PASS,无阻断。阶段2 IPC 接入后另行登记)。
--- ---