From 845c4c2d0a64159ab0438eaa687cd00ae6033a8b Mon Sep 17 00:00:00 2001 From: marchsnow-1 <263180987+MarchSnow-1@users.noreply.github.com> Date: Fri, 28 Aug 2026 14:04:02 +0900 Subject: [PATCH] =?UTF-8?q?feat:=20=E9=80=82=E9=85=8D=20Agent=20=E5=85=B3?= =?UTF-8?q?=E9=97=AD=E6=94=B6=E5=B0=BE=E8=AF=AD=E4=B9=89=EF=BC=8C=E5=BC=BA?= =?UTF-8?q?=E6=9D=80=E7=9C=8B=E9=97=A8=E7=8B=97=E5=89=8D=E7=BD=AE=E9=98=B2?= =?UTF-8?q?=20disconnect=20=E5=8D=A1=E6=AD=BB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src-tauri/src/commands/maa_agent.rs | 30 ++++++++++++++++++++--------- src-tauri/src/commands/utils.rs | 23 +++++++++++++++------- 2 files changed, 37 insertions(+), 16 deletions(-) diff --git a/src-tauri/src/commands/maa_agent.rs b/src-tauri/src/commands/maa_agent.rs index d276381c..adfb65a9 100644 --- a/src-tauri/src/commands/maa_agent.rs +++ b/src-tauri/src/commands/maa_agent.rs @@ -527,6 +527,13 @@ pub async fn start_tasks_impl( ) -> Result, String> { info!("start_tasks_impl called"); + // 仅新一次运行时清理上一轮残留 agent(reset_state=false 的追加批次 + // 复用现有 agent,不能断开)。常态下上一轮已由任务结束时的同步 + // disconnect 清理,此处为幂等兜底:防收尾超时/卡死时的进程残留 + if reset_state { + let _ = stop_agent_impl(maa_state, &instance_id); + } + info!("instance_id: {}", instance_id); info!("tasks: {:?}", tasks); info!("agent_configs: {:?}", agent_configs); @@ -857,20 +864,16 @@ pub fn stop_agent_impl(maa_state: &Arc, instance_id: &str) -> Result<( children.len() ); - // 同步断开所有 client:确保 custom 反注册在返回前完成, - // 避免旧 agent 的反注册晚于新 agent 的注册,误删新 agent 的同名 custom 条目。 - for client in &clients { - let _ = client.disconnect(); - } - drop(clients); // Drop 同步执行 clear_custom_registration() - - // 子进程收尾放到后台:等待自然退出,超时强杀兜底,避免进程泄漏。 + // 先启动强杀看门狗,再同步断开:disconnect 等 ShutDownResponse 期间若 + // agent 收尾卡死(RPC 超时默认不设限),看门狗到期杀进程 → socket 断开 → + // 阻塞中的 disconnect 立即报错返回,调用线程不会被永久钉死。 + // 正常路径 agent 收尾完自行退出,看门狗轮询立即收获、不会触发 kill。 thread::spawn(move || { for (i, mut child) in children.into_iter().enumerate() { debug!("Waiting for agent process #{} to exit...", i); let start = std::time::Instant::now(); - let timeout = std::time::Duration::from_secs(5); + let timeout = std::time::Duration::from_secs(15); let mut exited = false; while start.elapsed() < timeout { @@ -899,6 +902,15 @@ pub fn stop_agent_impl(maa_state: &Arc, instance_id: &str) -> Result<( } }); + // 同步断开所有 client:确保 custom 反注册在返回前完成, + // 避免旧 agent 的反注册晚于新 agent 的注册,误删新 agent 的同名 custom 条目。 + // disconnect 由框架保证先排空事件转发再发 ShutDownRequest,并等待 + // agent 收尾完成(ShutDownResponse)后返回。 + for client in &clients { + let _ = client.disconnect(); + } + drop(clients); // Drop 同步执行 clear_custom_registration() + Ok(()) } diff --git a/src-tauri/src/commands/utils.rs b/src-tauri/src/commands/utils.rs index 84e0ab21..7096db89 100644 --- a/src-tauri/src/commands/utils.rs +++ b/src-tauri/src/commands/utils.rs @@ -6,6 +6,7 @@ use super::types::{MaaCallbackEvent, MaaState, StateChangedEvent}; use crate::ws_broadcast::{WsBroadcast, WsEvent}; use std::path::PathBuf; use std::sync::Arc; +use std::thread; use tauri::{AppHandle, Emitter, Manager}; /// 发送回调事件到前端(Tauri WebView + WebSocket 浏览器客户端) @@ -142,13 +143,21 @@ pub fn handle_task_callback( if all_done { emit_state_changed(app, instance_id, "tasks-completed"); - // 实例内全部任务结束后清理旧 agent,避免 agent 进程跨轮累积 - if let Err(e) = super::maa_agent::stop_agent_impl(maa_state, instance_id) { - log::warn!( - "[handle_task_callback] Failed to stop agents after tasks completed: {}", - e - ); - } + // 实例内全部任务结束后清理旧 agent,避免 agent 进程跨轮累积。 + // 挪到后台线程执行:disconnect 会同步等待 agent 收尾完成 + // (ShutDownResponse,可达数秒),而本回调运行在框架事件分发 + // 线程上,阻塞它会卡住后续所有事件。收尾顺序由 disconnect + // 内部保证,mem::take 幂等,无需额外延迟或守卫。 + let maa_state = Arc::clone(maa_state); + let instance_id = instance_id.to_string(); + thread::spawn(move || { + if let Err(e) = super::maa_agent::stop_agent_impl(&maa_state, &instance_id) { + log::warn!( + "[handle_task_callback] Failed to stop agents after tasks completed: {}", + e + ); + } + }); } }