diff --git a/crates/codegen/kigi-tui/src/app/acp_handler/session_notification.rs b/crates/codegen/kigi-tui/src/app/acp_handler/session_notification.rs index 3f74b18..9214f58 100644 --- a/crates/codegen/kigi-tui/src/app/acp_handler/session_notification.rs +++ b/crates/codegen/kigi-tui/src/app/acp_handler/session_notification.rs @@ -45,6 +45,14 @@ pub(super) fn refresh_context_used(view: &mut AgentView, used: u64) { let total = view.session.models.get_context_window().unwrap_or(0); view.apply_context_used(used, total); } +/// The count a compaction lifecycle update carries for the context bar: the banner's "N% full" must not sit next to a stale pre-turn number. +pub(super) fn compaction_context_refresh(update: &XaiSessionUpdate) -> Option { + match update { + XaiSessionUpdate::AutoCompactStarted { tokens_used, .. } => Some(*tokens_used), + XaiSessionUpdate::AutoCompactCompleted { tokens_after, .. } => Some(*tokens_after), + _ => None, + } +} /// Refresh the bar and record `used` as the confirmed count for a pending /// compaction message; call only from the `meta.totalTokens` path. pub(super) fn confirm_context_used(view: &mut AgentView, used: u64) { @@ -197,8 +205,10 @@ pub(super) fn handle_session_notification(notif: &acp::ExtNotification, app: &mu &mut agent.scrollback, is_api_key_auth, ); - if let XaiSessionUpdate::AutoCompactCompleted { tokens_after, .. } = update { - refresh_context_used(agent, *tokens_after); + if let Some(used) = compaction_context_refresh(update) { + refresh_context_used(agent, used); + } + if let XaiSessionUpdate::AutoCompactCompleted { .. } = update { agent.todo.update_todos(Vec::new()); } changed @@ -1071,10 +1081,7 @@ pub(super) fn handle_child_session_notification( | XaiSessionUpdate::AutoCompactFailed { .. } | XaiSessionUpdate::AutoCompactCancelled { .. } | XaiSessionUpdate::RetryState(_) => { - let compact_tokens = match &update { - XaiSessionUpdate::AutoCompactCompleted { tokens_after, .. } => Some(*tokens_after), - _ => None, - }; + let compact_tokens = compaction_context_refresh(&update); let mut changed = false; if let Some(child_view) = agent.subagent_views.get_mut(child_sid) { changed = apply_session_event( diff --git a/crates/codegen/kigi-tui/src/app/acp_handler/tests/session_events.rs b/crates/codegen/kigi-tui/src/app/acp_handler/tests/session_events.rs index 5f2fb14..77425bb 100644 --- a/crates/codegen/kigi-tui/src/app/acp_handler/tests/session_events.rs +++ b/crates/codegen/kigi-tui/src/app/acp_handler/tests/session_events.rs @@ -576,9 +576,8 @@ } #[test] - fn child_compact_started_does_not_reset_context_used() { - // Sibling variants in the same outer arm must not touch the numerator; - // guards against accidental widening of the AutoCompactCompleted gate. + fn child_compact_started_refreshes_context_used() { + // AutoCompactStarted carries the count the trigger fired on; the child bar must show it. let mut agent = make_agent(Some("root-sess")); let child_sid = "child-sess-3"; agent @@ -603,7 +602,17 @@ let child_view = agent.subagent_views.get(child_sid).unwrap(); assert_eq!( child_view.context_state.as_ref().map(|c| c.used), - Some(90_000) + Some(95_000) + ); + // A sibling variant without a count leaves the numerator alone. + let cancelled = XaiSessionUpdate::AutoCompactCancelled { + reason: "user".into(), + }; + let _ = handle_child_session_notification(cancelled, child_sid, &mut agent, false); + let child_view = agent.subagent_views.get(child_sid).unwrap(); + assert_eq!( + child_view.context_state.as_ref().map(|c| c.used), + Some(95_000) ); } @@ -693,5 +702,30 @@ !session.model_incompatible, "non-encrypted_content error types must not set model_incompatible" ); + } + /// The banner's count must also refresh the context bar. + #[test] + fn auto_compact_started_refreshes_the_context_bar() { + let mut app = make_app_with_agent("sess-1"); + let id = AgentId(0); + app.agents.get_mut(&id).unwrap().apply_context_used(15_000, 262_144); + let payload = SessionNotification { + session_id: acp::SessionId::new("sess-1"), + update: XaiSessionUpdate::AutoCompactStarted { + tokens_used: 90_000, + context_window: 262_144, + percentage: 34, + reason: "threshold".into(), + }, + meta: None, + }; + let notif = acp::ExtNotification::new( + "kigi/session/update", + std::sync::Arc::from(serde_json::value::to_raw_value(&payload).unwrap()), + ); + let _ = handle_ext_notification(¬if, &mut app); + let bar = app.agents[&id].context_state.as_ref().map(|c| (c.used, c.total)); + assert_eq!(bar, Some((90_000, 262_144))); + }