ai_agent/services/compact/
post_compact_cleanup.rs1use crate::services::compact::microcompact::reset_microcompact_state;
8use std::collections::HashMap;
9use std::sync::{LazyLock, Mutex};
10
11const MAIN_THREAD_SOURCES: &[&str] = &["repl_main_thread", "sdk"];
13
14pub fn is_main_thread_compact(query_source: Option<&str>) -> bool {
16 match query_source {
17 Some(source) => MAIN_THREAD_SOURCES.contains(&source),
18 None => true, }
20}
21
22#[derive(Debug, Default, Clone)]
26pub struct CleanupState {
27 pub context_collapse_reset: bool,
28 pub user_context_cleared: bool,
29 pub memory_files_cleared: bool,
30 pub system_prompt_cleared: bool,
31 pub classifier_approvals_cleared: bool,
32 pub speculative_checks_cleared: bool,
33 pub beta_tracing_cleared: bool,
34 pub session_messages_cleared: bool,
35 pub file_content_swept: bool,
36}
37
38static CLEANUP_STATE: LazyLock<Mutex<CleanupState>> =
39 LazyLock::new(|| Mutex::new(CleanupState::default()));
40
41pub fn get_cleanup_state() -> CleanupState {
43 CLEANUP_STATE.lock().unwrap().clone()
44}
45
46pub fn reset_cleanup_state_for_testing() {
48 *CLEANUP_STATE.lock().unwrap() = CleanupState::default();
49}
50
51pub fn run_post_compact_cleanup(query_source: Option<&str>) {
56 let mut cleanup = CleanupState::default();
57
58 reset_microcompact_state();
60
61 if !is_main_thread_compact(query_source) {
63 log::debug!(
64 "[post-compact] Skipping main-thread cleanup for sub-agent source: {:?}",
65 query_source
66 );
67 return;
68 }
69
70 reset_context_collapse();
72 cleanup.context_collapse_reset = true;
73
74 clear_user_context_cache();
76 cleanup.user_context_cleared = true;
77
78 clear_memory_files_cache();
80 cleanup.memory_files_cleared = true;
81
82 clear_system_prompt_sections();
84 cleanup.system_prompt_cleared = true;
85
86 clear_classifier_approvals();
88 cleanup.classifier_approvals_cleared = true;
89
90 clear_speculative_checks();
92 cleanup.speculative_checks_cleared = true;
93
94 clear_beta_tracing_state();
96 cleanup.beta_tracing_cleared = true;
97
98 clear_session_messages_cache();
100 cleanup.session_messages_cleared = true;
101
102 sweep_file_content_cache();
104 cleanup.file_content_swept = true;
105
106 *CLEANUP_STATE.lock().unwrap() = cleanup;
108
109 log::info!("[post-compact] Cleanup complete");
110}
111
112pub fn reset_context_collapse() {
116 log::debug!("[context-collapse] State reset - clearing collapse tracking");
117 }
121
122pub fn clear_user_context_cache() {
126 log::debug!("[post-compact] User context cache cleared");
127 }
129
130pub fn clear_memory_files_cache() {
134 log::debug!("[post-compact] Memory files cache cleared");
135 }
137
138pub fn clear_system_prompt_sections() {
141 log::debug!("[post-compact] System prompt sections cleared");
142 }
144
145pub fn clear_classifier_approvals() {
149 log::debug!("[post-compact] Classifier approvals cleared");
150 }
152
153pub fn clear_speculative_checks() {
156 log::debug!("[post-compact] Speculative checks cleared");
157 }
159
160pub fn clear_beta_tracing_state() {
163 log::debug!("[post-compact] Beta tracing state cleared");
164 }
166
167pub fn clear_session_messages_cache() {
170 log::debug!("[post-compact] Session messages cache cleared");
171 }
173
174pub fn sweep_file_content_cache() {
178 log::debug!("[post-compact] File content cache swept");
179 }
181
182pub fn clear_all_caches() {
184 reset_context_collapse();
185 clear_user_context_cache();
186 clear_memory_files_cache();
187 clear_system_prompt_sections();
188 clear_classifier_approvals();
189 clear_speculative_checks();
190 clear_beta_tracing_state();
191 clear_session_messages_cache();
192 sweep_file_content_cache();
193}
194
195pub fn get_last_cleanup_summary() -> String {
197 let state = CLEANUP_STATE.lock().unwrap();
198 let mut parts = Vec::new();
199
200 if state.context_collapse_reset {
201 parts.push("context_collapse");
202 }
203 if state.user_context_cleared {
204 parts.push("user_context");
205 }
206 if state.memory_files_cleared {
207 parts.push("memory_files");
208 }
209 if state.system_prompt_cleared {
210 parts.push("system_prompt");
211 }
212 if state.classifier_approvals_cleared {
213 parts.push("classifier_approvals");
214 }
215 if state.speculative_checks_cleared {
216 parts.push("speculative_checks");
217 }
218 if state.beta_tracing_cleared {
219 parts.push("beta_tracing");
220 }
221 if state.session_messages_cleared {
222 parts.push("session_messages");
223 }
224 if state.file_content_swept {
225 parts.push("file_content");
226 }
227
228 if parts.is_empty() {
229 "No caches cleared (possibly sub-agent compact)".to_string()
230 } else {
231 format!("Caches cleared: {}", parts.join(", "))
232 }
233}
234
235#[cfg(test)]
236mod tests {
237 use super::*;
238
239 #[test]
240 fn test_is_main_thread_compact_repl() {
241 assert!(is_main_thread_compact(Some("repl_main_thread")));
242 }
243
244 #[test]
245 fn test_is_main_thread_compact_sdk() {
246 assert!(is_main_thread_compact(Some("sdk")));
247 }
248
249 #[test]
250 fn test_is_main_thread_compact_none() {
251 assert!(is_main_thread_compact(None));
252 }
253
254 #[test]
255 fn test_is_main_thread_compact_subagent() {
256 assert!(!is_main_thread_compact(Some("session_memory")));
257 assert!(!is_main_thread_compact(Some("compact")));
258 assert!(!is_main_thread_compact(Some("prompt_suggestion")));
259 }
260
261 #[test]
262 fn test_run_post_compact_cleanup_main_thread() {
263 run_post_compact_cleanup(Some("repl_main_thread"));
265 let state = get_cleanup_state();
266 assert!(state.context_collapse_reset);
267 assert!(state.user_context_cleared);
268 }
269
270 #[test]
271 fn test_run_post_compact_cleanup_subagent() {
272 {
274 let mut state = CLEANUP_STATE.lock().unwrap();
275 *state = CleanupState::default();
276 }
277 run_post_compact_cleanup(Some("session_memory"));
279 let state = get_cleanup_state();
280 assert!(!state.context_collapse_reset);
282 }
283
284 #[test]
285 fn test_reset_context_collapse() {
286 reset_context_collapse();
288 }
289
290 #[test]
291 fn test_clear_system_prompt_sections() {
292 clear_system_prompt_sections();
294 }
295
296 #[test]
297 fn test_clear_all_caches() {
298 clear_all_caches();
300 }
301
302 #[test]
303 fn test_get_last_cleanup_summary_empty() {
304 reset_cleanup_state_for_testing();
306 run_post_compact_cleanup(Some("subagent"));
308
309 let summary = get_last_cleanup_summary();
310 assert!(summary.contains("No caches cleared"), "Expected 'No caches cleared', got: {}", summary);
311 }
312
313 #[test]
314 fn test_get_last_cleanup_summary_populated() {
315 reset_cleanup_state_for_testing();
316 run_post_compact_cleanup(Some("repl_main_thread"));
317
318 let summary = get_last_cleanup_summary();
319 assert!(summary.contains("context_collapse"));
320 assert!(summary.contains("user_context"));
321 }
322}