use super::*;
use std::collections::{BTreeMap, BTreeSet};
#[derive(Debug, Clone, Default)]
pub(crate) struct Activity {
pub(crate) records: usize,
pub(crate) lanes: BTreeSet<String>,
pub(crate) tools: BTreeMap<String, usize>,
pub(crate) thinking: usize,
pub(crate) agent_messages: usize,
pub(crate) user_prompts: usize,
pub(crate) notifications: usize,
pub(crate) shrinks: Vec<String>,
}
impl Activity {
pub(crate) fn note_shrink(&mut self, lane: &str, bytes: u64) {
self.shrinks.push(format!("{lane}: -{bytes} bytes"));
}
pub(crate) fn fold(&mut self, rec: &Record, lane: &str) {
self.records += 1;
self.lanes.insert(lane.to_string());
if rec.is_type("queue-operation") || rec.attachment_type().is_some() {
self.notifications += crate::live::delivered_pulse_labels(rec).len();
return;
}
let labels = rec.classify(&crate::model::ClassifyCtx::top_level());
for c in &labels {
match c {
crate::model::Class::AgentToolUse => {
for b in rec.blocks().unwrap_or_default() {
if let Block::ToolUse { name, .. } = b {
let n = name.clone().unwrap_or_else(|| "(unnamed)".to_string());
*self.tools.entry(n).or_insert(0) += 1;
}
}
}
crate::model::Class::AgentThinking
| crate::model::Class::AgentThinkingNarration => self.thinking += 1,
crate::model::Class::AgentMessage => self.agent_messages += 1,
crate::model::Class::UserMessage => self.user_prompts += 1,
c if c.path().starts_with("harness.notification") => self.notifications += 1,
_ => {}
}
}
}
pub(crate) fn summary_line(&self) -> String {
let shrank = if self.shrinks.is_empty() {
String::new()
} else {
format!(
" · transcript shrank {} time(s) ({}): rewritten in place, baseline moved",
self.shrinks.len(),
self.shrinks.join(", ")
)
};
if self.records == 0 {
return format!("nothing landed after the baseline{shrank}");
}
let tools = if self.tools.is_empty() {
"no tools".to_string()
} else {
let mut by_count: Vec<(&String, &usize)> = self.tools.iter().collect();
by_count.sort_by(|a, b| b.1.cmp(a.1).then(a.0.cmp(b.0)));
format!(
"tools {}",
by_count
.iter()
.map(|(n, c)| format!("{n} x{c}"))
.collect::<Vec<_>>()
.join(" ")
)
};
format!(
"{} record(s) in {} lane(s): {tools} · thinking {} · messages {} · prompts {} · \
notifications {}{shrank}",
self.records,
self.lanes.len(),
self.thinking,
self.agent_messages,
self.user_prompts,
self.notifications
)
}
pub(crate) fn json(&self) -> serde_json::Value {
serde_json::json!({
"records": self.records,
"lanes": self.lanes.len(),
"tools": self.tools,
"thinking": self.thinking,
"agent_messages": self.agent_messages,
"user_prompts": self.user_prompts,
"notifications": self.notifications,
"shrinks": self.shrinks,
})
}
}