1use super::{AgentTool, AgentToolResult, ToolContext, ToolError, ToolExecutionMode, ToolTier};
9use async_trait::async_trait;
10use serde_json::{Value, json};
11use std::collections::HashMap;
12use std::sync::LazyLock;
13use std::sync::Mutex;
14use std::sync::atomic::{AtomicU64, Ordering};
15use tokio::sync::oneshot;
16
17#[derive(Clone, Debug)]
19#[allow(dead_code)]
20struct VibeWorker {
21 id: String,
22 name: String,
23 status: String,
24 created_at: u64,
25}
26
27static VIBE_WORKERS: LazyLock<Mutex<HashMap<String, VibeWorker>>> =
29 LazyLock::new(|| Mutex::new(HashMap::new()));
30
31static NEXT_VIBE_ID: AtomicU64 = AtomicU64::new(1);
32
33pub struct VibeTool;
35
36#[async_trait]
37impl AgentTool for VibeTool {
38 fn name(&self) -> &str {
39 "vibe"
40 }
41
42 fn label(&self) -> &str {
43 "Vibe"
44 }
45
46 fn description(&self) -> &str {
47 concat!(
48 "Manage persistent worker sessions ('vibe mode'). ",
49 "Operations: spawn (create a new worker with a task), ",
50 "send (send a message to a worker), wait (wait for worker output), ",
51 "kill (terminate a worker), list (list all workers). ",
52 "Each worker runs with an isolated context."
53 )
54 }
55
56 fn essential(&self) -> bool {
57 false
58 }
59
60 fn parameters_schema(&self) -> Value {
61 json!({
62 "type": "object",
63 "properties": {
64 "op": {
65 "type": "string",
66 "enum": ["spawn", "send", "wait", "kill", "list"],
67 "description": "Vibe operation."
68 },
69 "name": {
70 "type": "string",
71 "description": "Worker name (for spawn, send, wait, kill). Required for spawn."
72 },
73 "task": {
74 "type": "string",
75 "description": "Task description (for spawn)."
76 },
77 "message": {
78 "type": "string",
79 "description": "Message to send to a worker (for send)."
80 },
81 "id": {
82 "type": "string",
83 "description": "Worker ID (for send, wait, kill)."
84 }
85 },
86 "required": ["op"]
87 })
88 }
89
90 fn intent(&self) -> Option<&str> {
91 Some("Manage vibe worker sessions")
92 }
93
94 fn execution_mode(&self) -> ToolExecutionMode {
95 ToolExecutionMode::SequentialOnly
96 }
97
98 fn tool_tier(&self) -> ToolTier {
99 ToolTier::Exec
100 }
101
102 async fn execute(
103 &self,
104 _tool_call_id: &str,
105 params: Value,
106 _signal: Option<oneshot::Receiver<()>>,
107 _ctx: &ToolContext,
108 ) -> Result<AgentToolResult, ToolError> {
109 let op = params
110 .get("op")
111 .and_then(|v| v.as_str())
112 .ok_or_else(|| "Missing required parameter: op".to_string())?;
113
114 let now = std::time::SystemTime::now()
115 .duration_since(std::time::UNIX_EPOCH)
116 .map(|d| d.as_secs())
117 .unwrap_or(0);
118
119 let mut workers = VIBE_WORKERS
120 .lock()
121 .map_err(|e| format!("Vibe worker lock error: {}", e))?;
122
123 match op {
124 "spawn" => {
125 let name = params
126 .get("name")
127 .and_then(|v| v.as_str())
128 .ok_or_else(|| "Missing 'name' for spawn".to_string())?;
129 let task = params.get("task").and_then(|v| v.as_str()).unwrap_or("");
130
131 if workers
132 .values()
133 .any(|w| w.name == name && w.status == "running")
134 {
135 return Err(format!("Worker '{}' is already running.", name));
136 }
137
138 let id = NEXT_VIBE_ID.fetch_add(1, Ordering::Relaxed);
139 let worker = VibeWorker {
140 id: format!("vibe-{}", id),
141 name: name.to_string(),
142 status: "running".to_string(),
143 created_at: now,
144 };
145
146 workers.insert(worker.id.clone(), worker.clone());
147
148 Ok(AgentToolResult::success(format!(
149 concat!(
150 "Worker spawned.\n",
151 "Name: {}\n",
152 "ID: {}\n",
153 "Task: {}\n\n",
154 "The worker is now running with an isolated context. ",
155 "Use 'send' to communicate with it, 'wait' to receive output, ",
156 "and 'kill' to terminate it."
157 ),
158 name, worker.id, task
159 )))
160 }
161 "send" => {
162 let id = params
163 .get("id")
164 .and_then(|v| v.as_str())
165 .ok_or_else(|| "Missing 'id' for send".to_string())?;
166 let message = params
167 .get("message")
168 .and_then(|v| v.as_str())
169 .ok_or_else(|| "Missing 'message' for send".to_string())?;
170
171 let worker = workers
172 .get_mut(id)
173 .ok_or_else(|| format!("Worker '{}' not found.", id))?;
174
175 if worker.status != "running" {
176 return Err(format!(
177 "Worker '{}' is not running (status: {}).",
178 id, worker.status
179 ));
180 }
181
182 Ok(AgentToolResult::success(format!(
185 concat!(
186 "Message sent to worker '{}'.\n",
187 "Message: {}\n\n",
188 "Use 'wait' to receive the worker's response."
189 ),
190 worker.name, message
191 )))
192 }
193 "wait" => {
194 let _id = params.get("id").and_then(|v| v.as_str());
195
196 let running_count = workers.values().filter(|w| w.status == "running").count();
197
198 if running_count == 0 {
199 Ok(AgentToolResult::success("No running workers to wait for."))
200 } else {
201 Ok(AgentToolResult::success(format!(
202 "Waiting... {} worker(s) running. Use 'list' to check status.",
203 running_count
204 )))
205 }
206 }
207 "kill" => {
208 let id = params
209 .get("id")
210 .and_then(|v| v.as_str())
211 .ok_or_else(|| "Missing 'id' for kill".to_string())?;
212
213 let worker = workers
214 .get_mut(id)
215 .ok_or_else(|| format!("Worker '{}' not found.", id))?;
216
217 if worker.status != "running" {
218 return Err(format!("Worker '{}' is not running.", worker.name));
219 }
220
221 worker.status = "terminated".to_string();
222 let name = worker.name.clone();
223
224 Ok(AgentToolResult::success(format!(
225 "Worker '{}' ({}) terminated.",
226 name, id
227 )))
228 }
229 "list" => {
230 let all: Vec<_> = workers.values().cloned().collect();
231 if all.is_empty() {
232 Ok(AgentToolResult::success("No vibe workers."))
233 } else {
234 let lines: Vec<String> = all
235 .into_iter()
236 .map(|w| format!("- {} (name: {}, status: {})", w.id, w.name, w.status))
237 .collect();
238 let mut output = format!("## Vibe Workers ({})\n", lines.len());
239 output.push_str(&lines.join("\n"));
240 Ok(AgentToolResult::success(output))
241 }
242 }
243 _ => Err(format!("Unknown vibe op: {}", op)),
244 }
245 }
246}
247
248#[cfg(test)]
249mod tests {
250 use super::*;
251
252 #[tokio::test]
253 async fn test_vibe_spawn_list_kill() {
254 *VIBE_WORKERS.lock().unwrap() = HashMap::new();
256
257 let tool = VibeTool;
258
259 let params = json!({"op": "spawn", "name": "worker1", "task": "Analyze log files"});
261 let result = tool
262 .execute("id", params, None, &ToolContext::default())
263 .await
264 .unwrap();
265 assert!(result.success);
266 assert!(result.output.contains("Worker spawned"));
267
268 let params2 = json!({"op": "list"});
270 let result2 = tool
271 .execute("id", params2, None, &ToolContext::default())
272 .await
273 .unwrap();
274 assert!(result2.success);
275 assert!(result2.output.contains("worker1"));
276 }
277}