Skip to main content

clawft_kernel/wasm_runner/
tools_agent.rs

1//! Built-in agent and IPC tool implementations.
2
3use std::sync::Arc;
4
5use super::catalog::builtin_tool_catalog;
6use super::registry::BuiltinTool;
7use super::types::*;
8
9/// Built-in `agent.spawn` tool.
10///
11/// Spawns a new agent process via the kernel's AgentSupervisor.
12/// Always runs natively (needs direct kernel struct access).
13pub struct AgentSpawnTool {
14    spec: BuiltinToolSpec,
15    process_table: Arc<crate::process::ProcessTable>,
16}
17
18impl AgentSpawnTool {
19    pub fn new(process_table: Arc<crate::process::ProcessTable>) -> Self {
20        let catalog = builtin_tool_catalog();
21        let spec = catalog
22            .into_iter()
23            .find(|s| s.name == "agent.spawn")
24            .expect("agent.spawn must be in catalog");
25        Self {
26            spec,
27            process_table,
28        }
29    }
30}
31
32impl BuiltinTool for AgentSpawnTool {
33    fn name(&self) -> &str {
34        "agent.spawn"
35    }
36
37    fn spec(&self) -> &BuiltinToolSpec {
38        &self.spec
39    }
40
41    fn execute(&self, args: serde_json::Value) -> Result<serde_json::Value, ToolError> {
42        let agent_id = args
43            .get("agent_id")
44            .and_then(|v| v.as_str())
45            .ok_or_else(|| ToolError::InvalidArgs("missing 'agent_id' parameter".into()))?;
46
47        let backend = args
48            .get("backend")
49            .and_then(|v| v.as_str())
50            .unwrap_or("native");
51
52        if backend == "wasm" {
53            return Err(ToolError::ExecutionFailed(
54                "WASM backend not yet available for agent.spawn".into(),
55            ));
56        }
57
58        // Create a process entry directly in the process table.
59        // In production this would go through AgentSupervisor::spawn(),
60        // but for the reference tool impl we create the entry directly.
61        let entry = crate::process::ProcessEntry {
62            pid: 0, // assigned by insert()
63            agent_id: agent_id.to_string(),
64            state: crate::process::ProcessState::Running,
65            capabilities: crate::capability::AgentCapabilities::default(),
66            resource_usage: crate::process::ResourceUsage::default(),
67            cancel_token: crate::process::CancellationToken::new(),
68            parent_pid: None,
69        };
70
71        let pid = self
72            .process_table
73            .insert(entry)
74            .map_err(|e| ToolError::ExecutionFailed(format!("spawn failed: {e}")))?;
75
76        Ok(serde_json::json!({
77            "pid": pid,
78            "agent_id": agent_id,
79            "state": "running",
80        }))
81    }
82}
83
84/// Built-in `agent.stop` tool.
85pub struct AgentStopTool {
86    spec: BuiltinToolSpec,
87    process_table: Arc<crate::process::ProcessTable>,
88}
89
90impl AgentStopTool {
91    pub fn new(process_table: Arc<crate::process::ProcessTable>) -> Self {
92        let spec = builtin_tool_catalog()
93            .into_iter()
94            .find(|s| s.name == "agent.stop")
95            .unwrap();
96        Self {
97            spec,
98            process_table,
99        }
100    }
101}
102
103impl BuiltinTool for AgentStopTool {
104    fn name(&self) -> &str {
105        "agent.stop"
106    }
107    fn spec(&self) -> &BuiltinToolSpec {
108        &self.spec
109    }
110    fn execute(&self, args: serde_json::Value) -> Result<serde_json::Value, ToolError> {
111        let pid = args
112            .get("pid")
113            .and_then(|v| v.as_u64())
114            .ok_or_else(|| ToolError::InvalidArgs("missing 'pid'".into()))?;
115        let entry = self
116            .process_table
117            .get(pid)
118            .ok_or_else(|| ToolError::NotFound(format!("pid {pid}")))?;
119        entry.cancel_token.cancel();
120        self.process_table
121            .update_state(pid, crate::process::ProcessState::Stopping)
122            .map_err(|e| ToolError::ExecutionFailed(e.to_string()))?;
123        Ok(serde_json::json!({"stopped": pid, "agent_id": entry.agent_id}))
124    }
125}
126
127/// Built-in `agent.list` tool.
128pub struct AgentListTool {
129    spec: BuiltinToolSpec,
130    process_table: Arc<crate::process::ProcessTable>,
131}
132
133impl AgentListTool {
134    pub fn new(process_table: Arc<crate::process::ProcessTable>) -> Self {
135        let spec = builtin_tool_catalog()
136            .into_iter()
137            .find(|s| s.name == "agent.list")
138            .unwrap();
139        Self {
140            spec,
141            process_table,
142        }
143    }
144}
145
146impl BuiltinTool for AgentListTool {
147    fn name(&self) -> &str {
148        "agent.list"
149    }
150    fn spec(&self) -> &BuiltinToolSpec {
151        &self.spec
152    }
153    fn execute(&self, _args: serde_json::Value) -> Result<serde_json::Value, ToolError> {
154        let list = self.process_table.list();
155        let entries: Vec<serde_json::Value> = list
156            .iter()
157            .map(|e| {
158                serde_json::json!({
159                    "pid": e.pid,
160                    "agent_id": e.agent_id,
161                    "state": format!("{:?}", e.state),
162                })
163            })
164            .collect();
165        Ok(serde_json::json!({"agents": entries, "count": entries.len()}))
166    }
167}
168
169/// Built-in `agent.inspect` tool.
170pub struct AgentInspectTool {
171    spec: BuiltinToolSpec,
172    process_table: Arc<crate::process::ProcessTable>,
173}
174
175impl AgentInspectTool {
176    pub fn new(process_table: Arc<crate::process::ProcessTable>) -> Self {
177        let spec = builtin_tool_catalog()
178            .into_iter()
179            .find(|s| s.name == "agent.inspect")
180            .unwrap();
181        Self {
182            spec,
183            process_table,
184        }
185    }
186}
187
188impl BuiltinTool for AgentInspectTool {
189    fn name(&self) -> &str {
190        "agent.inspect"
191    }
192    fn spec(&self) -> &BuiltinToolSpec {
193        &self.spec
194    }
195    fn execute(&self, args: serde_json::Value) -> Result<serde_json::Value, ToolError> {
196        let pid = args
197            .get("pid")
198            .and_then(|v| v.as_u64())
199            .ok_or_else(|| ToolError::InvalidArgs("missing 'pid'".into()))?;
200        let entry = self
201            .process_table
202            .get(pid)
203            .ok_or_else(|| ToolError::NotFound(format!("pid {pid}")))?;
204        Ok(serde_json::json!({
205            "pid": entry.pid,
206            "agent_id": entry.agent_id,
207            "state": format!("{:?}", entry.state),
208            "parent_pid": entry.parent_pid,
209            "resource_usage": {
210                "messages_sent": entry.resource_usage.messages_sent,
211                "tool_calls": entry.resource_usage.tool_calls,
212                "cpu_time_ms": entry.resource_usage.cpu_time_ms,
213            },
214            "capabilities": {
215                "can_spawn": entry.capabilities.can_spawn,
216                "can_ipc": entry.capabilities.can_ipc,
217                "can_exec_tools": entry.capabilities.can_exec_tools,
218                "can_network": entry.capabilities.can_network,
219            },
220        }))
221    }
222}
223
224/// Built-in `agent.send` tool.
225#[cfg(feature = "native")]
226pub struct AgentSendTool {
227    spec: BuiltinToolSpec,
228    process_table: Arc<crate::process::ProcessTable>,
229    a2a: Arc<crate::a2a::A2ARouter>,
230}
231
232#[cfg(feature = "native")]
233impl AgentSendTool {
234    pub fn new(
235        process_table: Arc<crate::process::ProcessTable>,
236        a2a: Arc<crate::a2a::A2ARouter>,
237    ) -> Self {
238        let spec = builtin_tool_catalog()
239            .into_iter()
240            .find(|s| s.name == "agent.send")
241            .unwrap();
242        Self {
243            spec,
244            process_table,
245            a2a,
246        }
247    }
248}
249
250#[cfg(feature = "native")]
251impl BuiltinTool for AgentSendTool {
252    fn name(&self) -> &str {
253        "agent.send"
254    }
255    fn spec(&self) -> &BuiltinToolSpec {
256        &self.spec
257    }
258    fn execute(&self, args: serde_json::Value) -> Result<serde_json::Value, ToolError> {
259        let pid = args
260            .get("pid")
261            .and_then(|v| v.as_u64())
262            .ok_or_else(|| ToolError::InvalidArgs("missing 'pid'".into()))?;
263        let message = args
264            .get("message")
265            .cloned()
266            .ok_or_else(|| ToolError::InvalidArgs("missing 'message'".into()))?;
267        // Verify target exists
268        let _ = self
269            .process_table
270            .get(pid)
271            .ok_or_else(|| ToolError::NotFound(format!("pid {pid}")))?;
272        let msg = crate::ipc::KernelMessage::new(
273            0, // from kernel
274            crate::ipc::MessageTarget::Process(pid),
275            crate::ipc::MessagePayload::Json(message),
276        );
277        let msg_id = msg.id.clone();
278        // Use blocking send since BuiltinTool::execute is sync
279        // In production, agent.send would go through the async agent loop
280        let a2a = self.a2a.clone();
281        std::thread::spawn(move || {
282            let rt = tokio::runtime::Builder::new_current_thread()
283                .enable_all()
284                .build()
285                .unwrap();
286            rt.block_on(async { a2a.send(msg).await })
287        })
288        .join()
289        .map_err(|_| ToolError::ExecutionFailed("send thread panicked".into()))?
290        .map_err(|e| ToolError::ExecutionFailed(e.to_string()))?;
291        Ok(serde_json::json!({"sent": true, "pid": pid, "msg_id": msg_id}))
292    }
293}
294
295/// Built-in `agent.suspend` tool.
296pub struct AgentSuspendTool {
297    spec: BuiltinToolSpec,
298    process_table: Arc<crate::process::ProcessTable>,
299}
300
301impl AgentSuspendTool {
302    pub fn new(process_table: Arc<crate::process::ProcessTable>) -> Self {
303        let spec = builtin_tool_catalog()
304            .into_iter()
305            .find(|s| s.name == "agent.suspend")
306            .unwrap();
307        Self {
308            spec,
309            process_table,
310        }
311    }
312}
313
314impl BuiltinTool for AgentSuspendTool {
315    fn name(&self) -> &str {
316        "agent.suspend"
317    }
318    fn spec(&self) -> &BuiltinToolSpec {
319        &self.spec
320    }
321    fn execute(&self, args: serde_json::Value) -> Result<serde_json::Value, ToolError> {
322        let pid = args
323            .get("pid")
324            .and_then(|v| v.as_u64())
325            .ok_or_else(|| ToolError::InvalidArgs("missing 'pid'".into()))?;
326        self.process_table
327            .update_state(pid, crate::process::ProcessState::Suspended)
328            .map_err(|e| ToolError::ExecutionFailed(e.to_string()))?;
329        Ok(serde_json::json!({"suspended": pid}))
330    }
331}
332
333/// Built-in `agent.resume` tool.
334pub struct AgentResumeTool {
335    spec: BuiltinToolSpec,
336    process_table: Arc<crate::process::ProcessTable>,
337}
338
339impl AgentResumeTool {
340    pub fn new(process_table: Arc<crate::process::ProcessTable>) -> Self {
341        let spec = builtin_tool_catalog()
342            .into_iter()
343            .find(|s| s.name == "agent.resume")
344            .unwrap();
345        Self {
346            spec,
347            process_table,
348        }
349    }
350}
351
352impl BuiltinTool for AgentResumeTool {
353    fn name(&self) -> &str {
354        "agent.resume"
355    }
356    fn spec(&self) -> &BuiltinToolSpec {
357        &self.spec
358    }
359    fn execute(&self, args: serde_json::Value) -> Result<serde_json::Value, ToolError> {
360        let pid = args
361            .get("pid")
362            .and_then(|v| v.as_u64())
363            .ok_or_else(|| ToolError::InvalidArgs("missing 'pid'".into()))?;
364        self.process_table
365            .update_state(pid, crate::process::ProcessState::Running)
366            .map_err(|e| ToolError::ExecutionFailed(e.to_string()))?;
367        Ok(serde_json::json!({"resumed": pid}))
368    }
369}
370
371// ---------------------------------------------------------------------------
372// IPC tool implementations
373// ---------------------------------------------------------------------------
374
375/// Built-in `ipc.send` tool.
376///
377/// Sends a message to a target PID or topic via kernel IPC.
378pub struct IpcSendTool {
379    spec: BuiltinToolSpec,
380}
381
382impl Default for IpcSendTool {
383    fn default() -> Self {
384        Self::new()
385    }
386}
387
388impl IpcSendTool {
389    pub fn new() -> Self {
390        let catalog = builtin_tool_catalog();
391        let spec = catalog
392            .into_iter()
393            .find(|s| s.name == "ipc.send")
394            .expect("ipc.send must be in catalog");
395        Self { spec }
396    }
397}
398
399impl BuiltinTool for IpcSendTool {
400    fn name(&self) -> &str {
401        "ipc.send"
402    }
403    fn spec(&self) -> &BuiltinToolSpec {
404        &self.spec
405    }
406    fn execute(&self, _args: serde_json::Value) -> Result<serde_json::Value, ToolError> {
407        // Stub: real implementation will route through KernelIpc
408        Err(ToolError::ExecutionFailed(
409            "ipc.send requires async kernel context".into(),
410        ))
411    }
412}
413
414/// Built-in `ipc.subscribe` tool.
415///
416/// Subscribes the calling agent to a topic for receiving messages.
417pub struct IpcSubscribeTool {
418    spec: BuiltinToolSpec,
419}
420
421impl Default for IpcSubscribeTool {
422    fn default() -> Self {
423        Self::new()
424    }
425}
426
427impl IpcSubscribeTool {
428    pub fn new() -> Self {
429        let catalog = builtin_tool_catalog();
430        let spec = catalog
431            .into_iter()
432            .find(|s| s.name == "ipc.subscribe")
433            .expect("ipc.subscribe must be in catalog");
434        Self { spec }
435    }
436}
437
438impl BuiltinTool for IpcSubscribeTool {
439    fn name(&self) -> &str {
440        "ipc.subscribe"
441    }
442    fn spec(&self) -> &BuiltinToolSpec {
443        &self.spec
444    }
445    fn execute(&self, _args: serde_json::Value) -> Result<serde_json::Value, ToolError> {
446        // Stub: real implementation will route through TopicRouter
447        Err(ToolError::ExecutionFailed(
448            "ipc.subscribe requires async kernel context".into(),
449        ))
450    }
451}