use super::*;
pub(crate) const CHILD_RECENT_SECS: i64 = 300;
#[derive(Debug, Clone)]
pub(crate) struct ChildState {
pub(crate) session_id: String,
pub(crate) state: &'static str,
pub(crate) detail: String,
}
#[derive(Debug, Clone, Default)]
pub(crate) struct ChildrenReport {
pub(crate) children: Vec<ChildState>,
pub(crate) journal_in_flight: usize,
pub(crate) live_count: usize,
}
pub(crate) fn children_report(
main_jsonl: &Path,
returned: &std::collections::HashSet<String>,
) -> Result<ChildrenReport> {
let mut report = ChildrenReport::default();
let subs = crate::subagent::subagent_transcript_files(main_jsonl).unwrap_or_default();
for sub in &subs {
let sid = crate::subagent::session_id_from_path(sub);
let shape = tail_shape(sub)?;
let tail_age = shape.last_ts_utc.as_deref().and_then(|t| age_secs(Some(t)));
let (state, detail) = if returned.contains(&sid) {
(
"settled",
"completion notification received in the main transcript".to_string(),
)
} else if let Some((tool, ts)) = &shape.unreturned_use {
(
"in-flight",
format!(
"unreturned {tool} call{}",
age_secs(ts.as_deref())
.map(|a| format!(" ({a}s ago)"))
.unwrap_or_default()
),
)
} else if tail_age.is_some_and(|a| a <= CHILD_RECENT_SECS)
&& shape.last_stop_reason.as_deref() != Some("end_turn")
{
(
"generating",
format!(
"last record {}s ago, no end_turn yet",
tail_age.unwrap_or(0)
),
)
} else {
(
"settled",
tail_age.map_or_else(
|| "no timestamped tail".to_string(),
|a| format!("last record {a}s ago"),
),
)
};
if state != "settled" {
report.live_count += 1;
}
report.children.push(ChildState {
session_id: sid,
state,
detail,
});
}
let sub_root = main_jsonl
.with_extension("")
.join("subagents")
.join("workflows");
if sub_root.is_dir() {
for wf in std::fs::read_dir(&sub_root)? {
let wf = wf?;
let journal = wf.path().join("journal.jsonl");
if !journal.is_file() {
continue;
}
let Ok(raw) = std::fs::read_to_string(&journal) else {
continue;
};
let mut started = 0usize;
let mut resulted = 0usize;
for line in raw.lines() {
let Ok(v) = serde_json::from_str::<serde_json::Value>(line) else {
continue;
};
match v.get("type").and_then(serde_json::Value::as_str) {
Some("started") => started += 1,
Some("result") => resulted += 1,
_ => {}
}
}
report.journal_in_flight += started.saturating_sub(resulted);
}
}
if report.journal_in_flight > 0 {
report.live_count += report.journal_in_flight;
}
Ok(report)
}