feat(subagent): add progress event handling and formatting for subagent execution
This commit is contained in:
@@ -368,6 +368,7 @@ pub fn trigger_review(state: &mut AppStateRest) {
|
||||
SubagentEvent::StepFailed { step, error } => {
|
||||
tracing::warn!("[review] step {} failed: {}", step, error);
|
||||
}
|
||||
SubagentEvent::Progress(_) => {}
|
||||
SubagentEvent::Completed { .. } => {
|
||||
tracing::debug!("[review] completed");
|
||||
}
|
||||
|
||||
@@ -296,6 +296,17 @@ fn generate_workspace_tree(roots: &[std::path::PathBuf]) -> String {
|
||||
out
|
||||
}
|
||||
|
||||
fn format_subagent_progress(prefix: &str, text: &str) -> String {
|
||||
let lines: Vec<&str> = text.lines().filter(|l| !l.trim().is_empty()).collect();
|
||||
if lines.is_empty() {
|
||||
format!("{prefix}...")
|
||||
} else if lines.len() == 1 {
|
||||
format!("{prefix}: {}", lines[0])
|
||||
} else {
|
||||
lines[lines.len() - 2..].join("\n")
|
||||
}
|
||||
}
|
||||
|
||||
/// Synchronous subagent entry point: run up to `ctx.max_steps` iterations
|
||||
/// of the LLM tool loop.
|
||||
///
|
||||
@@ -369,6 +380,10 @@ pub fn run_subagent(ctx: &SubagentContext, tx: &mpsc::Sender<SubagentEvent>) ->
|
||||
anyhow::bail!("subagent aborted by parent at step {step}");
|
||||
}
|
||||
|
||||
let tx_clone = tx.clone();
|
||||
let mut current_thinking = String::new();
|
||||
let mut current_token = String::new();
|
||||
|
||||
// Use streaming API so the abort flag is checked per SSE event,
|
||||
// making the subagent responsive to cancellation even during an
|
||||
// LLM call (non-streaming would block for 10-30s unchecked).
|
||||
@@ -377,13 +392,24 @@ pub fn run_subagent(ctx: &SubagentContext, tx: &mpsc::Sender<SubagentEvent>) ->
|
||||
tdefs_opt.clone(),
|
||||
Some(0.7),
|
||||
Some(4096),
|
||||
|_event| -> bool {
|
||||
|event| -> bool {
|
||||
// Check abort on every SSE event for responsive cancellation.
|
||||
if ctx.abort_flag.as_ref().is_some_and(|f| f.load(std::sync::atomic::Ordering::SeqCst)) {
|
||||
return false; // signals provider to abort
|
||||
}
|
||||
// We don't stream tokens to the UI for subagents — just
|
||||
// need the assembled message at the end.
|
||||
match event {
|
||||
crate::app::runtime::stream::StreamEvent::Reasoning(text) => {
|
||||
current_thinking.push_str(text);
|
||||
let prog = format_subagent_progress("thinking", ¤t_thinking);
|
||||
let _ = tx_clone.blocking_send(SubagentEvent::Progress(prog));
|
||||
}
|
||||
crate::app::runtime::stream::StreamEvent::Token(text) => {
|
||||
current_token.push_str(text);
|
||||
let prog = format_subagent_progress("replying", ¤t_token);
|
||||
let _ = tx_clone.blocking_send(SubagentEvent::Progress(prog));
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
true
|
||||
},
|
||||
);
|
||||
|
||||
@@ -31,4 +31,5 @@ pub enum SubagentEvent {
|
||||
#[allow(dead_code)]
|
||||
output: String,
|
||||
},
|
||||
Progress(String),
|
||||
}
|
||||
|
||||
@@ -238,6 +238,21 @@ fn spawn_single_agent(
|
||||
SubagentEvent::StepFailed { step, error } => {
|
||||
tracing::warn!("[subagent] step {} failed: {}", step, error);
|
||||
}
|
||||
SubagentEvent::Progress(prog) => {
|
||||
if let Some(ref f) = drain_live {
|
||||
f(
|
||||
drain_agent_id.clone(),
|
||||
drain_agent_name.clone(),
|
||||
AgentStatus {
|
||||
state: AgentState::Running,
|
||||
started_at: Some(drain_started_at),
|
||||
completed_at: None,
|
||||
error: None,
|
||||
progress: Some(prog.clone()),
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
SubagentEvent::Completed { .. } => {
|
||||
tracing::debug!("[subagent] completed");
|
||||
}
|
||||
|
||||
@@ -141,10 +141,12 @@ pub fn draw_workflow_panel(frame: &mut Frame, area: Rect, state: &crate::app::st
|
||||
Span::styled(err.clone(), Style::default().fg(Theme::ERROR)),
|
||||
]));
|
||||
} else if let Some(ref prog) = agent.status.progress {
|
||||
card_lines.push(Line::from(vec![
|
||||
Span::styled(" ", Style::default()),
|
||||
Span::styled(prog.clone(), Style::default().fg(Theme::TEXT_DIM).add_modifier(Modifier::ITALIC)),
|
||||
]));
|
||||
for line in prog.lines().take(2) {
|
||||
card_lines.push(Line::from(vec![
|
||||
Span::styled(" ", Style::default()),
|
||||
Span::styled(line.to_string(), Style::default().fg(Theme::TEXT_DIM).add_modifier(Modifier::ITALIC)),
|
||||
]));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user