@@ -76,6 +76,21 @@ pub(crate) struct DesktopSessionWithTurnsRestore {
7676 pub turns : Vec < DialogTurnData > ,
7777}
7878
79+ fn overlay_live_session_state ( restored : & mut Session , live : Option < Session > ) {
80+ let Some ( live) = live else {
81+ return ;
82+ } ;
83+ if live. session_id != restored. session_id {
84+ return ;
85+ }
86+
87+ // Disk state deliberately stores Processing as Idle so a process restart
88+ // never revives work. A view served by the process that still owns the
89+ // runtime must expose its live state, otherwise a Peer controller treats
90+ // an executing turn as interrupted history and drops subsequent chunks.
91+ restored. state = live. state ;
92+ }
93+
7994#[ derive( Clone ) ]
8095struct ResolvedDesktopSessionScope {
8196 workspace_path : String ,
@@ -510,7 +525,7 @@ impl DesktopSessionApplication {
510525 let resolve_storage_path_duration_ms =
511526 path_started_at. elapsed ( ) . as_millis ( ) . min ( u64:: MAX as u128 ) as u64 ;
512527 on_storage_path_resolved ( resolve_storage_path_duration_ms) ;
513- let ( session, turns, total_turn_count, mut timings) = self
528+ let ( mut session, turns, total_turn_count, mut timings) = self
514529 . compatibility
515530 . restore_session_view_from_storage_path (
516531 & storage_path,
@@ -520,6 +535,11 @@ impl DesktopSessionApplication {
520535 )
521536 . await
522537 . map_err ( |error| DesktopSessionApplicationError :: Core ( error. to_string ( ) ) ) ?;
538+ let live_session = self
539+ . compatibility
540+ . loaded_session_snapshot ( session_id)
541+ . map_err ( |error| DesktopSessionApplicationError :: Core ( error. to_string ( ) ) ) ?;
542+ overlay_live_session_state ( & mut session, live_session) ;
523543 timings. resolve_storage_path_duration_ms = resolve_storage_path_duration_ms;
524544 Ok ( DesktopSessionViewRestore {
525545 session,
@@ -765,6 +785,48 @@ mod tests {
765785 ) ;
766786 }
767787
788+ #[ test]
789+ fn live_processing_state_overlays_sanitized_view_state ( ) {
790+ let mut restored = Session :: new_with_id (
791+ "session-1" . to_string ( ) ,
792+ "Restored" . to_string ( ) ,
793+ "agentic" . to_string ( ) ,
794+ Default :: default ( ) ,
795+ ) ;
796+ let mut live = restored. clone ( ) ;
797+ live. state = bitfun_core:: agentic:: core:: SessionState :: Processing {
798+ current_turn_id : "turn-1" . to_string ( ) ,
799+ phase : bitfun_core:: agentic:: core:: ProcessingPhase :: Streaming ,
800+ } ;
801+
802+ overlay_live_session_state ( & mut restored, Some ( live) ) ;
803+
804+ assert ! ( matches!(
805+ restored. state,
806+ bitfun_core:: agentic:: core:: SessionState :: Processing {
807+ ref current_turn_id,
808+ ..
809+ } if current_turn_id == "turn-1"
810+ ) ) ;
811+ }
812+
813+ #[ test]
814+ fn missing_live_session_keeps_persisted_view_state ( ) {
815+ let mut restored = Session :: new_with_id (
816+ "session-1" . to_string ( ) ,
817+ "Restored" . to_string ( ) ,
818+ "agentic" . to_string ( ) ,
819+ Default :: default ( ) ,
820+ ) ;
821+
822+ overlay_live_session_state ( & mut restored, None ) ;
823+
824+ assert ! ( matches!(
825+ restored. state,
826+ bitfun_core:: agentic:: core:: SessionState :: Idle
827+ ) ) ;
828+ }
829+
768830 #[ tokio:: test]
769831 async fn local_session_storage_identity_survives_workspace_removal ( ) {
770832 let root = std:: env:: temp_dir ( ) . join ( format ! (
0 commit comments