fix: 优先审批 + 首用户隔离 + 审批DB闭环 + memo错误

1. (High) 内置工具先审批再执行 — is_builtin 检查优先,
   High 风险通过 builtin_approve 后再调用 BuiltinRegistry
2. (High) 首用户 current_user_id — 移除 !is_empty() 守卫
3. (Medium) 审批 DB 状态闭环 — 取消/错误时按 user_id 更新最近 pending
4. (Medium) manage_memos 写入失败 — .ok() → 完整错误返回
This commit is contained in:
2026-06-02 15:30:53 +08:00
parent 88de5597b3
commit 065229b9da
3 changed files with 20 additions and 10 deletions
+8 -6
View File
@@ -371,20 +371,22 @@ async fn cmd_listen(
cp.get("name").and_then(|v| v.as_str()).map(|s| s.to_string()).unwrap_or_default() cp.get("name").and_then(|v| v.as_str()).map(|s| s.to_string()).unwrap_or_default()
} else { n.clone() }; } else { n.clone() };
// 内置工具 // 内置工具(高风险先审批再执行)
if let Some(mut result) = tools::builtin::BuiltinRegistry::execute(&target_name, &args).await { if tools::builtin::BuiltinRegistry::is_builtin(&target_name) {
if tools::builtin::BuiltinRegistry::is_high_risk(&target_name) { if tools::builtin::BuiltinRegistry::is_high_risk(&target_name) {
let mut ctx = ExecutionContext::new(&user_id); let mut ctx = ExecutionContext::new(&user_id);
ctx = ctx.with_approval(approval.clone()); ctx = ctx.with_approval(approval.clone());
ctx = ctx.with_wechat(send); ctx = ctx.with_wechat(send.clone());
let approved = builtin_approve(&ctx, &target_name).await; let approved = builtin_approve(&ctx, &target_name).await;
match approved { match approved {
Ok(true) => return Ok(result.output), Ok(true) => {},
Ok(false) => return Ok(SkillResult::rejected(&target_name).output), Ok(false) => return Ok(SkillResult::rejected(&target_name).output),
Err(e) => return Ok(e), Err(e) => return Ok(e),
} }
} }
return Ok(result.output); if let Some(result) = tools::builtin::BuiltinRegistry::execute(&target_name, &args).await {
return Ok(result.output);
}
} }
// 回退到外部 SkillRegistry // 回退到外部 SkillRegistry
@@ -539,7 +541,7 @@ async fn listen_loop(
// 用户切换:清空上下文,加载新用户历史 // 用户切换:清空上下文,加载新用户历史
{ {
let mut last_user = last_user_id.lock().await; let mut last_user = last_user_id.lock().await;
if *last_user != from.to_string() && !last_user.is_empty() { if *last_user != *from {
info!("用户切换: {} → {}", *last_user, from); info!("用户切换: {} → {}", *last_user, from);
if let Some(conv) = &conversation { if let Some(conv) = &conversation {
let session = conv.session(); let session = conv.session();
+2 -2
View File
@@ -126,12 +126,12 @@ impl ApprovalManager {
ApprovalDecision::Approved => "approved", ApprovalDecision::Approved => "approved",
_ => "rejected", _ => "rejected",
}; };
// 按 user_id + 最近 pending 记录更新
let _ = sqlx::query( let _ = sqlx::query(
"UPDATE pending_approvals SET status = $1, consumed_at = NOW() WHERE user_id = $2 AND code_hash = $3 AND status = 'pending'", "UPDATE pending_approvals SET status = $1, consumed_at = NOW() WHERE user_id = $2 AND status = 'pending' ORDER BY created_at DESC LIMIT 1",
) )
.bind(status) .bind(status)
.bind(user_id) .bind(user_id)
.bind(&format!("{:x}", Sha256::digest(reply.as_bytes())))
.execute(pool.as_ref()) .execute(pool.as_ref())
.await; .await;
} }
+10 -2
View File
@@ -36,6 +36,10 @@ impl BuiltinRegistry {
pub fn is_high_risk(name: &str) -> bool { pub fn is_high_risk(name: &str) -> bool {
Self::specs().iter().any(|s| s.name == name && s.risk_level == RiskLevel::High) Self::specs().iter().any(|s| s.name == name && s.risk_level == RiskLevel::High)
} }
pub fn is_builtin(name: &str) -> bool {
Self::specs().iter().any(|s| s.name == name)
}
} }
fn execute_datetime() -> SkillResult { fn execute_datetime() -> SkillResult {
@@ -64,7 +68,9 @@ async fn handle_memos(args_json: &str) -> SkillResult {
"id": next_id, "content": content, "id": next_id, "content": content,
"created_at": Local::now().format("%Y-%m-%d %H:%M").to_string() "created_at": Local::now().format("%Y-%m-%d %H:%M").to_string()
})); }));
std::fs::write(path, serde_json::to_string_pretty(&data).unwrap()).ok(); if let Err(e) = std::fs::write(path, serde_json::to_string_pretty(&data).unwrap()) {
return SkillResult::error(format!("写入备忘录失败: {}", e));
}
SkillResult::ok(format!("已添加备忘录 #{}", next_id)) SkillResult::ok(format!("已添加备忘录 #{}", next_id))
} }
"list" => { "list" => {
@@ -80,7 +86,9 @@ async fn handle_memos(args_json: &str) -> SkillResult {
let before = data.len(); let before = data.len();
data.retain(|m| m.get("id").and_then(|v| v.as_i64()) != Some(id)); data.retain(|m| m.get("id").and_then(|v| v.as_i64()) != Some(id));
if data.len() == before { return SkillResult::error(format!("未找到备忘录 #{}", id)); } if data.len() == before { return SkillResult::error(format!("未找到备忘录 #{}", id)); }
std::fs::write(path, serde_json::to_string_pretty(&data).unwrap()).ok(); if let Err(e) = std::fs::write(path, serde_json::to_string_pretty(&data).unwrap()) {
return SkillResult::error(format!("写入备忘录失败: {}", e));
}
SkillResult::ok(format!("已删除备忘录 #{}", id)) SkillResult::ok(format!("已删除备忘录 #{}", id))
} }
_ => SkillResult::error("未知操作,支持: add, list, delete"), _ => SkillResult::error("未知操作,支持: add, list, delete"),