@@ -9,7 +9,6 @@ use std::time::SystemTime;
99use tauri:: { AppHandle , Emitter , Manager } ;
1010use tokio:: process:: { Child , Command } ;
1111use tokio:: sync:: Mutex ;
12- use uuid;
1312
1413/// Global state to track current Claude process
1514pub struct ClaudeProcessState {
@@ -821,7 +820,7 @@ pub async fn execute_claude_code(
821820 . stdout ( Stdio :: piped ( ) )
822821 . stderr ( Stdio :: piped ( ) ) ;
823822
824- spawn_claude_process ( app, cmd) . await
823+ spawn_claude_process ( app, cmd, prompt , model , project_path ) . await
825824}
826825
827826/// Continue an existing Claude Code conversation with streaming output
@@ -861,7 +860,7 @@ pub async fn continue_claude_code(
861860 . stdout ( Stdio :: piped ( ) )
862861 . stderr ( Stdio :: piped ( ) ) ;
863862
864- spawn_claude_process ( app, cmd) . await
863+ spawn_claude_process ( app, cmd, prompt , model , project_path ) . await
865864}
866865
867866/// Resume an existing Claude Code session by ID with streaming output
@@ -904,7 +903,7 @@ pub async fn resume_claude_code(
904903 . stdout ( Stdio :: piped ( ) )
905904 . stderr ( Stdio :: piped ( ) ) ;
906905
907- spawn_claude_process ( app, cmd) . await
906+ spawn_claude_process ( app, cmd, prompt , model , project_path ) . await
908907}
909908
910909/// Cancel the currently running Claude Code execution
@@ -918,40 +917,83 @@ pub async fn cancel_claude_execution(
918917 session_id
919918 ) ;
920919
921- let claude_state = app. state :: < ClaudeProcessState > ( ) ;
922- let mut current_process = claude_state. current_process . lock ( ) . await ;
923-
924- if let Some ( mut child) = current_process. take ( ) {
925- // Try to get the PID before killing
926- let pid = child. id ( ) ;
927- log:: info!( "Attempting to kill Claude process with PID: {:?}" , pid) ;
920+ let killed = if let Some ( sid) = & session_id {
921+ // Try to find and kill via ProcessRegistry first
922+ let registry = app. state :: < crate :: process:: ProcessRegistryState > ( ) ;
923+ if let Ok ( Some ( process_info) ) = registry. 0 . get_claude_session_by_id ( sid) {
924+ match registry. 0 . kill_process ( process_info. run_id ) . await {
925+ Ok ( success) => success,
926+ Err ( e) => {
927+ log:: warn!( "Failed to kill via registry: {}" , e) ;
928+ false
929+ }
930+ }
931+ } else {
932+ false
933+ }
934+ } else {
935+ false
936+ } ;
928937
929- // Kill the process
930- match child . kill ( ) . await {
931- Ok ( _ ) => {
932- log :: info! ( "Successfully killed Claude process" ) ;
938+ // If registry kill didn't work, try the legacy approach
939+ if !killed {
940+ let claude_state = app . state :: < ClaudeProcessState > ( ) ;
941+ let mut current_process = claude_state . current_process . lock ( ) . await ;
933942
934- // If we have a session ID, emit session-specific events
935- if let Some ( sid) = session_id {
936- let _ = app. emit ( & format ! ( "claude-cancelled:{}" , sid) , true ) ;
937- tokio:: time:: sleep ( tokio:: time:: Duration :: from_millis ( 100 ) ) . await ;
938- let _ = app. emit ( & format ! ( "claude-complete:{}" , sid) , false ) ;
943+ if let Some ( mut child) = current_process. take ( ) {
944+ // Try to get the PID before killing
945+ let pid = child. id ( ) ;
946+ log:: info!( "Attempting to kill Claude process with PID: {:?}" , pid) ;
947+
948+ // Kill the process
949+ match child. kill ( ) . await {
950+ Ok ( _) => {
951+ log:: info!( "Successfully killed Claude process" ) ;
952+ }
953+ Err ( e) => {
954+ log:: error!( "Failed to kill Claude process: {}" , e) ;
955+ return Err ( format ! ( "Failed to kill Claude process: {}" , e) ) ;
939956 }
940-
941- // Also emit generic events for backward compatibility
942- let _ = app. emit ( "claude-cancelled" , true ) ;
943- tokio:: time:: sleep ( tokio:: time:: Duration :: from_millis ( 100 ) ) . await ;
944- let _ = app. emit ( "claude-complete" , false ) ;
945- Ok ( ( ) )
946- }
947- Err ( e) => {
948- log:: error!( "Failed to kill Claude process: {}" , e) ;
949- Err ( format ! ( "Failed to kill Claude process: {}" , e) )
950957 }
958+ } else {
959+ log:: warn!( "No active Claude process to cancel" ) ;
951960 }
961+ }
962+
963+ // Emit cancellation events
964+ if let Some ( sid) = session_id {
965+ let _ = app. emit ( & format ! ( "claude-cancelled:{}" , sid) , true ) ;
966+ tokio:: time:: sleep ( tokio:: time:: Duration :: from_millis ( 100 ) ) . await ;
967+ let _ = app. emit ( & format ! ( "claude-complete:{}" , sid) , false ) ;
968+ }
969+
970+ // Also emit generic events for backward compatibility
971+ let _ = app. emit ( "claude-cancelled" , true ) ;
972+ tokio:: time:: sleep ( tokio:: time:: Duration :: from_millis ( 100 ) ) . await ;
973+ let _ = app. emit ( "claude-complete" , false ) ;
974+
975+ Ok ( ( ) )
976+ }
977+
978+ /// Get all running Claude sessions
979+ #[ tauri:: command]
980+ pub async fn list_running_claude_sessions (
981+ registry : tauri:: State < ' _ , crate :: process:: ProcessRegistryState > ,
982+ ) -> Result < Vec < crate :: process:: ProcessInfo > , String > {
983+ registry. 0 . get_running_claude_sessions ( )
984+ }
985+
986+ /// Get live output from a Claude session
987+ #[ tauri:: command]
988+ pub async fn get_claude_session_output (
989+ registry : tauri:: State < ' _ , crate :: process:: ProcessRegistryState > ,
990+ session_id : String ,
991+ ) -> Result < String , String > {
992+ // Find the process by session ID
993+ if let Some ( process_info) = registry. 0 . get_claude_session_by_id ( & session_id) ? {
994+ registry. 0 . get_live_output ( process_info. run_id )
952995 } else {
953- log:: warn!( "No active Claude process to cancel" ) ;
954- Ok ( ( ) )
996+ Ok ( String :: new ( ) )
955997 }
956998}
957999
@@ -1151,18 +1193,9 @@ fn get_claude_settings_sync(_app: &AppHandle) -> Result<ClaudeSettings, String>
11511193}
11521194
11531195/// Helper function to spawn Claude process and handle streaming
1154- async fn spawn_claude_process ( app : AppHandle , mut cmd : Command ) -> Result < ( ) , String > {
1196+ async fn spawn_claude_process ( app : AppHandle , mut cmd : Command , prompt : String , model : String , project_path : String ) -> Result < ( ) , String > {
11551197 use tokio:: io:: { AsyncBufReadExt , BufReader } ;
1156-
1157- // Generate a unique session ID for this Claude Code session
1158- let session_id = format ! (
1159- "claude-{}-{}" ,
1160- std:: time:: SystemTime :: now( )
1161- . duration_since( std:: time:: UNIX_EPOCH )
1162- . unwrap_or_default( )
1163- . as_millis( ) ,
1164- uuid:: Uuid :: new_v4( ) . to_string( )
1165- ) ;
1198+ use std:: sync:: Mutex ;
11661199
11671200 // Spawn the process
11681201 let mut child = cmd
@@ -1174,17 +1207,20 @@ async fn spawn_claude_process(app: AppHandle, mut cmd: Command) -> Result<(), St
11741207 let stderr = child. stderr . take ( ) . ok_or ( "Failed to get stderr" ) ?;
11751208
11761209 // Get the child PID for logging
1177- let pid = child. id ( ) ;
1210+ let pid = child. id ( ) . unwrap_or ( 0 ) ;
11781211 log:: info!(
1179- "Spawned Claude process with PID: {:?} and session ID: {}" ,
1180- pid,
1181- session_id
1212+ "Spawned Claude process with PID: {:?}" ,
1213+ pid
11821214 ) ;
11831215
1184- // Create readers
1216+ // Create readers first (before moving child)
11851217 let stdout_reader = BufReader :: new ( stdout) ;
11861218 let stderr_reader = BufReader :: new ( stderr) ;
11871219
1220+ // We'll extract the session ID from Claude's init message
1221+ let session_id_holder: Arc < Mutex < Option < String > > > = Arc :: new ( Mutex :: new ( None ) ) ;
1222+ let run_id_holder: Arc < Mutex < Option < i64 > > > = Arc :: new ( Mutex :: new ( None ) ) ;
1223+
11881224 // Store the child process in the global state (for backward compatibility)
11891225 let claude_state = app. state :: < ClaudeProcessState > ( ) ;
11901226 {
@@ -1199,26 +1235,73 @@ async fn spawn_claude_process(app: AppHandle, mut cmd: Command) -> Result<(), St
11991235
12001236 // Spawn tasks to read stdout and stderr
12011237 let app_handle = app. clone ( ) ;
1202- let session_id_clone = session_id. clone ( ) ;
1238+ let session_id_holder_clone = session_id_holder. clone ( ) ;
1239+ let run_id_holder_clone = run_id_holder. clone ( ) ;
1240+ let registry = app. state :: < crate :: process:: ProcessRegistryState > ( ) ;
1241+ let registry_clone = registry. 0 . clone ( ) ;
1242+ let project_path_clone = project_path. clone ( ) ;
1243+ let prompt_clone = prompt. clone ( ) ;
1244+ let model_clone = model. clone ( ) ;
12031245 let stdout_task = tokio:: spawn ( async move {
12041246 let mut lines = stdout_reader. lines ( ) ;
12051247 while let Ok ( Some ( line) ) = lines. next_line ( ) . await {
12061248 log:: debug!( "Claude stdout: {}" , line) ;
1207- // Emit the line to the frontend with session isolation
1208- let _ = app_handle. emit ( & format ! ( "claude-output:{}" , session_id_clone) , & line) ;
1249+
1250+ // Parse the line to check for init message with session ID
1251+ if let Ok ( msg) = serde_json:: from_str :: < serde_json:: Value > ( & line) {
1252+ if msg[ "type" ] == "system" && msg[ "subtype" ] == "init" {
1253+ if let Some ( claude_session_id) = msg[ "session_id" ] . as_str ( ) {
1254+ let mut session_id_guard = session_id_holder_clone. lock ( ) . unwrap ( ) ;
1255+ if session_id_guard. is_none ( ) {
1256+ * session_id_guard = Some ( claude_session_id. to_string ( ) ) ;
1257+ log:: info!( "Extracted Claude session ID: {}" , claude_session_id) ;
1258+
1259+ // Now register with ProcessRegistry using Claude's session ID
1260+ match registry_clone. register_claude_session (
1261+ claude_session_id. to_string ( ) ,
1262+ pid,
1263+ project_path_clone. clone ( ) ,
1264+ prompt_clone. clone ( ) ,
1265+ model_clone. clone ( ) ,
1266+ ) {
1267+ Ok ( run_id) => {
1268+ log:: info!( "Registered Claude session with run_id: {}" , run_id) ;
1269+ let mut run_id_guard = run_id_holder_clone. lock ( ) . unwrap ( ) ;
1270+ * run_id_guard = Some ( run_id) ;
1271+ }
1272+ Err ( e) => {
1273+ log:: error!( "Failed to register Claude session: {}" , e) ;
1274+ }
1275+ }
1276+ }
1277+ }
1278+ }
1279+ }
1280+
1281+ // Store live output in registry if we have a run_id
1282+ if let Some ( run_id) = * run_id_holder_clone. lock ( ) . unwrap ( ) {
1283+ let _ = registry_clone. append_live_output ( run_id, & line) ;
1284+ }
1285+
1286+ // Emit the line to the frontend with session isolation if we have session ID
1287+ if let Some ( ref session_id) = * session_id_holder_clone. lock ( ) . unwrap ( ) {
1288+ let _ = app_handle. emit ( & format ! ( "claude-output:{}" , session_id) , & line) ;
1289+ }
12091290 // Also emit to the generic event for backward compatibility
12101291 let _ = app_handle. emit ( "claude-output" , & line) ;
12111292 }
12121293 } ) ;
12131294
12141295 let app_handle_stderr = app. clone ( ) ;
1215- let session_id_clone2 = session_id . clone ( ) ;
1296+ let session_id_holder_clone2 = session_id_holder . clone ( ) ;
12161297 let stderr_task = tokio:: spawn ( async move {
12171298 let mut lines = stderr_reader. lines ( ) ;
12181299 while let Ok ( Some ( line) ) = lines. next_line ( ) . await {
12191300 log:: error!( "Claude stderr: {}" , line) ;
1220- // Emit error lines to the frontend with session isolation
1221- let _ = app_handle_stderr. emit ( & format ! ( "claude-error:{}" , session_id_clone2) , & line) ;
1301+ // Emit error lines to the frontend with session isolation if we have session ID
1302+ if let Some ( ref session_id) = * session_id_holder_clone2. lock ( ) . unwrap ( ) {
1303+ let _ = app_handle_stderr. emit ( & format ! ( "claude-error:{}" , session_id) , & line) ;
1304+ }
12221305 // Also emit to the generic event for backward compatibility
12231306 let _ = app_handle_stderr. emit ( "claude-error" , & line) ;
12241307 }
@@ -1227,7 +1310,9 @@ async fn spawn_claude_process(app: AppHandle, mut cmd: Command) -> Result<(), St
12271310 // Wait for the process to complete
12281311 let app_handle_wait = app. clone ( ) ;
12291312 let claude_state_wait = claude_state. current_process . clone ( ) ;
1230- let session_id_clone3 = session_id. clone ( ) ;
1313+ let session_id_holder_clone3 = session_id_holder. clone ( ) ;
1314+ let run_id_holder_clone2 = run_id_holder. clone ( ) ;
1315+ let registry_clone2 = registry. 0 . clone ( ) ;
12311316 tokio:: spawn ( async move {
12321317 let _ = stdout_task. await ;
12331318 let _ = stderr_task. await ;
@@ -1240,35 +1325,38 @@ async fn spawn_claude_process(app: AppHandle, mut cmd: Command) -> Result<(), St
12401325 log:: info!( "Claude process exited with status: {}" , status) ;
12411326 // Add a small delay to ensure all messages are processed
12421327 tokio:: time:: sleep ( tokio:: time:: Duration :: from_millis ( 100 ) ) . await ;
1243- let _ = app_handle_wait. emit (
1244- & format ! ( "claude-complete:{}" , session_id_clone3) ,
1245- status. success ( ) ,
1246- ) ;
1328+ if let Some ( ref session_id) = * session_id_holder_clone3. lock ( ) . unwrap ( ) {
1329+ let _ = app_handle_wait. emit (
1330+ & format ! ( "claude-complete:{}" , session_id) ,
1331+ status. success ( ) ,
1332+ ) ;
1333+ }
12471334 // Also emit to the generic event for backward compatibility
12481335 let _ = app_handle_wait. emit ( "claude-complete" , status. success ( ) ) ;
12491336 }
12501337 Err ( e) => {
12511338 log:: error!( "Failed to wait for Claude process: {}" , e) ;
12521339 // Add a small delay to ensure all messages are processed
12531340 tokio:: time:: sleep ( tokio:: time:: Duration :: from_millis ( 100 ) ) . await ;
1254- let _ = app_handle_wait
1255- . emit ( & format ! ( "claude-complete:{}" , session_id_clone3) , false ) ;
1341+ if let Some ( ref session_id) = * session_id_holder_clone3. lock ( ) . unwrap ( ) {
1342+ let _ = app_handle_wait
1343+ . emit ( & format ! ( "claude-complete:{}" , session_id) , false ) ;
1344+ }
12561345 // Also emit to the generic event for backward compatibility
12571346 let _ = app_handle_wait. emit ( "claude-complete" , false ) ;
12581347 }
12591348 }
12601349 }
12611350
1351+ // Unregister from ProcessRegistry if we have a run_id
1352+ if let Some ( run_id) = * run_id_holder_clone2. lock ( ) . unwrap ( ) {
1353+ let _ = registry_clone2. unregister_process ( run_id) ;
1354+ }
1355+
12621356 // Clear the process from state
12631357 * current_process = None ;
12641358 } ) ;
12651359
1266- // Return the session ID to the frontend
1267- let _ = app. emit (
1268- & format ! ( "claude-session-started:{}" , session_id) ,
1269- session_id. clone ( ) ,
1270- ) ;
1271-
12721360 Ok ( ( ) )
12731361}
12741362
0 commit comments