refactor: 全栈质量硬化

后端:
  - 权限系统重写: 全局→按 Session 隔离, 新增规则查询 API
  - 安全加固: 登录 IP 限流, Token 仅存 Cookie, bibcode 白名单校验
  - SSE 超时保护, 异步 I/O 迁移, 10+ 处静默 DB 错误改为显式日志
  - ar5iv 下标解析修复, parse_paper_row 去重, 优雅关闭

  前端:
  - useSyncScroll 重写: 段落 ID 映射修复中英错位
  - 全局竞态修复 (active 标志), libraryRef 闭包过期修复
  - ErrorBoundary + vitest 测试基础设施
  - Logo 组件提取, CustomSelect 泛型化, TabId 类型统一
  - ReaderPanel 自动视图模式, AIAssistantPanel 状态批处理
This commit is contained in:
fmq
2026-06-28 14:40:26 +08:00
parent 5f2d2d83f6
commit c5fd5b0d66
54 changed files with 3223 additions and 743 deletions
+16 -18
View File
@@ -129,7 +129,7 @@ pub async fn execute_parallel(
app_state: Arc<AppState>,
hook_registry: &HookRegistry,
permission_checker: Option<&PermissionChecker>,
session_permission_checker: Option<&std::sync::RwLock<PermissionChecker>>,
session_permission_checker: Option<&PermissionChecker>,
denial_tracker: Option<&std::sync::Mutex<DenialTracker>>,
checkpoint_manager: Option<&std::sync::Arc<CheckpointManager>>,
tx: &mpsc::UnboundedSender<AgentStreamEvent>,
@@ -336,25 +336,23 @@ pub async fn execute_parallel(
// 会话级权限检查(API 动态添加的规则,优先级高于环境变量规则)
if let Some(session_checker) = session_permission_checker {
if let Ok(checker) = session_checker.read() {
let session_result = checker.check(&prep.tool_name, Some(&prep.args));
// 会话规则结果覆盖或升级
match session_result {
PermissionResult::Denied { reason } => {
// 会话 Deny 强制覆盖
perm_result = PermissionResult::Denied { reason };
}
PermissionResult::AskUser { message } => {
// 会话 Ask 在 Allow 时升级
if perm_result.is_allowed() {
perm_result = PermissionResult::AskUser { message };
}
}
PermissionResult::Allowed => {
// 会话 Allow 仅覆盖 Allowed,保持 Deny/AskUser 不变
// 避免覆盖工具级 check_permissions() 升级的 AskUser
let session_result = session_checker.check(&prep.tool_name, Some(&prep.args));
// 会话规则结果覆盖或升级
match session_result {
PermissionResult::Denied { reason } => {
// 会话 Deny 强制覆盖
perm_result = PermissionResult::Denied { reason };
}
PermissionResult::AskUser { message } => {
// 会话 Ask 在 Allow 时升级
if perm_result.is_allowed() {
perm_result = PermissionResult::AskUser { message };
}
}
PermissionResult::Allowed => {
// 会话 Allow 仅覆盖 Allowed,保持 Deny/AskUser 不变
// 避免覆盖工具级 check_permissions() 升级的 AskUser
}
}
}
+19 -9
View File
@@ -130,10 +130,7 @@ impl AgentRuntime {
permission_checker.clone(),
),
));
// 初始化会话级权限检查器(与 AgentRuntime 使用相同的环境变量规则)
if let Ok(mut session_checker) = app_state.session_permission_checker.write() {
*session_checker = (*permission_checker).clone();
}
// 会话级权限检查器将在 run_react_loop 中按 session_id 注册
// 视觉模型可用时注册 analyze_image 工具
if app_state.vision_llm.is_some() {
@@ -202,10 +199,7 @@ impl AgentRuntime {
permission_checker.clone(),
),
));
// 初始化会话级权限检查器
if let Ok(mut session_checker) = app_state.session_permission_checker.write() {
*session_checker = (*permission_checker).clone();
}
// 会话级权限检查器将在 run_react_loop 中按 session_id 注册
// 视觉模型可用时注册 analyze_image 工具
if app_state.vision_llm.is_some() {
@@ -497,6 +491,15 @@ impl AgentRuntime {
let sid = &session_info.session_id;
let turn_index = session_info.turn_index;
// 注册当前会话的权限检查器(如不存在则从全局配置初始化)
{
if let Ok(mut checkers) = self.app_state.session_permission_checkers.write() {
checkers
.entry(sid.clone())
.or_insert_with(|| (*self.permission_checker).clone());
}
}
let tool_defs = self.tool_registry.definitions();
let mut duplicate_detector = DuplicateDetector::default();
let mut metrics = AgentMetrics::default();
@@ -867,13 +870,20 @@ impl AgentRuntime {
}
// 并行执行工具(带权限检查、checkpoint 和分区器)
let session_checker_snapshot = {
self.app_state
.session_permission_checkers
.read()
.ok()
.and_then(|checkers| checkers.get(sid).cloned())
};
let exec_result = executor::execute_parallel(
&prepared_calls,
&self.tool_registry,
self.app_state.clone(),
hook_registry,
Some(&self.permission_checker),
Some(&self.app_state.session_permission_checker),
session_checker_snapshot.as_ref(),
Some(&self.denial_tracker),
Some(&self.checkpoint_manager),
tx,
+10
View File
@@ -76,6 +76,16 @@ impl PermissionChecker {
}
}
/// 返回当前规则列表的只读引用
pub fn rules(&self) -> &[PermissionRule] {
&self.rules
}
/// 返回当前权限模式
pub fn mode(&self) -> PermissionMode {
self.mode
}
/// 添加规则。先添加的优先级更高。
pub fn add_rule(&mut self, rule: PermissionRule) {
self.rules.push(rule);
+2 -1
View File
@@ -600,12 +600,13 @@ mod tests {
skill_registry: Arc::new(RwLock::new(SkillRegistry::new(PathBuf::from("/tmp/sk")))),
pending_questions: Arc::new(Mutex::new(std::collections::HashMap::new())),
pending_permissions: Arc::new(Mutex::new(std::collections::HashMap::new())),
session_permission_checker: Arc::new(RwLock::new(PermissionChecker::new())),
session_permission_checkers: Arc::new(RwLock::new(std::collections::HashMap::new())),
sse_broadcast: None,
memory_manager: Arc::new(tokio::sync::Mutex::new(MemoryManager::new(PathBuf::from(
"/tmp/test_mem",
)))),
sessions: Arc::new(Mutex::new(std::collections::HashMap::new())),
login_rate_limiter: Arc::new(Mutex::new(std::collections::HashMap::new())),
});
ToolContext::new(app_state)