新增: 路径授权三档化 once/session/always
Phase B 弹窗 once/always → once/session/always 三档(本次单次执行后清/当前会话切会话清/始终落KV)。修 once 原写 session 致本次实为本会话名实不符。AllowedDirs 加 once 层 + ai_authorize_dir decision 改 match 四分支 + 执行后 clear_once。
This commit is contained in:
@@ -751,9 +751,10 @@ fn append_cross_drive_note(result: serde_json::Value, note: &str) -> serde_json:
|
||||
/// 触发:process_tool_calls 预校验文件工具路径未命中白名单 → 挂起 loop(insert pending
|
||||
/// with path_auth=Some + emit AiDirAuthRequired)。前端弹窗三选项,用户选择后调本 IPC。
|
||||
///
|
||||
/// `decision`:
|
||||
/// - `"once"`:写入 `AppState.allowed_dirs.session`(进程级会话临时授权,随 active 会话切换/新建/删除由 clear_session_allowed_dirs 清空)→ 执行工具 → 恢复 loop
|
||||
/// - `"always"`:写入持久化 Settings KV `allowed_dirs`(reload_allowed_dirs 同步内存)→ 执行 → 恢复
|
||||
/// `decision`(三授权档 + 拒):
|
||||
/// - `"once"`:写入 `allowed_dirs.once`(本次单次,执行后 clear_once_allowed_dirs 清空)→ 执行 → 恢复
|
||||
/// - `"session"`:写入 `allowed_dirs.session`(当前会话,切换/新建/删除活跃会话由 clear_session_allowed_dirs 清空)→ 执行 → 恢复
|
||||
/// - `"always"`:写入持久化 Settings KV `allowed_dirs`(reload_allowed_dirs 同步内存 persistent)→ 执行 → 恢复
|
||||
/// - `"deny"`:工具返 Err "用户拒绝路径授权" → 恢复 loop
|
||||
///
|
||||
/// 复用审批恢复架构:remove pending → (写授权/拒) → execute/Err → 替换占位 tool_result →
|
||||
@@ -843,23 +844,33 @@ pub async fn ai_authorize_dir(
|
||||
return Ok("rejected".to_string());
|
||||
}
|
||||
|
||||
// ── "once":写会话级临时授权(随会话销毁,不落库) ──
|
||||
// L1 补丁:遍历所有未授权父目录(rename_file from+to 不同目录时多项)。
|
||||
if decision == "once" {
|
||||
for d in &dir_strs {
|
||||
state.add_session_allowed_dir(d.clone()).await;
|
||||
// ── 授权档写入(三档:once 本次单次 / session 当前会话 / always 始终落 KV)──
|
||||
// L1 补丁:遍历所有未授权父目录(rename_file from+to 不同目录时多项),确保 handler 放行。
|
||||
// deny 已在上方早 return,此处仅 once/session/always。
|
||||
match decision.as_str() {
|
||||
"once" => {
|
||||
for d in &dir_strs {
|
||||
state.add_once_allowed_dir(d.clone()).await;
|
||||
}
|
||||
tracing::info!(target: "ai_authz", dirs = ?dir_strs, conv_id = ?conv_id, "[AI-AUTHZ] 授权档=once(本次单次,执行后清)");
|
||||
}
|
||||
}
|
||||
|
||||
// ── "always":写持久化授权(Settings KV + 同步内存 persistent) ──
|
||||
if decision == "always" {
|
||||
for d in &dir_strs {
|
||||
if let Err(e) = state.add_persistent_allowed_dir(d.clone()).await {
|
||||
tracing::warn!("[F-03B] 持久化授权目录失败(降级仅本次会话): dir={} err={}", d, e);
|
||||
// 降级:写入会话临时授权,保本路径本会话可用
|
||||
"session" => {
|
||||
for d in &dir_strs {
|
||||
state.add_session_allowed_dir(d.clone()).await;
|
||||
}
|
||||
tracing::info!(target: "ai_authz", dirs = ?dir_strs, conv_id = ?conv_id, "[AI-AUTHZ] 授权档=session(当前会话,切会话清)");
|
||||
}
|
||||
"always" => {
|
||||
for d in &dir_strs {
|
||||
if let Err(e) = state.add_persistent_allowed_dir(d.clone()).await {
|
||||
tracing::warn!("[F-03B] 持久化授权目录失败(降级当前会话): dir={} err={}", d, e);
|
||||
// 降级:写入会话临时授权,保本路径本会话可用
|
||||
state.add_session_allowed_dir(d.clone()).await;
|
||||
}
|
||||
}
|
||||
tracing::info!(target: "ai_authz", dirs = ?dir_strs, conv_id = ?conv_id, "[AI-AUTHZ] 授权档=always(始终,落 KV)");
|
||||
}
|
||||
_ => {} // deny 已上方早 return;未知 decision 不写授权(执行会再走预校验弹窗)
|
||||
}
|
||||
|
||||
// ── 执行工具(路径现已授权,handler 内 resolve_workspace_path_with_allowed 放行) ──
|
||||
@@ -908,6 +919,10 @@ pub async fn ai_authorize_dir(
|
||||
}
|
||||
}
|
||||
|
||||
// once 授权档语义=本次单次:工具执行完(handler 已读 once 放行)立即清空,下次同路径仍弹窗。
|
||||
// 仅 once 档写入 once 层,无条件清空安全(session/always 不写 once,清空无副作用)。
|
||||
state.clear_once_allowed_dirs().await;
|
||||
|
||||
// 替换占位 tool_result 为真实结果
|
||||
{
|
||||
let mut session = state.ai_session.lock().await;
|
||||
|
||||
Reference in New Issue
Block a user