diff --git a/crates/tui/src/session_manager.rs b/crates/tui/src/session_manager.rs index 0149cfd2..f3212d24 100644 --- a/crates/tui/src/session_manager.rs +++ b/crates/tui/src/session_manager.rs @@ -108,6 +108,13 @@ pub struct SessionMetadata { /// Accumulated sub-agent/background LLM cost in CNY (for persisted billing). #[serde(default)] pub subagent_cost_cny: f64, + /// Max-ever displayed session+subagent cost in USD (preserves #244 + /// monotonic guarantee across session restarts). + #[serde(default)] + pub displayed_cost_high_water_usd: f64, + /// Max-ever displayed session+subagent cost in CNY. + #[serde(default)] + pub displayed_cost_high_water_cny: f64, } impl SessionMetadata { @@ -117,6 +124,8 @@ impl SessionMetadata { self.session_cost_cny = other.session_cost_cny; self.subagent_cost_usd = other.subagent_cost_usd; self.subagent_cost_cny = other.subagent_cost_cny; + self.displayed_cost_high_water_usd = other.displayed_cost_high_water_usd; + self.displayed_cost_high_water_cny = other.displayed_cost_high_water_cny; } } @@ -606,6 +615,8 @@ pub fn create_saved_session_with_mode( session_cost_cny: 0.0, subagent_cost_usd: 0.0, subagent_cost_cny: 0.0, + displayed_cost_high_water_usd: 0.0, + displayed_cost_high_water_cny: 0.0, }, messages: capped_messages, system_prompt: merge_truncation_note( @@ -877,6 +888,8 @@ mod tests { session_cost_cny: 0.0, subagent_cost_usd: 0.0, subagent_cost_cny: 0.0, + displayed_cost_high_water_usd: 0.0, + displayed_cost_high_water_cny: 0.0, }, system_prompt: None, context_references: Vec::new(), @@ -907,6 +920,8 @@ mod tests { session_cost_cny: 0.0, subagent_cost_usd: 0.0, subagent_cost_cny: 0.0, + displayed_cost_high_water_usd: 0.0, + displayed_cost_high_water_cny: 0.0, }, system_prompt: None, context_references: Vec::new(), diff --git a/crates/tui/src/tui/app.rs b/crates/tui/src/tui/app.rs index 362925f9..71977d46 100644 --- a/crates/tui/src/tui/app.rs +++ b/crates/tui/src/tui/app.rs @@ -1655,6 +1655,8 @@ impl App { metadata.session_cost_cny = self.session.session_cost_cny; metadata.subagent_cost_usd = self.session.subagent_cost; metadata.subagent_cost_cny = self.session.subagent_cost_cny; + metadata.displayed_cost_high_water_usd = self.session.displayed_cost_high_water; + metadata.displayed_cost_high_water_cny = self.session.displayed_cost_high_water_cny; } /// Recompute the displayed cost high-water mark. Called any time a cost diff --git a/crates/tui/src/tui/session_picker.rs b/crates/tui/src/tui/session_picker.rs index 61b257b1..84a4200c 100644 --- a/crates/tui/src/tui/session_picker.rs +++ b/crates/tui/src/tui/session_picker.rs @@ -615,6 +615,8 @@ mod tests { session_cost_cny: 0.0, subagent_cost_usd: 0.0, subagent_cost_cny: 0.0, + displayed_cost_high_water_usd: 0.0, + displayed_cost_high_water_cny: 0.0, } } diff --git a/crates/tui/src/tui/ui.rs b/crates/tui/src/tui/ui.rs index e2503be7..f91ff57e 100644 --- a/crates/tui/src/tui/ui.rs +++ b/crates/tui/src/tui/ui.rs @@ -6068,12 +6068,17 @@ fn apply_loaded_session(app: &mut App, session: &SavedSession) -> bool { app.session.subagent_cost = session.metadata.subagent_cost_usd; app.session.subagent_cost_cny = session.metadata.subagent_cost_cny; app.session.subagent_cost_event_seqs.clear(); - // Set high-water marks to the restored totals so the footer never - // reverses when reconciliation events fire on a resumed session. + // Restore the high-water marks from persisted metadata so the + // monotonic cost guarantee (#244) survives session restarts. + // Take the max with the current totals — old sessions without + // persisted high-water fields deserialise to 0.0 and fall back to + // the restored total with no regression. let total_restored_usd = app.session.session_cost + app.session.subagent_cost; let total_restored_cny = app.session.session_cost_cny + app.session.subagent_cost_cny; - app.session.displayed_cost_high_water = total_restored_usd; - app.session.displayed_cost_high_water_cny = total_restored_cny; + app.session.displayed_cost_high_water = + session.metadata.displayed_cost_high_water_usd.max(total_restored_usd); + app.session.displayed_cost_high_water_cny = + session.metadata.displayed_cost_high_water_cny.max(total_restored_cny); app.session.last_prompt_tokens = None; app.session.last_completion_tokens = None; app.session.last_prompt_cache_hit_tokens = None; diff --git a/crates/tui/src/tui/ui/tests.rs b/crates/tui/src/tui/ui/tests.rs index 8a307adb..c6aa424c 100644 --- a/crates/tui/src/tui/ui/tests.rs +++ b/crates/tui/src/tui/ui/tests.rs @@ -699,6 +699,8 @@ fn saved_session_with_messages(messages: Vec) -> SavedSession { session_cost_cny: 0.0, subagent_cost_usd: 0.0, subagent_cost_cny: 0.0, + displayed_cost_high_water_usd: 0.0, + displayed_cost_high_water_cny: 0.0, }, messages, system_prompt: None,