diff --git a/crates/goose/src/agents/platform_extensions/summon.rs b/crates/goose/src/agents/platform_extensions/summon.rs index d6e5cc579..4307f1200 100644 --- a/crates/goose/src/agents/platform_extensions/summon.rs +++ b/crates/goose/src/agents/platform_extensions/summon.rs @@ -83,6 +83,15 @@ pub struct CompletedTask { pub completed_at: Instant, } +/// Result from handle_load_task_result with structured metadata for the caller +#[derive(Debug)] +struct TaskLoadResult { + content: Vec, + status: &'static str, + turns: Option, + duration_secs: Option, +} + #[derive(Debug, Deserialize)] struct AgentMetadata { name: String, @@ -790,13 +799,29 @@ impl SummonClient { let name = source_name.unwrap(); if is_session_id(name) { - let content = self.handle_load_task_result(name, cancel).await?; + let task_result = self.handle_load_task_result(name, cancel).await?; let mut meta = Meta::new(); meta.0.insert( "subagent_session_id".to_string(), serde_json::Value::String(name.to_string()), ); - return Ok(CallToolResult::success(content).with_meta(Some(meta))); + meta.0.insert( + "task_status".to_string(), + serde_json::Value::String(task_result.status.to_string()), + ); + if let Some(turns) = task_result.turns { + meta.0.insert( + "turns_taken".to_string(), + serde_json::Value::Number(turns.into()), + ); + } + if let Some(secs) = task_result.duration_secs { + meta.0.insert( + "duration_secs".to_string(), + serde_json::Value::Number(secs.into()), + ); + } + return Ok(CallToolResult::success(task_result.content).with_meta(Some(meta))); } self.handle_load_source(session_id, name, &working_dir) @@ -808,33 +833,42 @@ impl SummonClient { &self, task_id: &str, cancel: bool, - ) -> Result, String> { + ) -> Result { let mut completed = self.completed_tasks.lock().await; if let Some(task) = completed.remove(task_id) { - let status = if task.result.is_ok() { - "✓ Completed" - } else { - "✗ Failed" + let status_key = match &task.result { + Ok(_) => "completed", + Err(e) if e.starts_with("Task panicked:") => "panicked", + Err(_) => "failed", + }; + let status = match status_key { + "completed" => "✓ Completed", + "panicked" => "✗ Panicked", + _ => "✗ Failed", }; let output = match task.result { Ok(output) => output, Err(error) => format!("Error: {}", error), }; - - return Ok(vec![Content::text(format!( - "# Background Task Result: {}\n\n\ - **Task:** {}\n\ - **Status:** {}\n\ - **Duration:** {} ({} turns)\n\n\ - ## Output\n\n{}", - task_id, - task.description, - status, - round_duration(task.duration), - task.turns_taken, - output - ))]); + return Ok(TaskLoadResult { + content: vec![Content::text(format!( + "# Background Task Result: {}\n\n\ + **Task:** {}\n\ + **Status:** {}\n\ + **Duration:** {} ({} turns)\n\n\ + ## Output\n\n{}", + task_id, + task.description, + status, + round_duration(task.duration), + task.turns_taken, + output + ))], + status: status_key, + turns: Some(task.turns_taken), + duration_secs: Some(task.duration.as_secs()), + }); } drop(completed); @@ -865,18 +899,23 @@ impl SummonClient { } }; - return Ok(vec![Content::text(format!( - "# Background Task Result: {}\n\n\ - **Task:** {}\n\ - **Status:** ⊘ Cancelled\n\ - **Duration:** {} ({} turns)\n\n\ - ## Output\n\n{}", - task_id, - task.description, - round_duration(duration), - turns_taken, - output - ))]); + return Ok(TaskLoadResult { + content: vec![Content::text(format!( + "# Background Task Result: {}\n\n\ + **Task:** {}\n\ + **Status:** ⊘ Cancelled\n\ + **Duration:** {} ({} turns)\n\n\ + ## Output\n\n{}", + task_id, + task.description, + round_duration(duration), + turns_taken, + output + ))], + status: "cancelled", + turns: Some(turns_taken), + duration_secs: Some(duration.as_secs()), + }); } // Wait for the running task to complete, keeping the tool call @@ -899,24 +938,37 @@ impl SummonClient { tokio::select! { result = &mut task.handle => { - let output = match result { - Ok(Ok(s)) => s, - Ok(Err(e)) => format!("Error: {}", e), - Err(e) => format!("Task panicked: {}", e), + let (output, status_key) = match result { + Ok(Ok(s)) => (s, "completed"), + Ok(Err(e)) => (format!("Error: {}", e), "failed"), + Err(e) => (format!("Task panicked: {}", e), "panicked"), }; - return Ok(vec![Content::text(format!( - "# Background Task Result: {}\n\n\ - **Task:** {}\n\ - **Status:** ✓ Completed\n\ - **Duration:** {} ({} turns)\n\n\ - ## Output\n\n{}", - task_id, - task.description, - round_duration(task.started_at.elapsed()), - task.turns.load(Ordering::Relaxed), - output - ))]); + let turns_taken = task.turns.load(Ordering::Relaxed); + let elapsed = task.started_at.elapsed(); + let status_display = match status_key { + "completed" => "✓ Completed", + "panicked" => "✗ Panicked", + _ => "✗ Failed", + }; + return Ok(TaskLoadResult { + content: vec![Content::text(format!( + "# Background Task Result: {}\n\n\ + **Task:** {}\n\ + **Status:** {}\n\ + **Duration:** {} ({} turns)\n\n\ + ## Output\n\n{}", + task_id, + task.description, + status_display, + round_duration(elapsed), + turns_taken, + output + ))], + status: status_key, + turns: Some(turns_taken), + duration_secs: Some(elapsed.as_secs()), + }); } _ = tokio::time::sleep(Duration::from_secs(300)) => { self.background_tasks.lock().await.insert(task_id.to_string(), task); @@ -2476,7 +2528,7 @@ You review code."#; .handle_load_task_result("20260204_1", false) .await .expect("load should wait and return result"); - let text = extract_text(&result[0]); + let text = extract_text(&result.content[0]); assert!(text.contains("Completed")); assert!(text.contains("done")); @@ -2538,13 +2590,15 @@ You review code."#; .handle_load_task_result("20260204_2", false) .await .unwrap(); - let text = extract_text(&result[0]); + let text = extract_text(&result.content[0]); assert!(text.contains("20260204_2")); assert!(text.contains("Successful task")); assert!(text.contains("✓ Completed")); assert!(text.contains("1m")); assert!(text.contains("5 turns")); assert!(text.contains("Task completed successfully with output")); + assert_eq!(result.status, "completed"); + assert_eq!(result.turns, Some(5)); assert!(!client .completed_tasks @@ -2556,9 +2610,10 @@ You review code."#; .handle_load_task_result("20260204_3", false) .await .unwrap(); - let text = extract_text(&result[0]); + let text = extract_text(&result.content[0]); assert!(text.contains("✗ Failed")); assert!(text.contains("Error: Something went wrong")); + assert_eq!(result.status, "failed"); let result = client.handle_load_task_result("20260204_3", false).await; assert!(result.is_err()); @@ -2597,10 +2652,12 @@ You review code."#; .handle_load_task_result("20260204_1", true) .await .unwrap(); - let text = extract_text(&result[0]); + let text = extract_text(&result.content[0]); assert!(text.contains("Cancelled")); assert!(text.contains("20260204_1")); assert!(text.contains("Cancellable task")); + assert_eq!(result.status, "cancelled"); + assert_eq!(result.turns, Some(3)); assert!(token.is_cancelled()); assert!(!client .background_tasks