use super::*;
impl MissionControlState {
pub(super) fn update_subagent_transcript_started(
&mut self,
id: &ActivityId,
kind: &ActivityKind,
status: ActivityStatus,
label: &str,
) {
self.update_subagent_transcript(id, |task, is_task| {
if is_task {
task.status = status;
if matches!(kind, ActivityKind::SubagentTask) {
task.push_recent(format!("started {}", bounded_transcript_label(label, 80)));
}
} else {
task.push_recent(format!(
"{}: {}",
activity_kind_text(kind),
bounded_transcript_label(label, 80)
));
}
});
}
pub(super) fn update_subagent_transcript_final_preview(
&mut self,
id: &ActivityId,
metadata: &ActivityMetadata,
status: ActivityStatus,
) {
self.update_subagent_transcript(id, |task, _| {
let summary = metadata
.detail
.as_deref()
.unwrap_or(metadata.label.as_str())
.trim();
if summary.is_empty() {
task.push_recent(format!("final: {}", activity_status_text(status)));
} else {
task.push_recent(format!("final: {}", bounded_transcript_label(summary, 100)));
}
});
}
pub(super) fn update_subagent_transcript_finished(
&mut self,
id: &ActivityId,
status: ActivityStatus,
metadata: Option<&ActivityMetadata>,
) {
self.update_subagent_transcript(id, |task, is_task| {
if is_task {
task.status = status;
}
let label = metadata
.map(|metadata| metadata.label.as_str())
.unwrap_or_default()
.trim();
if label.is_empty() || is_task {
task.push_recent(format!("finished: {}", activity_status_text(status)));
} else {
task.push_recent(format!(
"finished {}: {}",
activity_status_text(status),
bounded_transcript_label(label, 80)
));
}
});
}
pub(super) fn update_subagent_transcript_usage(
&mut self,
id: &ActivityId,
current_tokens: usize,
max_tokens: usize,
reasoning_tokens: Option<usize>,
source: ContextUsageSource,
request_sequence: u64,
) {
self.update_subagent_transcript(id, |task, _| {
task.update_usage(
current_tokens,
max_tokens,
reasoning_tokens,
source,
request_sequence,
);
});
}
pub(super) fn update_subagent_transcript(
&mut self,
id: &ActivityId,
update: impl FnOnce(&mut ActiveSubagentTask, bool),
) {
self.retain_valid_active_tool_transcripts();
let mut update = Some(update);
let parent_ids = self
.active_tool_transcripts
.keys()
.cloned()
.collect::<Vec<_>>();
let rendered = parent_ids.into_iter().find_map(|parent_id| {
let task_id = {
let active = self.active_tool_transcripts.get(&parent_id)?;
let subagents = active.subagents.as_ref()?;
self.resolve_subagent_transcript_task(&parent_id, subagents, id)?
};
let active = self.active_tool_transcripts.get_mut(&parent_id)?;
let subagents = active.subagents.as_mut()?;
subagents.ensure_task_from_descendant(&parent_id, &task_id);
let task = subagents.task_for_descendant_mut(&task_id)?;
let is_task = task.id == *id;
let update = update.take()?;
update(task, is_task);
Some((active.index, active.render_entry()))
});
if let Some((absolute_index, entry)) = rendered
&& let Some(index) = self.transcript_local_index(absolute_index)
&& let Some(transcript_entry) = self.transcript.get_mut(index)
{
*transcript_entry = entry;
self.invalidate_transcript_entry_cache(index);
}
}
pub(super) fn has_complete_activity_parent_graph(&self, id: &ActivityId) -> bool {
let Some(_) = self.nodes.get(id) else {
return false;
};
let mut current = id;
let mut visited = HashSet::new();
loop {
if !visited.insert(current.clone()) {
return true;
}
let Some(node) = self.nodes.get(current) else {
return false;
};
match &node.parent_provenance {
ActivityParentProvenance::Unavailable => return false,
ActivityParentProvenance::ExplicitRoot => return true,
ActivityParentProvenance::Declared(declared_parent) => {
if node.parent_id.as_ref() != Some(declared_parent)
|| !self.nodes.contains_key(declared_parent)
{
return false;
}
current = declared_parent;
}
}
}
}
pub(super) fn activity_path_fallback_allowed(&self, id: &ActivityId) -> bool {
self.nodes.get(id).is_none_or(|node| {
matches!(
&node.parent_provenance,
ActivityParentProvenance::Unavailable
)
})
}
pub(super) fn resolve_subagent_transcript_task(
&self,
parent_id: &ActivityId,
subagents: &ActiveSubagentTranscript,
id: &ActivityId,
) -> Option<ActivityId> {
if let Some(task) = subagents.tasks.iter().find(|task| task.id == *id) {
return Some(task.id.clone());
}
if let Some(task) = subagents
.tasks
.iter()
.find(|task| self.activity_is_descendant_of(id, &task.id))
{
return Some(task.id.clone());
}
if self.has_complete_activity_parent_graph(id) || !self.activity_path_fallback_allowed(id) {
return None;
}
if let Some(task) = subagents.task_for_descendant(id) {
return Some(task.id.clone());
}
subagents.task_id_from_prefixed_descendant(parent_id, id)
}
pub(super) fn activity_child_progress(&self, parent_id: &ActivityId) -> String {
let children = self.children.get(parent_id).cloned().unwrap_or_default();
if children.is_empty() {
return "running".to_string();
}
let total = children.len();
let completed = children
.iter()
.filter(|id| {
self.nodes.get(id).is_some_and(|node| {
matches!(
node.status,
ActivityStatus::Success | ActivityStatus::Failed | ActivityStatus::Canceled
)
})
})
.count();
let failed = children
.iter()
.filter(|id| self.nodes.get(id).map(|node| node.status) == Some(ActivityStatus::Failed))
.count();
let running = children.iter().find_map(|id| {
let node = self.nodes.get(id)?;
matches!(
node.status,
ActivityStatus::Running | ActivityStatus::Writing
)
.then(|| node.metadata.label.clone())
});
match running {
Some(label) => format!(
"{completed}/{total} complete{PADDED_INLINE_SEPARATOR}current: {}",
normalize_inline_separators(&label)
),
None if failed > 0 => {
format!("{completed}/{total} complete{PADDED_INLINE_SEPARATOR}{failed} failed")
}
None => format!("{completed}/{total} complete"),
}
}
}