@@ -6,10 +6,25 @@ use std::time::SystemTime;
66use std:: io:: { BufRead , BufReader } ;
77use std:: process:: Stdio ;
88use tauri:: { AppHandle , Emitter , Manager } ;
9- use tokio:: process:: Command ;
9+ use tokio:: process:: { Command , Child } ;
10+ use tokio:: sync:: Mutex ;
11+ use std:: sync:: Arc ;
1012use crate :: process:: ProcessHandle ;
1113use crate :: checkpoint:: { CheckpointResult , CheckpointDiff , SessionTimeline , Checkpoint } ;
1214
15+ /// Global state to track current Claude process
16+ pub struct ClaudeProcessState {
17+ pub current_process : Arc < Mutex < Option < Child > > > ,
18+ }
19+
20+ impl Default for ClaudeProcessState {
21+ fn default ( ) -> Self {
22+ Self {
23+ current_process : Arc :: new ( Mutex :: new ( None ) ) ,
24+ }
25+ }
26+ }
27+
1328/// Represents a project in the ~/.claude/projects directory
1429#[ derive( Debug , Clone , Serialize , Deserialize ) ]
1530pub struct Project {
@@ -925,6 +940,41 @@ pub async fn resume_claude_code(
925940 spawn_claude_process ( app, cmd) . await
926941}
927942
943+ /// Cancel the currently running Claude Code execution
944+ #[ tauri:: command]
945+ pub async fn cancel_claude_execution ( app : AppHandle ) -> Result < ( ) , String > {
946+ log:: info!( "Cancelling Claude Code execution" ) ;
947+
948+ let claude_state = app. state :: < ClaudeProcessState > ( ) ;
949+ let mut current_process = claude_state. current_process . lock ( ) . await ;
950+
951+ if let Some ( mut child) = current_process. take ( ) {
952+ // Try to get the PID before killing
953+ let pid = child. id ( ) ;
954+ log:: info!( "Attempting to kill Claude process with PID: {:?}" , pid) ;
955+
956+ // Kill the process
957+ match child. kill ( ) . await {
958+ Ok ( _) => {
959+ log:: info!( "Successfully killed Claude process" ) ;
960+ // Emit cancellation event
961+ let _ = app. emit ( "claude-cancelled" , true ) ;
962+ // Also emit complete with false to indicate failure
963+ tokio:: time:: sleep ( tokio:: time:: Duration :: from_millis ( 100 ) ) . await ;
964+ let _ = app. emit ( "claude-complete" , false ) ;
965+ Ok ( ( ) )
966+ }
967+ Err ( e) => {
968+ log:: error!( "Failed to kill Claude process: {}" , e) ;
969+ Err ( format ! ( "Failed to kill Claude process: {}" , e) )
970+ }
971+ }
972+ } else {
973+ log:: warn!( "No active Claude process to cancel" ) ;
974+ Ok ( ( ) )
975+ }
976+ }
977+
928978/// Helper function to check if sandboxing should be used based on settings
929979fn should_use_sandbox ( app : & AppHandle ) -> Result < bool , String > {
930980 // First check if sandboxing is even available on this platform
@@ -1097,10 +1147,21 @@ async fn spawn_claude_process(app: AppHandle, mut cmd: Command) -> Result<(), St
10971147 let stdout = child. stdout . take ( ) . ok_or ( "Failed to get stdout" ) ?;
10981148 let stderr = child. stderr . take ( ) . ok_or ( "Failed to get stderr" ) ?;
10991149
1150+ // Get the child PID for logging
1151+ let pid = child. id ( ) ;
1152+ log:: info!( "Spawned Claude process with PID: {:?}" , pid) ;
1153+
11001154 // Create readers
11011155 let stdout_reader = BufReader :: new ( stdout) ;
11021156 let stderr_reader = BufReader :: new ( stderr) ;
11031157
1158+ // Store the child process in the global state
1159+ let claude_state = app. state :: < ClaudeProcessState > ( ) ;
1160+ {
1161+ let mut current_process = claude_state. current_process . lock ( ) . await ;
1162+ * current_process = Some ( child) ;
1163+ }
1164+
11041165 // Spawn tasks to read stdout and stderr
11051166 let app_handle = app. clone ( ) ;
11061167 let stdout_task = tokio:: spawn ( async move {
@@ -1123,24 +1184,33 @@ async fn spawn_claude_process(app: AppHandle, mut cmd: Command) -> Result<(), St
11231184 } ) ;
11241185
11251186 // Wait for the process to complete
1187+ let app_handle_wait = app. clone ( ) ;
1188+ let claude_state_wait = claude_state. current_process . clone ( ) ;
11261189 tokio:: spawn ( async move {
11271190 let _ = stdout_task. await ;
11281191 let _ = stderr_task. await ;
11291192
1130- match child. wait ( ) . await {
1131- Ok ( status) => {
1132- log:: info!( "Claude process exited with status: {}" , status) ;
1133- // Add a small delay to ensure all messages are processed
1134- tokio:: time:: sleep ( tokio:: time:: Duration :: from_millis ( 100 ) ) . await ;
1135- let _ = app. emit ( "claude-complete" , status. success ( ) ) ;
1136- }
1137- Err ( e) => {
1138- log:: error!( "Failed to wait for Claude process: {}" , e) ;
1139- // Add a small delay to ensure all messages are processed
1140- tokio:: time:: sleep ( tokio:: time:: Duration :: from_millis ( 100 ) ) . await ;
1141- let _ = app. emit ( "claude-complete" , false ) ;
1193+ // Get the child from the state to wait on it
1194+ let mut current_process = claude_state_wait. lock ( ) . await ;
1195+ if let Some ( mut child) = current_process. take ( ) {
1196+ match child. wait ( ) . await {
1197+ Ok ( status) => {
1198+ log:: info!( "Claude process exited with status: {}" , status) ;
1199+ // Add a small delay to ensure all messages are processed
1200+ tokio:: time:: sleep ( tokio:: time:: Duration :: from_millis ( 100 ) ) . await ;
1201+ let _ = app_handle_wait. emit ( "claude-complete" , status. success ( ) ) ;
1202+ }
1203+ Err ( e) => {
1204+ log:: error!( "Failed to wait for Claude process: {}" , e) ;
1205+ // Add a small delay to ensure all messages are processed
1206+ tokio:: time:: sleep ( tokio:: time:: Duration :: from_millis ( 100 ) ) . await ;
1207+ let _ = app_handle_wait. emit ( "claude-complete" , false ) ;
1208+ }
11421209 }
11431210 }
1211+
1212+ // Clear the process from state
1213+ * current_process = None ;
11441214 } ) ;
11451215
11461216 Ok ( ( ) )
0 commit comments