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
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:
@@ -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};
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user