@@ -1746,6 +1746,13 @@ async fn run_interactive(
17461746 app. provider_registry = base_query_config. provider_registry . clone ( ) ;
17471747 app. refresh_context_window_size ( ) ;
17481748 app. auto_compact_enabled = live_config. auto_compact ;
1749+ let compact_threshold = live_config. effective_compact_threshold ( ) ;
1750+ let compact_threshold_pct = if compact_threshold <= 1.0 {
1751+ compact_threshold * 100.0
1752+ } else {
1753+ compact_threshold
1754+ } ;
1755+ app. auto_compact_threshold = compact_threshold_pct. clamp ( 0.0 , 100.0 ) . ceil ( ) as u8 ;
17491756 app. completion_toast_enabled = settings. completion_toast_enabled ( ) ;
17501757 app. bell_on_complete = settings. bell_on_complete ;
17511758
@@ -3034,62 +3041,59 @@ async fn run_interactive(
30343041 ) ;
30353042 }
30363043
3037- // Auto-compact: when context usage hits 99% and no query is running,
3038- // automatically submit a compact request.
3039- if app. context_window_size > 0
3044+ // Auto-compact: when enabled and context usage reaches the configured
3045+ // threshold with no query running, summarize through the dedicated
3046+ // no-tools compaction path instead of starting an agentic turn.
3047+ if app. auto_compact_enabled
3048+ && app. context_window_size > 0
30403049 && !app. is_streaming
30413050 && current_query. is_none ( )
30423051 && !app. auto_compact_running
30433052 {
3044- let used_pct = ( app. context_used_tokens as f64 / app. context_window_size as f64 * 100.0 ) as u64 ;
3045- if used_pct >= 99 {
3053+ let used_pct =
3054+ ( app. context_used_tokens as f64 / app. context_window_size as f64 * 100.0 ) as u64 ;
3055+ if used_pct >= u64:: from ( app. auto_compact_threshold ) {
30463056 app. auto_compact_running = true ;
3047- let msg_count = messages. len ( ) ;
3048- let compact_msg = format ! (
3049- "[Auto-compact triggered ({} messages, {}% context used). \
3050- Provide a detailed summary of our conversation so far, \
3051- preserving all key technical details, decisions made, \
3052- file paths mentioned, and current task status.]",
3053- msg_count, used_pct
3054- ) ;
3055- app. status_message = Some ( "Context 99% full — auto-compacting…" . to_string ( ) ) ;
3056- let user_msg = claurst_core:: types:: Message :: user ( compact_msg) ;
3057- messages. push ( user_msg. clone ( ) ) ;
3058- app. push_message ( user_msg) ;
3059- session. messages = messages. clone ( ) ;
3060- session. updated_at = chrono:: Utc :: now ( ) ;
3057+ app. status_message = Some ( format ! (
3058+ "Context {}% full — auto-compacting…" ,
3059+ used_pct
3060+ ) ) ;
30613061
3062- // Dispatch the compact query immediately.
30633062 let ct = CancellationToken :: new ( ) ;
30643063 cancel = Some ( ct. clone ( ) ) ;
30653064 let msgs_arc = Arc :: new ( tokio:: sync:: Mutex :: new ( messages. clone ( ) ) ) ;
30663065 let msgs_arc_clone = msgs_arc. clone ( ) ;
3067- let tools_arc_clone = tools_arc. clone ( ) ;
3068- let ctx_clone = tool_ctx. clone ( ) ;
3069- let mut qcfg = base_query_config. clone ( ) ;
3070- qcfg. model = claurst_api:: effective_model_for_config ( & cmd_ctx. config , & model_registry) ;
3071- qcfg. max_tokens = cmd_ctx. config . effective_max_tokens ( ) ;
3072- let tracker = cost_tracker. clone ( ) ;
3073- let tx = event_tx. clone ( ) ;
3066+ let compact_model =
3067+ claurst_api:: effective_model_for_config ( & cmd_ctx. config , & model_registry) ;
30743068 let client_clone = client. clone ( ) ;
30753069 app. is_streaming = true ;
30763070
30773071 let handle = tokio:: spawn ( async move {
3078- let mut msgs = msgs_arc_clone. lock ( ) . await . clone ( ) ;
3079- let outcome = claurst_query:: run_query_loop (
3072+ if ct. is_cancelled ( ) {
3073+ return QueryOutcome :: Cancelled ;
3074+ }
3075+
3076+ let msgs = msgs_arc_clone. lock ( ) . await . clone ( ) ;
3077+ match claurst_query:: compact_conversation (
30803078 client_clone. as_ref ( ) ,
3081- & mut msgs,
3082- tools_arc_clone. as_slice ( ) ,
3083- & ctx_clone,
3084- & qcfg,
3085- tracker,
3086- Some ( tx) ,
3087- ct,
3088- None ,
3079+ & msgs,
3080+ & compact_model,
30893081 )
3090- . await ;
3091- * msgs_arc_clone. lock ( ) . await = msgs;
3092- outcome
3082+ . await
3083+ {
3084+ Ok ( compacted) => {
3085+ let message = compacted
3086+ . first ( )
3087+ . cloned ( )
3088+ . unwrap_or_else ( || claurst_core:: types:: Message :: assistant ( "" ) ) ;
3089+ * msgs_arc_clone. lock ( ) . await = compacted;
3090+ QueryOutcome :: EndTurn {
3091+ message,
3092+ usage : claurst_core:: types:: UsageInfo :: default ( ) ,
3093+ }
3094+ }
3095+ Err ( err) => QueryOutcome :: Error ( err) ,
3096+ }
30933097 } ) ;
30943098 current_query = Some ( ( handle, msgs_arc) ) ;
30953099 }
@@ -3634,16 +3638,32 @@ async fn run_interactive(
36343638 if task_finished {
36353639 if let Some ( ( handle, msgs_arc) ) = current_query. take ( ) {
36363640 // Get the outcome and handle errors
3637- if let Ok ( QueryOutcome :: Error ( err) ) = handle. await {
3638- while app. notifications . current_is_error ( ) {
3639- app. notifications . dismiss_current ( ) ;
3641+ let query_outcome = handle. await ;
3642+ match & query_outcome {
3643+ Ok ( QueryOutcome :: Error ( err) ) => {
3644+ while app. notifications . current_is_error ( ) {
3645+ app. notifications . dismiss_current ( ) ;
3646+ }
3647+ app. notifications . push (
3648+ claurst_tui:: notifications:: NotificationKind :: Error ,
3649+ err. to_string ( ) ,
3650+ None ,
3651+ ) ;
36403652 }
3641- app. notifications . push (
3642- claurst_tui:: notifications:: NotificationKind :: Error ,
3643- err. to_string ( ) ,
3644- None ,
3645- ) ;
3646- }
3653+ Err ( err) => {
3654+ while app. notifications . current_is_error ( ) {
3655+ app. notifications . dismiss_current ( ) ;
3656+ }
3657+ app. notifications . push (
3658+ claurst_tui:: notifications:: NotificationKind :: Error ,
3659+ format ! ( "Query task failed: {}" , err) ,
3660+ None ,
3661+ ) ;
3662+ }
3663+ _ => { }
3664+ } ;
3665+ let auto_compact_succeeded =
3666+ matches ! ( query_outcome, Ok ( QueryOutcome :: EndTurn { .. } ) ) ;
36473667 // Sync the updated conversation back to our local vector
36483668 messages = msgs_arc. lock ( ) . await . clone ( ) ;
36493669 session. messages = messages. clone ( ) ;
@@ -3661,9 +3681,16 @@ async fn run_interactive(
36613681 }
36623682 if app. auto_compact_running {
36633683 app. auto_compact_running = false ;
3664- // After auto-compact the context was summarised — reset usage.
3665- app. context_used_tokens = 0 ;
3666- app. status_message = Some ( "Auto-compact complete." . to_string ( ) ) ;
3684+ if !auto_compact_succeeded {
3685+ app. auto_compact_enabled = false ;
3686+ app. status_message = Some (
3687+ "Auto-compact failed and was disabled for this session." . to_string ( ) ,
3688+ ) ;
3689+ } else {
3690+ // After auto-compact the context was summarised — reset usage.
3691+ app. context_used_tokens = 0 ;
3692+ app. status_message = Some ( "Auto-compact complete." . to_string ( ) ) ;
3693+ }
36673694 }
36683695
36693696 // Save session to JSONL (primary storage)
0 commit comments