agent_engine/tools/subagent/
status.rs1use super::super::{Tool, ToolContext};
8use crate::{Result, RuntimeError};
9use serde_json::{json, Value};
10
11pub struct SubagentStatusTool;
12
13#[async_trait::async_trait]
14impl Tool for SubagentStatusTool {
15 fn origin(&self) -> crate::tools::ToolOrigin {
16 crate::tools::ToolOrigin::Builtin
17 }
18
19 fn name(&self) -> &str {
20 "subagent_status"
21 }
22
23 fn description(&self) -> &str {
24 "Poll the current state of a reactive subagent. Returns status \
25 (running/completed/cancelled/timed_out/failed), the last 500 characters of output \
26 produced so far, elapsed time in seconds, and the number of tool calls \
27 made. If the status is 'failed', an 'error' field contains the failure reason. \
28 Non-blocking — returns immediately. Use for an occasional progress peek only: \
29 subagents are reactive and push a completion event that wakes you when they \
30 finish, so never call this in a sleep/poll loop."
31 }
32
33 fn parameters(&self) -> Value {
34 json!({
35 "type": "object",
36 "properties": {
37 "handle_id": {
38 "type": "string",
39 "description": "Handle ID returned by subagent_start (e.g. \"sa_3\")."
40 }
41 },
42 "required": ["handle_id"]
43 })
44 }
45
46 async fn execute(&self, params: Value, ctx: ToolContext) -> Result<String> {
47 let handle_id = params["handle_id"]
48 .as_str()
49 .ok_or_else(|| RuntimeError::Tool("Missing 'handle_id' parameter".to_string()))?
50 .to_string();
51
52 let registry = ctx.capabilities.subagent_registry.as_ref().ok_or_else(|| {
55 RuntimeError::Tool("SubagentRegistry not available on this ToolContext".to_string())
56 })?;
57
58 let reg = registry.lock().unwrap();
59
60 let handle = reg.get(&handle_id).ok_or_else(|| {
61 RuntimeError::Tool(format!("No subagent found with handle_id '{}'", handle_id))
62 })?;
63
64 let full: String = handle.partial_output();
67 let agent_name = handle.agent_name.clone();
68 let status = handle.status();
69 let status_str = status.as_str().to_string();
70 let elapsed = handle.elapsed_secs();
71 let tool_count = handle.tool_log().len();
72 let model = handle.model.clone();
73 let terminal = handle.terminal_diagnostic();
74 let authorization = handle.authorization.clone();
75 let archive_error = handle.archive_error();
76 let _ = handle;
77 drop(reg);
78
79 let char_count = full.chars().count();
80 let partial_output: String = if char_count > 500 {
81 full.chars().skip(char_count - 500).collect()
82 } else {
83 full
84 };
85
86 let output_length = partial_output.chars().count();
87 if let Some(orchestration) = &ctx.capabilities.orchestration {
88 orchestration
89 .poll(&handle_id, &format!("{}:{}", status_str, tool_count))
90 .map_err(RuntimeError::Tool)?;
91 }
92
93 let mut resp = json!({
94 "handle_id": handle_id,
95 "agent_name": agent_name,
96 "status": status_str,
97 "partial_output": partial_output,
98 "elapsed_secs": (elapsed * 10.0).round() / 10.0,
99 "tool_count": tool_count,
100 "model": model,
101 "terminal_cause": terminal,
102 "authorization": authorization,
103 "archiveError": archive_error,
104 "output_length": output_length
105 });
106 if let Some(reason) = status.failure_reason() {
107 resp["error"] = json!(reason);
108 }
109 Ok(resp.to_string())
110 }
111}