feat: 新增管理后台前端项目及安全加固
Some checks failed
CI / Lint & TypeCheck (push) Has been cancelled
CI / Unit Tests (push) Has been cancelled
CI / Build Frontend (push) Has been cancelled
CI / Rust Check (push) Has been cancelled
CI / Security Scan (push) Has been cancelled
CI / E2E Tests (push) Has been cancelled

refactor(saas): 重构认证中间件与限流策略
- 登录限流调整为5次/分钟/IP
- 注册限流调整为3次/小时/IP
- GET请求不计入限流

fix(saas): 修复调度器时间戳处理
- 使用NOW()替代文本时间戳
- 兼容TEXT和TIMESTAMPTZ列类型

feat(saas): 实现环境变量插值
- 支持${ENV_VAR}语法解析
- 数据库密码支持环境变量注入

chore: 新增前端管理界面
- 基于React+Ant Design Pro
- 包含路由守卫/错误边界
- 对接58个API端点

docs: 更新安全加固文档
- 新增密钥管理规范
- 记录P0安全项审计结果
- 补充TLS终止说明

test: 完善配置解析单元测试
- 新增环境变量插值测试用例
This commit is contained in:
iven
2026-03-31 00:11:33 +08:00
parent 6821df5f44
commit eb956d0dce
129 changed files with 11913 additions and 863 deletions

View File

@@ -1,10 +1,15 @@
//! Director - Multi-Agent Orchestration
//! Director - Multi-Agent Orchestration (Experimental)
//!
//! The Director manages multi-agent conversations by:
//! - Determining which agent speaks next
//! - Managing conversation state and turn order
//! - Supporting multiple scheduling strategies
//! - Coordinating agent responses
//!
//! **Status**: This module is fully implemented but gated behind the `multi-agent` feature.
//! The desktop build does not currently enable this feature. When multi-agent support
//! is ready for production, add Tauri commands to create and interact with the Director,
//! and enable the feature in `desktop/src-tauri/Cargo.toml`.
use std::sync::Arc;
use serde::{Deserialize, Serialize};

View File

@@ -5,21 +5,21 @@
use std::sync::Arc;
use std::sync::atomic::{AtomicBool, Ordering};
use chrono::{Datelike, Timelike};
use tokio::sync::RwLock;
use tokio::sync::Mutex;
use tokio::time::{self, Duration};
use zclaw_types::Result;
use crate::Kernel;
/// Scheduler service that runs in the background and executes scheduled triggers
pub struct SchedulerService {
kernel: Arc<RwLock<Option<Kernel>>>,
kernel: Arc<Mutex<Option<Kernel>>>,
running: Arc<AtomicBool>,
check_interval: Duration,
}
impl SchedulerService {
/// Create a new scheduler service
pub fn new(kernel: Arc<RwLock<Option<Kernel>>>, check_interval_secs: u64) -> Self {
pub fn new(kernel: Arc<Mutex<Option<Kernel>>>, check_interval_secs: u64) -> Self {
Self {
kernel,
running: Arc::new(AtomicBool::new(false)),
@@ -74,58 +74,56 @@ impl SchedulerService {
/// Check all scheduled triggers and fire those that are due
async fn check_and_fire_scheduled_triggers(
kernel_lock: &Arc<RwLock<Option<Kernel>>>,
kernel_lock: &Arc<Mutex<Option<Kernel>>>,
) -> Result<()> {
let kernel_read = kernel_lock.read().await;
let kernel = match kernel_read.as_ref() {
Some(k) => k,
None => return Ok(()),
};
// Collect due triggers under lock
let to_execute: Vec<(String, String, String)> = {
let kernel_guard = kernel_lock.lock().await;
let kernel = match kernel_guard.as_ref() {
Some(k) => k,
None => return Ok(()),
};
// Get all triggers
let triggers = kernel.list_triggers().await;
let now = chrono::Utc::now();
let triggers = kernel.list_triggers().await;
let now = chrono::Utc::now();
// Filter to enabled Schedule triggers
let scheduled: Vec<_> = triggers.iter()
.filter(|t| {
t.config.enabled && matches!(t.config.trigger_type, zclaw_hands::TriggerType::Schedule { .. })
})
.collect();
let scheduled: Vec<_> = triggers.iter()
.filter(|t| {
t.config.enabled && matches!(t.config.trigger_type, zclaw_hands::TriggerType::Schedule { .. })
})
.collect();
if scheduled.is_empty() {
return Ok(());
}
if scheduled.is_empty() {
return Ok(());
}
tracing::debug!("[Scheduler] Checking {} scheduled triggers", scheduled.len());
tracing::debug!("[Scheduler] Checking {} scheduled triggers", scheduled.len());
// Drop the read lock before executing
let to_execute: Vec<(String, String, String)> = scheduled.iter()
.filter_map(|t| {
if let zclaw_hands::TriggerType::Schedule { ref cron } = t.config.trigger_type {
// Simple cron matching: check if we should fire now
if Self::should_fire_cron(cron, &now) {
Some((t.config.id.clone(), t.config.hand_id.clone(), cron.clone()))
scheduled.iter()
.filter_map(|t| {
if let zclaw_hands::TriggerType::Schedule { ref cron } = t.config.trigger_type {
if Self::should_fire_cron(cron, &now) {
Some((t.config.id.clone(), t.config.hand_id.clone(), cron.clone()))
} else {
None
}
} else {
None
}
} else {
None
}
})
.collect();
})
.collect()
}; // Lock dropped here
drop(kernel_read);
// Execute due triggers (with write lock since execute_hand may need it)
// Execute due triggers (acquire lock per execution)
let now = chrono::Utc::now();
for (trigger_id, hand_id, cron_expr) in to_execute {
tracing::info!(
"[Scheduler] Firing scheduled trigger '{}' → hand '{}' (cron: {})",
trigger_id, hand_id, cron_expr
);
let kernel_read = kernel_lock.read().await;
if let Some(kernel) = kernel_read.as_ref() {
let kernel_guard = kernel_lock.lock().await;
if let Some(kernel) = kernel_guard.as_ref() {
let trigger_source = zclaw_types::TriggerSource::Scheduled {
trigger_id: trigger_id.clone(),
};
@@ -265,9 +263,12 @@ impl SchedulerService {
_ => return false,
};
// Check if current timestamp aligns with the interval
// Check if current timestamp is within the scheduler check window of an interval boundary.
// The scheduler checks every `check_interval` seconds (default 60s), so we use ±30s window.
let timestamp = now.timestamp();
timestamp % interval_secs == 0
let remainder = timestamp % interval_secs;
// Fire if we're within ±30 seconds of an interval boundary
remainder <= 30 || remainder >= (interval_secs - 30)
}
}