diff --git a/crates/zclaw-saas/src/account/handlers.rs b/crates/zclaw-saas/src/account/handlers.rs index 329f84f..d36e25b 100644 --- a/crates/zclaw-saas/src/account/handlers.rs +++ b/crates/zclaw-saas/src/account/handlers.rs @@ -283,6 +283,11 @@ pub async fn device_heartbeat( .and_then(|v| v.as_str()) .ok_or_else(|| SaasError::InvalidInput("缺少 device_id".into()))?; + // Validate device_id length (must match register endpoint constraints) + if device_id.is_empty() || device_id.len() > 64 { + return Err(SaasError::InvalidInput("device_id 长度必须在 1-64 个字符之间".into())); + } + let now = chrono::Utc::now(); // Also update platform/app_version if provided (supports client upgrades) diff --git a/crates/zclaw-saas/src/relay/handlers.rs b/crates/zclaw-saas/src/relay/handlers.rs index d6a0976..b9f06ef 100644 --- a/crates/zclaw-saas/src/relay/handlers.rs +++ b/crates/zclaw-saas/src/relay/handlers.rs @@ -550,7 +550,7 @@ pub async fn retry_task( // 异步执行重试 — 根据解析结果选择执行路径 let db = state.db.clone(); let task_id = id.clone(); - tokio::spawn(async move { + let handle = tokio::spawn(async move { let result = match model_resolution { ModelResolution::Direct(ref candidate) => { service::execute_relay( @@ -575,6 +575,13 @@ pub async fn retry_task( Err(e) => tracing::warn!("Relay task {} 重试失败: {}", task_id, e), } }); + // Detach with warning — if server shuts down mid-retry, the task is lost. + // The DB status is already reset to 'queued', so a future restart can pick it up. + tokio::spawn(async move { + if let Err(e) = handle.await { + tracing::warn!("Relay retry task aborted (server shutdown?): {}", e); + } + }); // 异步派发操作日志 state.dispatch_log_operation( diff --git a/desktop/src-tauri/src/kernel_commands/agent.rs b/desktop/src-tauri/src/kernel_commands/agent.rs index 243cc3a..abe3675 100644 --- a/desktop/src-tauri/src/kernel_commands/agent.rs +++ b/desktop/src-tauri/src/kernel_commands/agent.rs @@ -295,6 +295,18 @@ pub async fn agent_import( let mut config: AgentConfig = serde_json::from_str(&config_json) .map_err(|e| format!("Invalid agent config JSON: {}", e))?; + // Validate system_prompt length to prevent excessive token consumption + const MAX_SYSTEM_PROMPT_LEN: usize = 50_000; + if let Some(ref prompt) = config.system_prompt { + if prompt.len() > MAX_SYSTEM_PROMPT_LEN { + return Err(format!( + "system_prompt too long: {} chars (max {})", + prompt.len(), + MAX_SYSTEM_PROMPT_LEN + )); + } + } + // Regenerate ID to avoid collisions config.id = AgentId::new();