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 { spec, process_table }
26    }
27}
28
29impl BuiltinTool for AgentSpawnTool {
30    fn name(&self) -> &str {
31        "agent.spawn"
32    }
33
34    fn spec(&self) -> &BuiltinToolSpec {
35        &self.spec
36    }
37
38    fn execute(&self, args: serde_json::Value) -> Result<serde_json::Value, ToolError> {
39        let agent_id = args
40            .get("agent_id")
41            .and_then(|v| v.as_str())
42            .ok_or_else(|| ToolError::InvalidArgs("missing 'agent_id' parameter".into()))?;
43
44        let backend = args
45            .get("backend")
46            .and_then(|v| v.as_str())
47            .unwrap_or("native");
48
49        if backend == "wasm" {
50            return Err(ToolError::ExecutionFailed(
51                "WASM backend not yet available for agent.spawn".into(),
52            ));
53        }
54
55        // Create a process entry directly in the process table.
56        // In production this would go through AgentSupervisor::spawn(),
57        // but for the reference tool impl we create the entry directly.
58        let entry = crate::process::ProcessEntry {
59            pid: 0, // assigned by insert()
60            agent_id: agent_id.to_string(),
61            state: crate::process::ProcessState::Running,
62            capabilities: crate::capability::AgentCapabilities::default(),
63            resource_usage: crate::process::ResourceUsage::default(),
64            cancel_token: tokio_util::sync::CancellationToken::new(),
65            parent_pid: None,
66        };
67
68        let pid = self
69            .process_table
70            .insert(entry)
71            .map_err(|e| ToolError::ExecutionFailed(format!("spawn failed: {e}")))?;
72
73        Ok(serde_json::json!({
74            "pid": pid,
75            "agent_id": agent_id,
76            "state": "running",
77        }))
78    }
79}
80
81/// Built-in `agent.stop` tool.
82pub struct AgentStopTool {
83    spec: BuiltinToolSpec,
84    process_table: Arc<crate::process::ProcessTable>,
85}
86
87impl AgentStopTool {
88    pub fn new(process_table: Arc<crate::process::ProcessTable>) -> Self {
89        let spec = builtin_tool_catalog().into_iter().find(|s| s.name == "agent.stop").unwrap();
90        Self { spec, process_table }
91    }
92}
93
94impl BuiltinTool for AgentStopTool {
95    fn name(&self) -> &str { "agent.stop" }
96    fn spec(&self) -> &BuiltinToolSpec { &self.spec }
97    fn execute(&self, args: serde_json::Value) -> Result<serde_json::Value, ToolError> {
98        let pid = args.get("pid").and_then(|v| v.as_u64())
99            .ok_or_else(|| ToolError::InvalidArgs("missing 'pid'".into()))?;
100        let entry = self.process_table.get(pid)
101            .ok_or_else(|| ToolError::NotFound(format!("pid {pid}")))?;
102        entry.cancel_token.cancel();
103        self.process_table.update_state(pid, crate::process::ProcessState::Stopping)
104            .map_err(|e| ToolError::ExecutionFailed(e.to_string()))?;
105        Ok(serde_json::json!({"stopped": pid, "agent_id": entry.agent_id}))
106    }
107}
108
109/// Built-in `agent.list` tool.
110pub struct AgentListTool {
111    spec: BuiltinToolSpec,
112    process_table: Arc<crate::process::ProcessTable>,
113}
114
115impl AgentListTool {
116    pub fn new(process_table: Arc<crate::process::ProcessTable>) -> Self {
117        let spec = builtin_tool_catalog().into_iter().find(|s| s.name == "agent.list").unwrap();
118        Self { spec, process_table }
119    }
120}
121
122impl BuiltinTool for AgentListTool {
123    fn name(&self) -> &str { "agent.list" }
124    fn spec(&self) -> &BuiltinToolSpec { &self.spec }
125    fn execute(&self, _args: serde_json::Value) -> Result<serde_json::Value, ToolError> {
126        let list = self.process_table.list();
127        let entries: Vec<serde_json::Value> = list.iter().map(|e| {
128            serde_json::json!({
129                "pid": e.pid,
130                "agent_id": e.agent_id,
131                "state": format!("{:?}", e.state),
132            })
133        }).collect();
134        Ok(serde_json::json!({"agents": entries, "count": entries.len()}))
135    }
136}
137
138/// Built-in `agent.inspect` tool.
139pub struct AgentInspectTool {
140    spec: BuiltinToolSpec,
141    process_table: Arc<crate::process::ProcessTable>,
142}
143
144impl AgentInspectTool {
145    pub fn new(process_table: Arc<crate::process::ProcessTable>) -> Self {
146        let spec = builtin_tool_catalog().into_iter().find(|s| s.name == "agent.inspect").unwrap();
147        Self { spec, process_table }
148    }
149}
150
151impl BuiltinTool for AgentInspectTool {
152    fn name(&self) -> &str { "agent.inspect" }
153    fn spec(&self) -> &BuiltinToolSpec { &self.spec }
154    fn execute(&self, args: serde_json::Value) -> Result<serde_json::Value, ToolError> {
155        let pid = args.get("pid").and_then(|v| v.as_u64())
156            .ok_or_else(|| ToolError::InvalidArgs("missing 'pid'".into()))?;
157        let entry = self.process_table.get(pid)
158            .ok_or_else(|| ToolError::NotFound(format!("pid {pid}")))?;
159        Ok(serde_json::json!({
160            "pid": entry.pid,
161            "agent_id": entry.agent_id,
162            "state": format!("{:?}", entry.state),
163            "parent_pid": entry.parent_pid,
164            "resource_usage": {
165                "messages_sent": entry.resource_usage.messages_sent,
166                "tool_calls": entry.resource_usage.tool_calls,
167                "cpu_time_ms": entry.resource_usage.cpu_time_ms,
168            },
169            "capabilities": {
170                "can_spawn": entry.capabilities.can_spawn,
171                "can_ipc": entry.capabilities.can_ipc,
172                "can_exec_tools": entry.capabilities.can_exec_tools,
173                "can_network": entry.capabilities.can_network,
174            },
175        }))
176    }
177}
178
179/// Built-in `agent.send` tool.
180pub struct AgentSendTool {
181    spec: BuiltinToolSpec,
182    process_table: Arc<crate::process::ProcessTable>,
183    a2a: Arc<crate::a2a::A2ARouter>,
184}
185
186impl AgentSendTool {
187    pub fn new(
188        process_table: Arc<crate::process::ProcessTable>,
189        a2a: Arc<crate::a2a::A2ARouter>,
190    ) -> Self {
191        let spec = builtin_tool_catalog().into_iter().find(|s| s.name == "agent.send").unwrap();
192        Self { spec, process_table, a2a }
193    }
194}
195
196impl BuiltinTool for AgentSendTool {
197    fn name(&self) -> &str { "agent.send" }
198    fn spec(&self) -> &BuiltinToolSpec { &self.spec }
199    fn execute(&self, args: serde_json::Value) -> Result<serde_json::Value, ToolError> {
200        let pid = args.get("pid").and_then(|v| v.as_u64())
201            .ok_or_else(|| ToolError::InvalidArgs("missing 'pid'".into()))?;
202        let message = args.get("message").cloned()
203            .ok_or_else(|| ToolError::InvalidArgs("missing 'message'".into()))?;
204        // Verify target exists
205        let _ = self.process_table.get(pid)
206            .ok_or_else(|| ToolError::NotFound(format!("pid {pid}")))?;
207        let msg = crate::ipc::KernelMessage::new(
208            0, // from kernel
209            crate::ipc::MessageTarget::Process(pid),
210            crate::ipc::MessagePayload::Json(message),
211        );
212        let msg_id = msg.id.clone();
213        // Use blocking send since BuiltinTool::execute is sync
214        // In production, agent.send would go through the async agent loop
215        let a2a = self.a2a.clone();
216        std::thread::spawn(move || {
217            let rt = tokio::runtime::Builder::new_current_thread()
218                .enable_all()
219                .build()
220                .unwrap();
221            rt.block_on(async { a2a.send(msg).await })
222        }).join()
223            .map_err(|_| ToolError::ExecutionFailed("send thread panicked".into()))?
224            .map_err(|e| ToolError::ExecutionFailed(e.to_string()))?;
225        Ok(serde_json::json!({"sent": true, "pid": pid, "msg_id": msg_id}))
226    }
227}
228
229/// Built-in `agent.suspend` tool.
230pub struct AgentSuspendTool {
231    spec: BuiltinToolSpec,
232    process_table: Arc<crate::process::ProcessTable>,
233}
234
235impl AgentSuspendTool {
236    pub fn new(process_table: Arc<crate::process::ProcessTable>) -> Self {
237        let spec = builtin_tool_catalog().into_iter().find(|s| s.name == "agent.suspend").unwrap();
238        Self { spec, process_table }
239    }
240}
241
242impl BuiltinTool for AgentSuspendTool {
243    fn name(&self) -> &str { "agent.suspend" }
244    fn spec(&self) -> &BuiltinToolSpec { &self.spec }
245    fn execute(&self, args: serde_json::Value) -> Result<serde_json::Value, ToolError> {
246        let pid = args.get("pid").and_then(|v| v.as_u64())
247            .ok_or_else(|| ToolError::InvalidArgs("missing 'pid'".into()))?;
248        self.process_table.update_state(pid, crate::process::ProcessState::Suspended)
249            .map_err(|e| ToolError::ExecutionFailed(e.to_string()))?;
250        Ok(serde_json::json!({"suspended": pid}))
251    }
252}
253
254/// Built-in `agent.resume` tool.
255pub struct AgentResumeTool {
256    spec: BuiltinToolSpec,
257    process_table: Arc<crate::process::ProcessTable>,
258}
259
260impl AgentResumeTool {
261    pub fn new(process_table: Arc<crate::process::ProcessTable>) -> Self {
262        let spec = builtin_tool_catalog().into_iter().find(|s| s.name == "agent.resume").unwrap();
263        Self { spec, process_table }
264    }
265}
266
267impl BuiltinTool for AgentResumeTool {
268    fn name(&self) -> &str { "agent.resume" }
269    fn spec(&self) -> &BuiltinToolSpec { &self.spec }
270    fn execute(&self, args: serde_json::Value) -> Result<serde_json::Value, ToolError> {
271        let pid = args.get("pid").and_then(|v| v.as_u64())
272            .ok_or_else(|| ToolError::InvalidArgs("missing 'pid'".into()))?;
273        self.process_table.update_state(pid, crate::process::ProcessState::Running)
274            .map_err(|e| ToolError::ExecutionFailed(e.to_string()))?;
275        Ok(serde_json::json!({"resumed": pid}))
276    }
277}
278
279// ---------------------------------------------------------------------------
280// IPC tool implementations
281// ---------------------------------------------------------------------------
282
283/// Built-in `ipc.send` tool.
284///
285/// Sends a message to a target PID or topic via kernel IPC.
286pub struct IpcSendTool {
287    spec: BuiltinToolSpec,
288}
289
290impl Default for IpcSendTool {
291    fn default() -> Self {
292        Self::new()
293    }
294}
295
296impl IpcSendTool {
297    pub fn new() -> Self {
298        let catalog = builtin_tool_catalog();
299        let spec = catalog
300            .into_iter()
301            .find(|s| s.name == "ipc.send")
302            .expect("ipc.send must be in catalog");
303        Self { spec }
304    }
305}
306
307impl BuiltinTool for IpcSendTool {
308    fn name(&self) -> &str { "ipc.send" }
309    fn spec(&self) -> &BuiltinToolSpec {
310        &self.spec
311    }
312    fn execute(&self, _args: serde_json::Value) -> Result<serde_json::Value, ToolError> {
313        // Stub: real implementation will route through KernelIpc
314        Err(ToolError::ExecutionFailed("ipc.send requires async kernel context".into()))
315    }
316}
317
318/// Built-in `ipc.subscribe` tool.
319///
320/// Subscribes the calling agent to a topic for receiving messages.
321pub struct IpcSubscribeTool {
322    spec: BuiltinToolSpec,
323}
324
325impl Default for IpcSubscribeTool {
326    fn default() -> Self {
327        Self::new()
328    }
329}
330
331impl IpcSubscribeTool {
332    pub fn new() -> Self {
333        let catalog = builtin_tool_catalog();
334        let spec = catalog
335            .into_iter()
336            .find(|s| s.name == "ipc.subscribe")
337            .expect("ipc.subscribe must be in catalog");
338        Self { spec }
339    }
340}
341
342impl BuiltinTool for IpcSubscribeTool {
343    fn name(&self) -> &str { "ipc.subscribe" }
344    fn spec(&self) -> &BuiltinToolSpec {
345        &self.spec
346    }
347    fn execute(&self, _args: serde_json::Value) -> Result<serde_json::Value, ToolError> {
348        // Stub: real implementation will route through TopicRouter
349        Err(ToolError::ExecutionFailed("ipc.subscribe requires async kernel context".into()))
350    }
351}