diff --git a/hive-agent/src/turn.rs b/hive-agent/src/turn.rs index e5dfd237..c111b388 100644 --- a/hive-agent/src/turn.rs +++ b/hive-agent/src/turn.rs @@ -790,8 +790,9 @@ impl Sink for BusSink<'_> { /// Apply a completed turn's parsed [`hive_claude::Telemetry`] to the bus: /// per-inference context usage + cumulative cost, the resolved model id, and /// the API-reported context window (the authoritative window for the auto-reset -/// watermark). Skips a degenerate turn that parsed nothing so it doesn't reset -/// the badges to zero. +/// watermark). A degenerate turn that parsed nothing leaves the usage badges +/// alone (it doesn't reset them to zero) but still replaces the resolved model, +/// so it is never credited to the previous turn's. fn apply_telemetry(bus: &Bus, telemetry: &hive_claude::Telemetry) { // On a degenerate turn that emitted a `result` but no `assistant` event, // the per-inference `context` stays zero while `cost` (cumulative) is not. @@ -803,11 +804,13 @@ fn apply_telemetry(bus: &Bus, telemetry: &hive_claude::Telemetry) { } else { telemetry.context }; + // Recorded before the skip so a degenerate turn clears the previous + // turn's id instead of being credited to it. + bus.set_resolved_model(telemetry.model.clone()); if ctx.context_tokens() == 0 { return; } bus.record_turn_usage(ctx, telemetry.cost); - bus.set_resolved_model(telemetry.model.clone()); if let Some(window) = telemetry.context_window { bus.set_api_context_window(window); } @@ -853,12 +856,22 @@ fn archive_session(bus: &Bus, session: &AgentSession) { #[cfg(test)] mod tests { use super::{ - PermissionAsk, TurnError, acp_permits, nonzero_or, parse_u64, report_compact, - runtime_error_to_turn, with_turn_end_compact, + PermissionAsk, TurnError, acp_permits, apply_telemetry, nonzero_or, parse_u64, + report_compact, runtime_error_to_turn, with_turn_end_compact, }; use crate::events::{Bus, LiveEvent}; use hive_runtime::{AcpError, Error}; + #[test] + fn a_turn_that_parsed_nothing_does_not_keep_the_previous_resolved_model() { + let bus = Bus::new(); + bus.set_resolved_model(Some("claude-opus-4-8".to_owned())); + + apply_telemetry(&bus, &hive_claude::Telemetry::default()); + + assert_eq!(bus.last_resolved_model(), None); + } + #[test] fn an_absent_or_blank_value_is_not_a_number() { assert_eq!(parse_u64(None), None);