Skip to main content

agent_engine/tools/subagent/
status.rs

1//! SubagentStatusTool — poll the current state of a running or completed reactive subagent.
2//!
3//! Returns a lightweight snapshot: lifecycle status, last 500 chars of output,
4//! elapsed wall-clock seconds, and tool-use count. Non-blocking — always returns
5//! immediately regardless of the subagent's progress.
6
7use serde_json::{json, Value};
8use crate::{Result, RuntimeError};
9use super::super::{Tool, ToolContext};
10
11pub struct SubagentStatusTool;
12
13#[async_trait::async_trait]
14impl Tool for SubagentStatusTool {
15    fn name(&self) -> &str { "subagent_status" }
16
17    fn description(&self) -> &str {
18        "Poll the current state of a reactive subagent. Returns status \
19         (running/completed/cancelled/timed_out/failed), the last 500 characters of output \
20         produced so far, elapsed time in seconds, and the number of tool calls \
21         made. If the status is 'failed', an 'error' field contains the failure reason. \
22         Non-blocking — returns immediately."
23    }
24
25    fn parameters(&self) -> Value {
26        json!({
27            "type": "object",
28            "properties": {
29                "handle_id": {
30                    "type": "string",
31                    "description": "Handle ID returned by subagent_start (e.g. \"sa_3\")."
32                }
33            },
34            "required": ["handle_id"]
35        })
36    }
37
38    async fn execute(&self, params: Value, ctx: ToolContext) -> Result<String> {
39        let handle_id = params["handle_id"].as_str()
40            .ok_or_else(|| RuntimeError::Tool("Missing 'handle_id' parameter".to_string()))?
41            .to_string();
42
43        // ── Registry lookup ────────────────────────────────────────────────────
44
45        let registry = ctx.capabilities.subagent_registry.as_ref()
46            .ok_or_else(|| RuntimeError::Tool(
47                "SubagentRegistry not available on this ToolContext".to_string()
48            ))?;
49
50        let reg = registry.lock().unwrap();
51
52        let handle = reg.get(&handle_id)
53            .ok_or_else(|| RuntimeError::Tool(
54                format!("No subagent found with handle_id '{}'", handle_id)
55            ))?;
56
57        // ── Build response ─────────────────────────────────────────────────────
58        // Clone all needed data under the lock, then drop lock before char traversal
59        let full: String = handle.partial_output();
60        let agent_name = handle.agent_name.clone();
61        let status = handle.status();
62        let status_str = status.as_str().to_string();
63        let elapsed = handle.elapsed_secs();
64        let tool_count = handle.tool_log().len();
65        let _ = handle;
66        drop(reg);
67
68        let char_count = full.chars().count();
69        let partial_output: String = if char_count > 500 {
70            full.chars().skip(char_count - 500).collect()
71        } else {
72            full
73        };
74
75        let mut resp = json!({
76            "handle_id":      handle_id,
77            "agent_name":     agent_name,
78            "status":         status_str,
79            "partial_output": partial_output,
80            "elapsed_secs":   (elapsed * 10.0).round() / 10.0,
81            "tool_count":     tool_count
82        });
83        if let Some(reason) = status.failure_reason() {
84            resp["error"] = json!(reason);
85        }
86        Ok(resp.to_string())
87    }
88}