@@ -34,6 +34,7 @@ pub mod commands;
3434pub mod compact;
3535pub mod history;
3636pub mod ide;
37+ pub mod persistence;
3738pub mod prompts;
3839pub mod session;
3940pub mod tools;
@@ -117,7 +118,7 @@ fn get_system_prompt(project_path: &Path, query: Option<&str>, plan_mode: PlanMo
117118 }
118119 // Then check if it's DevOps generation (Docker, Terraform, Helm)
119120 if prompts:: is_generation_query ( q) {
120- return prompts:: get_devops_prompt ( project_path) ;
121+ return prompts:: get_devops_prompt ( project_path, Some ( q ) ) ;
121122 }
122123 }
123124 // Default to analysis prompt
@@ -134,6 +135,10 @@ pub async fn run_interactive(
134135
135136 let mut session = ChatSession :: new ( project_path, provider, model) ;
136137
138+ // Terminal layout for split screen is disabled for now - see notes below
139+ // let terminal_layout = ui::TerminalLayout::new();
140+ // let layout_state = terminal_layout.state();
141+
137142 // Initialize conversation history with compaction support
138143 let mut conversation_history = ConversationHistory :: new ( ) ;
139144
@@ -176,6 +181,19 @@ pub async fn run_interactive(
176181
177182 session. print_banner ( ) ;
178183
184+ // NOTE: Terminal layout with ANSI scroll regions is disabled for now.
185+ // The scroll region approach conflicts with the existing input/output flow.
186+ // TODO: Implement proper scroll region support that integrates with the input handler.
187+ // For now, we rely on the pause/resume mechanism in progress indicator.
188+ //
189+ // if let Err(e) = terminal_layout.init() {
190+ // eprintln!(
191+ // "{}",
192+ // format!("Note: Terminal layout initialization failed: {}. Using fallback mode.", e)
193+ // .dimmed()
194+ // );
195+ // }
196+
179197 // Raw Rig messages for multi-turn - preserves Reasoning blocks for thinking
180198 // Our ConversationHistory only stores text summaries, but rig needs full Message structure
181199 let mut raw_chat_history: Vec < rig:: completion:: Message > = Vec :: new ( ) ;
@@ -185,6 +203,9 @@ pub async fn run_interactive(
185203 // Auto-accept mode for plan execution (skips write confirmations)
186204 let mut auto_accept_writes = false ;
187205
206+ // Initialize session recorder for conversation persistence
207+ let mut session_recorder = persistence:: SessionRecorder :: new ( project_path) ;
208+
188209 loop {
189210 // Show conversation status if we have history
190211 if !conversation_history. is_empty ( ) {
@@ -323,6 +344,12 @@ pub async fn run_interactive(
323344 // Create hook for Claude Code style tool display
324345 let hook = ToolDisplayHook :: new ( ) ;
325346
347+ // Create progress indicator for visual feedback during generation
348+ let progress = ui:: GenerationIndicator :: new ( ) ;
349+ // Layout connection disabled - using inline progress mode
350+ // progress.state().set_layout(layout_state.clone());
351+ hook. set_progress_state ( progress. state ( ) ) . await ;
352+
326353 let project_path_buf = session. project_path . clone ( ) ;
327354 // Select prompt based on query type (analysis vs generation) and plan mode
328355 let preamble = get_system_prompt (
@@ -336,7 +363,24 @@ pub async fn run_interactive(
336363 // Note: using raw_chat_history directly which preserves Reasoning blocks
337364 // This is needed for extended thinking to work with multi-turn conversations
338365
339- let response = match session. provider {
366+ // Get progress state for interrupt detection
367+ let progress_state = progress. state ( ) ;
368+
369+ // Use tokio::select! to race the API call against Ctrl+C
370+ // This allows immediate cancellation, not just between tool calls
371+ let mut user_interrupted = false ;
372+
373+ // API call with Ctrl+C interrupt support
374+ let response = tokio:: select! {
375+ biased; // Check ctrl_c first for faster response
376+
377+ _ = tokio:: signal:: ctrl_c( ) => {
378+ user_interrupted = true ;
379+ Err :: <String , String >( "User cancelled" . to_string( ) )
380+ }
381+
382+ result = async {
383+ match session. provider {
340384 ProviderType :: OpenAI => {
341385 let client = openai:: Client :: from_env( ) ;
342386 // For GPT-5.x reasoning models, enable reasoning with summary output
@@ -368,7 +412,8 @@ pub async fn run_interactive(
368412 . tool( TerraformValidateTool :: new( project_path_buf. clone( ) ) )
369413 . tool( TerraformInstallTool :: new( ) )
370414 . tool( ReadFileTool :: new( project_path_buf. clone( ) ) )
371- . tool ( ListDirectoryTool :: new ( project_path_buf. clone ( ) ) ) ;
415+ . tool( ListDirectoryTool :: new( project_path_buf. clone( ) ) )
416+ . tool( WebFetchTool :: new( ) ) ;
372417
373418 // Add tools based on mode
374419 if is_planning {
@@ -446,7 +491,8 @@ pub async fn run_interactive(
446491 . tool( TerraformValidateTool :: new( project_path_buf. clone( ) ) )
447492 . tool( TerraformInstallTool :: new( ) )
448493 . tool( ReadFileTool :: new( project_path_buf. clone( ) ) )
449- . tool ( ListDirectoryTool :: new ( project_path_buf. clone ( ) ) ) ;
494+ . tool( ListDirectoryTool :: new( project_path_buf. clone( ) ) )
495+ . tool( WebFetchTool :: new( ) ) ;
450496
451497 // Add tools based on mode
452498 if is_planning {
@@ -528,7 +574,8 @@ pub async fn run_interactive(
528574 . tool( TerraformValidateTool :: new( project_path_buf. clone( ) ) )
529575 . tool( TerraformInstallTool :: new( ) )
530576 . tool( ReadFileTool :: new( project_path_buf. clone( ) ) )
531- . tool ( ListDirectoryTool :: new ( project_path_buf. clone ( ) ) ) ;
577+ . tool( ListDirectoryTool :: new( project_path_buf. clone( ) ) )
578+ . tool( WebFetchTool :: new( ) ) ;
532579
533580 // Add tools based on mode
534581 if is_planning {
@@ -579,9 +626,17 @@ pub async fn run_interactive(
579626 . with_hook( hook. clone( ) )
580627 . multi_turn( 50 )
581628 . await
582- }
629+ }
630+ } . map_err( |e| e. to_string( ) )
631+ } => result
583632 } ;
584633
634+ // Stop the progress indicator before handling the response
635+ progress. stop ( ) . await ;
636+
637+ // Suppress unused variable warnings
638+ let _ = ( & progress_state, user_interrupted) ;
639+
585640 match response {
586641 Ok ( text) => {
587642 // Show final response
@@ -663,6 +718,16 @@ pub async fn run_interactive(
663718 . history
664719 . push ( ( "assistant" . to_string ( ) , text. clone ( ) ) ) ;
665720
721+ // Record to persistent session storage
722+ session_recorder. record_user_message ( & input) ;
723+ session_recorder. record_assistant_message ( & text, Some ( & tool_calls) ) ;
724+ if let Err ( e) = session_recorder. save ( ) {
725+ eprintln ! (
726+ "{}" ,
727+ format!( " Warning: Failed to save session: {}" , e) . dimmed( )
728+ ) ;
729+ }
730+
666731 // Check if plan_create was called - show interactive menu
667732 if let Some ( plan_info) = find_plan_create_call ( & tool_calls) {
668733 println ! ( ) ; // Space before menu
@@ -714,6 +779,32 @@ pub async fn run_interactive(
714779
715780 println ! ( ) ;
716781
782+ // Check if this was a user-initiated cancellation (Ctrl+C)
783+ if err_str. contains ( "cancelled" ) || err_str. contains ( "Cancelled" ) {
784+ // Extract any completed work before cancellation
785+ let completed_tools = extract_tool_calls_from_hook ( & hook) . await ;
786+ let tool_count = completed_tools. len ( ) ;
787+
788+ eprintln ! ( "{}" , "⚠ Generation interrupted." . yellow( ) ) ;
789+ if tool_count > 0 {
790+ eprintln ! (
791+ "{}" ,
792+ format!( " {} tool calls completed before interrupt." , tool_count)
793+ . dimmed( )
794+ ) ;
795+ // Add partial progress to history
796+ conversation_history. add_turn (
797+ current_input. clone ( ) ,
798+ format ! ( "[Interrupted after {} tool calls]" , tool_count) ,
799+ completed_tools,
800+ ) ;
801+ }
802+ eprintln ! ( "{}" , " Type your next message to continue." . dimmed( ) ) ;
803+
804+ // Don't retry, don't mark as succeeded - just break to return to prompt
805+ break ;
806+ }
807+
717808 // Check if this is a max depth error - handle as checkpoint
718809 if err_str. contains ( "MaxDepth" )
719810 || err_str. contains ( "max_depth" )
@@ -1067,9 +1158,21 @@ pub async fn run_interactive(
10671158 println ! ( ) ;
10681159 }
10691160
1161+ // Clean up terminal layout before exiting (disabled - layout not initialized)
1162+ // if let Err(e) = terminal_layout.cleanup() {
1163+ // eprintln!(
1164+ // "{}",
1165+ // format!("Warning: Terminal cleanup failed: {}", e).dimmed()
1166+ // );
1167+ // }
1168+
10701169 Ok ( ( ) )
10711170}
10721171
1172+ // NOTE: wait_for_interrupt function removed - ESC interrupt feature disabled
1173+ // due to terminal corruption issues with spawn_blocking raw mode handling.
1174+ // TODO: Re-implement using tool hook callbacks for cleaner interruption.
1175+
10731176/// Extract tool call records from the hook state for history tracking
10741177async fn extract_tool_calls_from_hook ( hook : & ToolDisplayHook ) -> Vec < ToolCallRecord > {
10751178 let state = hook. state ( ) ;
@@ -1422,7 +1525,8 @@ pub async fn run_query(
14221525 . tool ( TerraformValidateTool :: new ( project_path_buf. clone ( ) ) )
14231526 . tool ( TerraformInstallTool :: new ( ) )
14241527 . tool ( ReadFileTool :: new ( project_path_buf. clone ( ) ) )
1425- . tool ( ListDirectoryTool :: new ( project_path_buf. clone ( ) ) ) ;
1528+ . tool ( ListDirectoryTool :: new ( project_path_buf. clone ( ) ) )
1529+ . tool ( WebFetchTool :: new ( ) ) ;
14261530
14271531 // Add generation tools if this is a generation query
14281532 if is_generation {
@@ -1467,7 +1571,8 @@ pub async fn run_query(
14671571 . tool ( TerraformValidateTool :: new ( project_path_buf. clone ( ) ) )
14681572 . tool ( TerraformInstallTool :: new ( ) )
14691573 . tool ( ReadFileTool :: new ( project_path_buf. clone ( ) ) )
1470- . tool ( ListDirectoryTool :: new ( project_path_buf. clone ( ) ) ) ;
1574+ . tool ( ListDirectoryTool :: new ( project_path_buf. clone ( ) ) )
1575+ . tool ( WebFetchTool :: new ( ) ) ;
14711576
14721577 // Add generation tools if this is a generation query
14731578 if is_generation {
@@ -1515,7 +1620,8 @@ pub async fn run_query(
15151620 . tool ( TerraformValidateTool :: new ( project_path_buf. clone ( ) ) )
15161621 . tool ( TerraformInstallTool :: new ( ) )
15171622 . tool ( ReadFileTool :: new ( project_path_buf. clone ( ) ) )
1518- . tool ( ListDirectoryTool :: new ( project_path_buf. clone ( ) ) ) ;
1623+ . tool ( ListDirectoryTool :: new ( project_path_buf. clone ( ) ) )
1624+ . tool ( WebFetchTool :: new ( ) ) ;
15191625
15201626 // Add generation tools if this is a generation query
15211627 if is_generation {
0 commit comments