新增: idea source 消息级溯源 + workspace_root 去固定根 + run_command 偏好修复
idea source 结构化(消息级溯源闭环 — 灵感来源 conv_msg:{id}):
- 新建 idea_source.rs(IDEA_SOURCE_AUTO_FILL_ENABLED + maybe_fill_idea_source 保守五条件:
开关/create_idea/source 空(不覆盖 AI 填)/message_id 非 None/result.id)
- audit/mod low_risk + chat ai_approve 双执行路径接补全(create_idea Medium 审批)
workspace_root 去固定根(F-260619-03 收尾):
- is_authorized 删 workspace_root 固定放行(代码硬编码),改走 persistent/session 白名单
- default_with_root 保留(默认初始 persistent,首次免授权行为不变)
- 用户从白名单删工程根后也需授权(动态白名单完整语义)
run_command 偏好修复(db 分析:591ff4a0 run_command 31/56=55% 严重偏好):
- P1 描述收紧(明确仅命令执行 + 负向引导:读取 read_file/编辑 patch_file/列目录 list_directory/搜索 search_files)
- P2 ToolDomain::Exec(run_command 独立 domain,仅 Debug 含 Exec,Code/File/Search 不含)
- P3 注册顺序(run_command 放 File 层最后)+ DEBUG_RULES 加命令执行关键词
自验: df-ai 228(intent Exec)+ devflow 169(idea source)+ workspace EXIT 0
This commit is contained in:
@@ -119,6 +119,13 @@ const DEBUG_RULES: &[Rule] = &[
|
||||
Rule { kw: "栈", weight: 0.5 },
|
||||
Rule { kw: "panic", weight: 0.9 },
|
||||
Rule { kw: "traceback", weight: 0.9 },
|
||||
// 命令执行场景关键词(run_command 入口引导):用户明确"运行/执行/构建/测试/跑"
|
||||
// 才该走 Exec domain(run_command)。中等权重(0.7)避免误抢 Code 命中(编译/重构 1.0)。
|
||||
Rule { kw: "运行", weight: 0.7 },
|
||||
Rule { kw: "执行", weight: 0.7 },
|
||||
Rule { kw: "构建", weight: 0.7 },
|
||||
Rule { kw: "测试", weight: 0.7 },
|
||||
Rule { kw: "跑", weight: 0.6 },
|
||||
];
|
||||
|
||||
const FILE_RULES: &[Rule] = &[
|
||||
@@ -225,8 +232,10 @@ const GENERIC_GROUP: &[IntentGroup] = &[
|
||||
pub enum ToolDomain {
|
||||
/// 数据/业务:项目/任务/灵感/工作流/回收站
|
||||
Data,
|
||||
/// 文件:读写/patch/列目录/搜索/命令
|
||||
/// 文件:读写/patch/列目录/搜索(不含命令执行)
|
||||
File,
|
||||
/// 命令执行:run_command(shell 命令,独立 domain 防止被泛 File 意图带出)
|
||||
Exec,
|
||||
/// 网络:HTTP
|
||||
Http,
|
||||
}
|
||||
@@ -261,12 +270,15 @@ impl ToolDomain {
|
||||
"patch_file",
|
||||
"list_directory",
|
||||
"search_files",
|
||||
"run_command",
|
||||
"file_info",
|
||||
"append_file",
|
||||
"delete_file",
|
||||
"rename_file",
|
||||
],
|
||||
// Exec domain 独立:run_command 从 File 移出。
|
||||
// 意图侧仅 Debug 默认带 Exec(用户明确"运行/测试/构建"),
|
||||
// Code/File/Search 不带 → 减少 LLM 对 run_command 的偏好暴露。
|
||||
ToolDomain::Exec => &["run_command"],
|
||||
ToolDomain::Http => &["http_request"],
|
||||
}
|
||||
}
|
||||
@@ -337,11 +349,13 @@ fn best_in_group(message: &str, group: &[IntentGroup]) -> Option<(Intent, f32)>
|
||||
///
|
||||
/// 设计:Code → [file, http];File → [file];Project/Task/Idea → [data];
|
||||
/// Http → [http];Search → [file](含 search_files);Conversation → [];
|
||||
/// Chat → [];Debug → [file, http, data](调试常需读文件+查 API+查任务/工作流状态,CR-25 审查🟡-1 加 data 防"调试任务"丢 Data 工具);Unknown → [](全量)。
|
||||
/// Chat → [];Debug → [file, exec, http, data](调试常需跑命令+读文件+查 API+查任务/工作流状态,CR-25 审查🟡-1 加 data 防"调试任务"丢 Data 工具);
|
||||
/// **仅 Debug 含 Exec**(用户明确"运行/测试/构建/调试"才暴露 run_command),
|
||||
/// Code/File/Search 不含 Exec → 收紧 run_command 暴露面;Unknown → [](全量)。
|
||||
pub fn tool_subset_for(intent: &Intent) -> Vec<&'static str> {
|
||||
let domains: &[ToolDomain] = match intent {
|
||||
Intent::Code => &[ToolDomain::File, ToolDomain::Http],
|
||||
Intent::Debug => &[ToolDomain::File, ToolDomain::Http, ToolDomain::Data],
|
||||
Intent::Debug => &[ToolDomain::File, ToolDomain::Exec, ToolDomain::Http, ToolDomain::Data],
|
||||
Intent::File => &[ToolDomain::File],
|
||||
Intent::Project => &[ToolDomain::Data],
|
||||
Intent::Task => &[ToolDomain::Data],
|
||||
@@ -669,7 +683,9 @@ mod tests {
|
||||
fn tool_domain_names_match_registry() {
|
||||
// 抽样核对与 src-tauri tool_registry 实际注册名一致
|
||||
assert!(ToolDomain::File.tools().contains(&"patch_file"));
|
||||
assert!(ToolDomain::File.tools().contains(&"run_command"));
|
||||
// run_command 已从 File 移出 Exec domain(意图收紧:仅 Debug 默认带 run_command)
|
||||
assert!(!ToolDomain::File.tools().contains(&"run_command"));
|
||||
assert_eq!(ToolDomain::Exec.tools(), &["run_command"]);
|
||||
assert!(ToolDomain::Data.tools().contains(&"advance_task"));
|
||||
assert!(ToolDomain::Data.tools().contains(&"run_workflow"));
|
||||
assert!(ToolDomain::Data.tools().contains(&"list_trash"));
|
||||
@@ -838,9 +854,9 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn filter_debug_subset_spans_three_domains_complete() {
|
||||
// 对抗(多 domain):Debug subset 跨 File + Http + Data 三 domain,
|
||||
// 必须同条消息里能同时命中三个 domain 的工具(防 domain 漏挂)
|
||||
fn filter_debug_subset_spans_four_domains_complete() {
|
||||
// 对抗(多 domain):Debug subset 跨 File + Exec + Http + Data 四 domain,
|
||||
// 必须同条消息里能同时命中四个 domain 的工具(防 domain 漏挂)
|
||||
let all = vec![
|
||||
// Data domain
|
||||
tool_def("list_tasks"),
|
||||
@@ -849,6 +865,8 @@ mod tests {
|
||||
// File domain
|
||||
tool_def("read_file"),
|
||||
tool_def("patch_file"),
|
||||
// Exec domain
|
||||
tool_def("run_command"),
|
||||
// Http domain
|
||||
tool_def("http_request"),
|
||||
];
|
||||
@@ -856,8 +874,27 @@ mod tests {
|
||||
let names: Vec<String> = out.iter().map(|d| d.function.name.clone()).collect();
|
||||
assert!(names.contains(&"list_tasks".to_string()), "Debug 必含 Data(list_tasks), 防 CR-25 🟡-1 丢 Data");
|
||||
assert!(names.contains(&"read_file".to_string()), "Debug 必含 File");
|
||||
assert!(names.contains(&"run_command".to_string()), "Debug 必含 Exec(run_command)");
|
||||
assert!(names.contains(&"http_request".to_string()), "Debug 必含 Http");
|
||||
assert_eq!(names.len(), 6, "三 domain 工具全保留, 实际: {:?}", names);
|
||||
assert_eq!(names.len(), 7, "四 domain 工具全保留, 实际: {:?}", names);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn run_command_only_exposed_for_debug_intent() {
|
||||
// 核心修复断言:run_command 仅 Debug 暴露。
|
||||
// Code/File/Search 意图 subset 不含 Exec domain → 不含 run_command。
|
||||
for intent in [Intent::Code, Intent::File, Intent::Search] {
|
||||
let s = tool_subset_for(&intent);
|
||||
assert!(
|
||||
!s.contains(&"run_command"),
|
||||
"Intent {:?} subset 不应含 run_command(仅 Debug 暴露), 实际: {:?}",
|
||||
intent,
|
||||
s
|
||||
);
|
||||
}
|
||||
// Debug 必含 run_command(Exec domain)
|
||||
let dbg = tool_subset_for(&Intent::Debug);
|
||||
assert!(dbg.contains(&"run_command"), "Debug 必含 run_command");
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -893,11 +930,12 @@ mod tests {
|
||||
];
|
||||
for intent in all_intents {
|
||||
let subset = tool_subset_for(&intent);
|
||||
// 全 registry 工具名(三 domain 并集)
|
||||
// 全 registry 工具名(四 domain 并集:Data + File + Exec + Http)
|
||||
let registry: std::collections::HashSet<&str> = ToolDomain::Data
|
||||
.tools()
|
||||
.iter()
|
||||
.chain(ToolDomain::File.tools().iter())
|
||||
.chain(ToolDomain::Exec.tools().iter())
|
||||
.chain(ToolDomain::Http.tools().iter())
|
||||
.copied()
|
||||
.collect();
|
||||
|
||||
Reference in New Issue
Block a user