feat(summon): add structured metadata to task load results (#9521)
Signed-off-by: Douwe M Osinga <douwe@sidewalklabs.com> Co-authored-by: Douwe M Osinga <douwe@sidewalklabs.com>
This commit is contained in:
@@ -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<Content>,
|
||||
status: &'static str,
|
||||
turns: Option<u32>,
|
||||
duration_secs: Option<u64>,
|
||||
}
|
||||
|
||||
#[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<Vec<Content>, String> {
|
||||
) -> Result<TaskLoadResult, String> {
|
||||
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
|
||||
|
||||
Reference in New Issue
Block a user