atman_runtime/tools/
task_ops.rs1use crate::error::RuntimeError;
2use crate::tool::{BoxFut, Tier, Tool, ToolArgs, ToolCtx, ToolResult};
3use crate::value::Value;
4
5pub struct TaskList;
6
7fn extract_optional_string(args: &ToolArgs, name: &str) -> Option<String> {
8 match args.named(name)? {
9 Value::Str(s) => Some(s.clone()),
10 _ => None,
11 }
12}
13
14fn extract_optional_bool(args: &ToolArgs, name: &str) -> Option<bool> {
15 match args.named(name)? {
16 Value::Bool(b) => Some(*b),
17 _ => None,
18 }
19}
20
21impl Tool for TaskList {
22 fn name(&self) -> &str {
23 "task.list"
24 }
25
26 fn tier(&self) -> Tier {
27 Tier::One
28 }
29
30 fn description(&self) -> Option<&str> {
31 Some(
32 "List all background tasks (bash, terminal, flow). Returns array of {id, kind, label, status, elapsed_ms, source_handle}. Filter by kind/status. Default: running only; pass all=true to include completed.",
33 )
34 }
35
36 fn input_schema(&self) -> serde_json::Value {
37 serde_json::json!({
38 "type": "object",
39 "properties": {
40 "kind": {"type": "string", "description": "Filter by kind: bash|terminal|flow"},
41 "status": {"type": "string", "description": "Filter by status: running|ok|err|killed"},
42 "all": {"type": "boolean", "description": "Include completed tasks (default false)"}
43 }
44 })
45 }
46
47 fn call<'a>(&'a self, args: ToolArgs, ctx: &'a ToolCtx) -> BoxFut<'a, ToolResult> {
48 Box::pin(async move {
49 let kind_filter = extract_optional_string(&args, "kind");
50 let status_filter = extract_optional_string(&args, "status");
51 let all = extract_optional_bool(&args, "all").unwrap_or(false);
52
53 let registry = ctx.task_registry.clone().ok_or_else(|| {
54 RuntimeError::ToolFailed("task.list: registry not available".into())
55 })?;
56
57 let mut filter = crate::task_registry::TaskFilter::all();
58 if !all {
59 filter.status = Some(crate::task_registry::TaskStatus::Running);
60 }
61 if let Some(k) = &kind_filter {
62 filter.kind = parse_kind(k);
63 }
64 if let Some(s) = &status_filter {
65 filter.status = parse_status(s);
66 }
67
68 let snapshots = registry.list(&filter);
69 let arr: Vec<Value> = snapshots
70 .iter()
71 .map(|s| {
72 Value::from_json(serde_json::json!({
73 "id": s.id.0.to_string(),
74 "kind": kind_to_str(s.kind),
75 "label": s.label,
76 "status": status_to_str(s.status),
77 "elapsed_ms": s.elapsed_ms(),
78 "source_handle": s.source_handle,
79 }))
80 })
81 .collect();
82 Ok(Value::List(arr))
83 })
84 }
85}
86
87pub struct TaskKill;
88
89impl Tool for TaskKill {
90 fn name(&self) -> &str {
91 "task.kill"
92 }
93
94 fn tier(&self) -> Tier {
95 Tier::Three
96 }
97
98 fn description(&self) -> Option<&str> {
99 Some("Kill a background task by id. Returns {killed: true/false}.")
100 }
101
102 fn input_schema(&self) -> serde_json::Value {
103 serde_json::json!({
104 "type": "object",
105 "properties": {
106 "id": {"type": "string", "description": "Task id to kill"}
107 },
108 "required": ["id"]
109 })
110 }
111
112 fn call<'a>(&'a self, args: ToolArgs, ctx: &'a ToolCtx) -> BoxFut<'a, ToolResult> {
113 Box::pin(async move {
114 let id_str = extract_optional_string(&args, "id").ok_or_else(|| {
115 RuntimeError::ToolFailed("task.kill: missing 'id' argument".into())
116 })?;
117 let id =
118 crate::task_registry::TaskId(uuid::Uuid::parse_str(&id_str).map_err(|e| {
119 RuntimeError::ToolFailed(format!("task.kill: invalid id: {e}"))
120 })?);
121 let registry = ctx.task_registry.clone().ok_or_else(|| {
122 RuntimeError::ToolFailed("task.kill: registry not available".into())
123 })?;
124 let killed = registry.kill(&id);
125 Ok(Value::from_json(serde_json::json!({"killed": killed})))
126 })
127 }
128}
129
130fn parse_kind(s: &str) -> Option<crate::task_registry::TaskKind> {
131 use crate::task_registry::TaskKind;
132 match s {
133 "bash" => Some(TaskKind::Bash),
134 "terminal" => Some(TaskKind::Terminal),
135 "flow" => Some(TaskKind::Flow),
136 _ => None,
137 }
138}
139
140fn parse_status(s: &str) -> Option<crate::task_registry::TaskStatus> {
141 use crate::task_registry::TaskStatus;
142 match s {
143 "running" => Some(TaskStatus::Running),
144 "ok" => Some(TaskStatus::Ok),
145 "err" => Some(TaskStatus::Err),
146 "killed" => Some(TaskStatus::Killed),
147 _ => None,
148 }
149}
150
151fn kind_to_str(k: crate::task_registry::TaskKind) -> &'static str {
152 use crate::task_registry::TaskKind;
153 match k {
154 TaskKind::Bash => "bash",
155 TaskKind::Terminal => "terminal",
156 TaskKind::Flow => "flow",
157 }
158}
159
160fn status_to_str(s: crate::task_registry::TaskStatus) -> &'static str {
161 use crate::task_registry::TaskStatus;
162 match s {
163 TaskStatus::Running => "running",
164 TaskStatus::Killing => "killing",
165 TaskStatus::Ok => "ok",
166 TaskStatus::Err => "err",
167 TaskStatus::Killed => "killed",
168 }
169}