diff --git a/docs/todo.md b/docs/todo.md index c0be0dc..4146015 100644 --- a/docs/todo.md +++ b/docs/todo.md @@ -968,6 +968,11 @@ > - **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 安全方向不算错,未改)** 外全合理。 +> > **剩余低优 [ ](非阻断)**: > - [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 收不到) diff --git a/src-tauri/src/commands/ai/tool_registry.rs b/src-tauri/src/commands/ai/tool_registry.rs index 529c495..3320b62 100644 --- a/src-tauri/src/commands/ai/tool_registry.rs +++ b/src-tauri/src/commands/ai/tool_registry.rs @@ -1658,7 +1658,10 @@ fn register_file_tools( // 二进制文件(对齐 read_file/list_directory:\0 检测)。 // path_auth:授权目录内放行;未授权走 AiDirAuthRequired 申请(audit/mod.rs check_file_tool_auth // 经 extract_file_tool_paths 单路径分支触发,非 search_files 盲拒)。 - // risk:Med(读文件内容,授权目录内放行/外申请)。 + // risk:Low(只读内容搜索,对齐 read_file/search_files;目录授权走 path_auth 独立层, + // 不混入 risk_level——read_file 同样读内容+走 path_auth 但 Low。原 Med 过保守致每次搜索审批)。 + // BUG-260624-04:用户报"搜索代码也要授权",根因即此 Med 错标 + tool_display_hint 无 grep 条目 + // 致 reason fallback "创建操作"文案误导。降 Low + 补 hint 双修。 // 注册顺序:read_file/list_directory 后,search_files 前(高频检索工具靠前)。 let grep_schema = { let mut props = serde_json::Map::new(); @@ -1679,7 +1682,7 @@ fn register_file_tools( registry.register( "grep", "跨文件内容搜索(grep -rn 模式)。参数:pattern(正则,大小写敏感,无特殊字符时等价字面包含)、path(搜索根,锚 workspace + path_auth 授权)、glob(可选文件名过滤如 *.rs)、output_mode(content/files_with_matches/count)、-n(行号默认 true)、-i(大小写不敏感默认 false)、-C(上下文行数默认 0)、max_results(上限默认 50)。跳过噪音目录/噪音文件/symlink/二进制文件。返回 matches(files_with_matches 模式)或 matches(含 file/line/content/context,content 模式)+ total + truncated。授权目录内放行,未授权触发目录授权申请(AiDirAuthRequired)", grep_schema, - RiskLevel::Medium, + RiskLevel::Low, { let allowed_dirs = allowed_dirs.clone(); Box::new(move |args: serde_json::Value| { let allowed_dirs = allowed_dirs.clone(); Box::pin(async move { @@ -2119,6 +2122,7 @@ pub(crate) fn tool_display_hint(name: &str) -> Option<&'static str> { "read_file" => Some("读取文件"), "list_directory" => Some("列出目录"), "search_files" => Some("搜索文件"), + "grep" => Some("搜索代码内容"), "create_project" => Some("创建项目"), "update_project" => Some("修改项目"), "delete_project" => Some("删除项目"),