Skip to main content

synaps_cli/tools/subagent/
collect.rs

1//! SubagentCollectTool — check if a reactive subagent is done and return its result.
2//!
3//! Non-blocking — checks the registry once and returns immediately.
4//! If the subagent is still running, returns status + partial output.
5//! If done, returns the full result. The natural pair to `subagent_start` —
6//! start async, check when you want the answer.
7//!
8
9use serde_json::{json, Value};
10use crate::{Result, RuntimeError};
11use super::super::{Tool, ToolContext};
12use crate::runtime::subagent::SubagentStatus;
13
14
15pub struct SubagentCollectTool;
16
17#[async_trait::async_trait]
18impl Tool for SubagentCollectTool {
19    fn name(&self) -> &str { "subagent_collect" }
20
21    fn description(&self) -> &str {
22        "Check if a reactive subagent is done and return its result. Non-blocking — \
23         returns immediately. If still running, returns status and partial output. \
24         If finished, returns the full result. Call repeatedly to poll for completion."
25    }
26
27    fn parameters(&self) -> Value {
28        json!({
29            "type": "object",
30            "properties": {
31                "handle_id": {
32                    "type": "string",
33                    "description": "Handle ID returned by subagent_start (e.g. \"sa_3\")."
34                }
35            },
36            "required": ["handle_id"]
37        })
38    }
39
40    async fn execute(&self, params: Value, ctx: ToolContext) -> Result<String> {
41        let handle_id = params["handle_id"].as_str()
42            .ok_or_else(|| RuntimeError::Tool("Missing 'handle_id' parameter".to_string()))?
43            .to_string();
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        let handle = reg.get(&handle_id)
52            .ok_or_else(|| RuntimeError::Tool(
53                format!("No subagent found with handle_id '{}'", handle_id)
54            ))?;
55
56        // Clone all needed data under the lock, then drop before char traversal
57        let status = handle.status();
58        let output: String = handle.partial_output();
59        let elapsed = handle.elapsed_secs();
60        let _ = handle;
61        drop(reg);
62
63        if status == SubagentStatus::Running {
64            // Still going — return current state, don't block
65            let char_count = output.chars().count();
66            let output_so_far: String = if char_count > 500 {
67                output.chars().skip(char_count - 500).collect()
68            } else {
69                output
70            };
71            return Ok(json!({
72                "handle_id":    handle_id,
73                "status":       "running",
74                "elapsed_secs": (elapsed * 10.0).round() / 10.0,
75                "output_so_far": output_so_far
76            }).to_string());
77        }
78
79        // Done — return full result
80        Ok(json!({
81            "handle_id": handle_id,
82            "status":    status.as_str(),
83            "output":    output
84        }).to_string())
85    }
86}