Skip to main content

agent_engine/tools/shell/
start.rs

1//! `shell_start` tool — create a new interactive shell session.
2
3use crate::tools::{Tool, ToolContext};
4use crate::{Result, RuntimeError};
5use serde_json::{json, Value};
6
7pub struct ShellStartTool;
8
9#[async_trait::async_trait]
10impl Tool for ShellStartTool {
11    fn origin(&self) -> crate::tools::ToolOrigin {
12        crate::tools::ToolOrigin::Builtin
13    }
14
15    fn name(&self) -> &str {
16        "shell_start"
17    }
18
19    fn description(&self) -> &str {
20        "Start a new interactive shell session with a PTY. Returns a session ID and the initial output. Use shell_send to interact and shell_end to close."
21    }
22
23    fn parameters(&self) -> Value {
24        json!({
25            "type": "object",
26            "properties": {
27                "command": {
28                    "type": "string",
29                    "description": "Command to run (default: user's default shell). Examples: 'bash', 'python3', 'ssh user@host'"
30                },
31                "working_directory": {
32                    "type": "string",
33                    "description": "Working directory for the session (default: current directory)"
34                },
35                "env": {
36                    "type": "object",
37                    "description": "Additional environment variables as key-value pairs",
38                    "additionalProperties": { "type": "string" }
39                },
40                "rows": {
41                    "type": "integer",
42                    "description": "Terminal rows (default: from config, fallback 24)"
43                },
44                "cols": {
45                    "type": "integer",
46                    "description": "Terminal columns (default: from config, fallback 80)"
47                },
48                "readiness_timeout_ms": {
49                    "type": "integer",
50                    "description": "Override output readiness timeout for this session (ms)"
51                },
52                "idle_timeout": {
53                    "type": "integer",
54                    "description": "Override idle timeout for this session (seconds)"
55                }
56            },
57            "required": []
58        })
59    }
60
61    async fn execute(&self, params: Value, ctx: ToolContext) -> Result<String> {
62        let mgr = ctx
63            .capabilities
64            .session_manager
65            .as_ref()
66            .ok_or_else(|| RuntimeError::Tool("Shell sessions not available".into()))?;
67
68        let command = params["command"].as_str().map(|s| s.to_string());
69        let working_directory = params["working_directory"]
70            .as_str()
71            .map(|s| s.to_string())
72            .or_else(|| {
73                ctx.capabilities
74                    .cwd
75                    .as_ref()
76                    .map(|p| p.to_string_lossy().into_owned())
77            });
78        let rows = params["rows"].as_u64().map(|r| r as u16);
79        let cols = params["cols"].as_u64().map(|c| c as u16);
80        let readiness_timeout_ms = params["readiness_timeout_ms"].as_u64();
81        let idle_timeout = params["idle_timeout"].as_u64();
82
83        let env = params["env"]
84            .as_object()
85            .map(|obj| {
86                obj.iter()
87                    .filter_map(|(k, v)| v.as_str().map(|s| (k.clone(), s.to_string())))
88                    .collect()
89            })
90            .unwrap_or_default();
91
92        let opts = super::SessionOpts {
93            command,
94            working_directory,
95            env,
96            rows,
97            cols,
98            readiness_timeout_ms,
99            idle_timeout,
100        };
101
102        let (session_id, output, status) = mgr
103            .create_session_cancellable(
104                opts,
105                ctx.channels.tx_delta.as_ref(),
106                ctx.limits.max_tool_buffer,
107                ctx.capabilities.launch_cancel.as_ref(),
108            )
109            .await?;
110
111        let mut result = format!("[Session {} | {}]\n", session_id, status);
112        if !output.is_empty() {
113            result.push_str(&output);
114        }
115        Ok(result)
116    }
117}