fix(less-computer): Agent 真正动手干活——放行+护栏(acceptEdits + Bash/工具 + 高风险拦截)
根因:run_voice_agent_transcript 之前硬编码 Plan 只读 + 无工具,所以 Agent 只能返回文本、 根本不会真的开 Chrome / 跑命令。改成项目既有的「放行 + 护栏」动作模式: - permission_mode = acceptEdits(尊重高级里的设置;coding_agent_mode_from_pref 映射) - allowed_tools = Bash/Read/Edit/Write/Glob/Grep/WebFetch/WebSearch(能真正动手) - 护栏 settings:deny 高风险(rm -rf / sudo / 强推等)自动拦截 = 用户选的「混合:仅高风险」MVP - cwd = workdir 或 $HOME(--add-dir 限定文件作用域);运行前 git 快照便于回滚;用后清理临时护栏文件 真实链路实测(claude v2.1.161,haiku):让它「在 /tmp 建 lesscomputer_probe.txt 内容 ok」 → 文件真的被创建、result=success/「done」、15.8s/$0.05。证明无头链路确实会执行动作。 「开 Chrome」走同一条 Bash 路径(open -a)。 注:交互式弹审批(高风险在弹框点同意)+ 自适应弹框 UI 为后续;当前高风险=自动拦截。 cargo check 0 / less_computer + guard + autonomous_prompt 单测全过。
This commit is contained in:
1 parent
dae966ffd1
commit
8fd3ca4a19
1 file changed
+57
-5
@@ -550,19 +550,59 @@ async fn run_voice_agent_transcript(
|
||||
);
|
||||
|
||||
let prefs = inner.prefs.get();
|
||||
// 工作目录:用户设的 workdir,否则 $HOME。--add-dir 把文件作用域限定在此。
|
||||
let cwd = prefs
|
||||
.coding_agent_workdir
|
||||
.clone()
|
||||
.filter(|d| !d.trim().is_empty())
|
||||
.map(std::path::PathBuf::from)
|
||||
.or_else(|| std::env::var("HOME").ok().map(std::path::PathBuf::from));
|
||||
// 运行前 git 快照(cwd 是 git 仓库才有效;非仓库无副作用),便于回滚文件改动。
|
||||
if let Some(dir) = &cwd {
|
||||
if let Some(sha) = crate::coding_agent::create_git_snapshot(dir) {
|
||||
log::info!("[less-computer] 运行前 git 快照 {sha}(git stash apply 可回滚)");
|
||||
}
|
||||
}
|
||||
// 「放行 + 护栏」:让 Agent 真正动手(开应用 / 跑命令 / 读写文件)完成任务;
|
||||
// 高风险/不可逆命令(rm -rf / sudo / 强推等)由护栏 deny 清单自动拦截(混合模式 MVP,
|
||||
// 高风险=拦截;交互式弹审批为后续)。权限模式尊重高级设置,默认 acceptEdits。
|
||||
let mode = coding_agent_mode_from_pref(&prefs.coding_agent_permission_mode);
|
||||
let settings_json =
|
||||
crate::coding_agent::guard::build_guard_settings_json(mode.as_cli_arg(), &[]);
|
||||
let settings_path = std::env::temp_dir().join(format!(
|
||||
"openless-less-computer-guard-{}.json",
|
||||
uuid::Uuid::new_v4()
|
||||
));
|
||||
if let Err(e) = std::fs::write(
|
||||
&settings_path,
|
||||
serde_json::to_vec_pretty(&settings_json).unwrap_or_default(),
|
||||
) {
|
||||
log::warn!("[less-computer] 写护栏配置失败: {e}");
|
||||
}
|
||||
|
||||
// 无头单次运行没有多轮兜底:包一层目标驱动的自动化前置说明,逼 Claude 一口气做完。
|
||||
let prompt = crate::coding_agent::autonomous_prompt(&transcript);
|
||||
let mut req = crate::coding_agent::CodingAgentRequest::new("voice-agent", prompt);
|
||||
let mut req = crate::coding_agent::CodingAgentRequest::new("less-computer", prompt);
|
||||
req.cwd = cwd;
|
||||
req.model = prefs
|
||||
.coding_agent_model
|
||||
.clone()
|
||||
.filter(|m| !m.trim().is_empty())
|
||||
.or_else(|| Some("sonnet".to_string()));
|
||||
// 只读 Plan 模式:安全 MVP——返回文本、不产生副作用。后续要工具操作再放开。
|
||||
req.permission_mode = crate::coding_agent::CodingAgentPermissionMode::Plan;
|
||||
req.allowed_tools = Vec::new();
|
||||
req.permission_mode = mode;
|
||||
req.settings_json_path = Some(settings_path.clone());
|
||||
req.allowed_tools = vec![
|
||||
"Bash".into(),
|
||||
"Read".into(),
|
||||
"Edit".into(),
|
||||
"Write".into(),
|
||||
"Glob".into(),
|
||||
"Grep".into(),
|
||||
"WebFetch".into(),
|
||||
"WebSearch".into(),
|
||||
];
|
||||
req.max_budget_usd = Some(0.5);
|
||||
req.timeout_secs = 90;
|
||||
req.timeout_secs = 120;
|
||||
req.session_persistence = false;
|
||||
|
||||
let (tx, mut rx) = tokio::sync::mpsc::unbounded_channel();
|
||||
@@ -583,6 +623,7 @@ async fn run_voice_agent_transcript(
|
||||
}
|
||||
}
|
||||
let run_result = run.await;
|
||||
let _ = std::fs::remove_file(&settings_path);
|
||||
let final_text = final_text.trim().to_string();
|
||||
|
||||
inner.state.lock().phase = SessionPhase::Idle;
|
||||
@@ -608,6 +649,17 @@ async fn run_voice_agent_transcript(
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// 把 prefs 里的权限模式字符串映射成枚举;未知值回落到 acceptEdits(放行+护栏的默认)。
|
||||
fn coding_agent_mode_from_pref(s: &str) -> crate::coding_agent::CodingAgentPermissionMode {
|
||||
use crate::coding_agent::CodingAgentPermissionMode as M;
|
||||
match s.trim() {
|
||||
"plan" => M::Plan,
|
||||
"default" => M::Default,
|
||||
"bypassPermissions" => M::BypassPermissions,
|
||||
_ => M::AcceptEdits,
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn request_stop_during_starting(inner: &Arc<Inner>, reason: &str) {
|
||||
{
|
||||
let mut state = inner.state.lock();
|
||||
|
||||
Reference in new issue
Block a user