agent_block_core/bridge/
sh.rs1use mlua::prelude::*;
13use std::path::PathBuf;
14use std::time::Duration;
15
16use crate::host::HostContext;
17
18pub fn register(lua: &Lua, ctx: &HostContext) -> LuaResult<()> {
19 let sh_tbl = lua.create_table()?;
20 let default_cwd = ctx.project_root.clone();
21
22 sh_tbl.set(
23 "exec",
24 lua.create_async_function(move |lua, (cmd, opts): (String, Option<LuaTable>)| {
25 let default_cwd = default_cwd.clone();
26 async move {
27 let timeout_secs: u64 = opts
28 .as_ref()
29 .and_then(|t| t.get::<Option<u64>>("timeout").ok().flatten())
30 .unwrap_or(30);
31
32 let cwd: PathBuf = opts
33 .as_ref()
34 .and_then(|t| t.get::<Option<String>>("cwd").ok().flatten())
35 .map(PathBuf::from)
36 .unwrap_or_else(|| default_cwd.clone());
37
38 let result = run_async(&cmd, &cwd, Duration::from_secs(timeout_secs)).await;
39
40 match result {
41 Ok((code, stdout, stderr)) => {
42 let t = lua.create_table()?;
43 t.set("ok", true)?;
44 t.set("code", code)?;
45 t.set("stdout", stdout)?;
46 t.set("stderr", stderr)?;
47 Ok(t)
48 }
49 Err(e) => {
50 let t = lua.create_table()?;
51 t.set("ok", false)?;
52 t.set("error", e)?;
53 Ok(t)
54 }
55 }
56 }
57 })?,
58 )?;
59
60 lua.globals().set("sh", sh_tbl)?;
61 Ok(())
62}
63
64async fn run_async(
65 cmd: &str,
66 cwd: &PathBuf,
67 timeout: Duration,
68) -> Result<(i32, String, String), String> {
69 let child = tokio::process::Command::new("sh")
70 .arg("-c")
71 .arg(cmd)
72 .current_dir(cwd)
73 .stdout(std::process::Stdio::piped())
74 .stderr(std::process::Stdio::piped())
75 .spawn()
76 .map_err(|e| format!("exec error: {e}"))?;
77
78 let output = tokio::time::timeout(timeout, child.wait_with_output())
79 .await
80 .map_err(|_| {
81 format!("timeout after {}s", timeout.as_secs())
85 })?
86 .map_err(|e| format!("wait error: {e}"))?;
87
88 let code = output.status.code().unwrap_or(-1);
89 let stdout = String::from_utf8_lossy(&output.stdout).to_string();
90 let stderr = String::from_utf8_lossy(&output.stderr).to_string();
91
92 Ok((code, stdout, stderr))
93}