Initial Agentsd project commit

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-06-09 15:09:56 +08:00
commit 1bf1f08f9a
41 changed files with 8106 additions and 0 deletions

View File

@@ -0,0 +1,124 @@
use std::collections::HashMap;
use std::sync::Mutex;
use tokio::process::Command;
struct ManagedProcess {
cmd: String,
}
/// Process syscall: spawn and manage subprocesses.
/// Uses the real OS PID as the identifier.
pub struct ProcessSyscall {
procs: Mutex<HashMap<u64, ManagedProcess>>,
children: tokio::sync::Mutex<HashMap<u64, tokio::process::Child>>,
}
impl ProcessSyscall {
pub fn new() -> Self {
Self {
procs: Mutex::new(HashMap::new()),
children: tokio::sync::Mutex::new(HashMap::new()),
}
}
pub async fn spawn(
&self,
cmd: &str,
args: &[String],
env: &HashMap<String, String>,
cwd: &str,
) -> Result<u64, String> {
let mut command = Command::new(cmd);
command.args(args);
if !cwd.is_empty() {
command.current_dir(cwd);
}
for (k, v) in env {
command.env(k, v);
}
command
.stdin(std::process::Stdio::piped())
.stdout(std::process::Stdio::piped())
.stderr(std::process::Stdio::piped());
let child = command.spawn().map_err(|e| e.to_string())?;
let pid = child
.id()
.ok_or_else(|| "spawned process has no OS pid".to_string())? as u64;
self.procs.lock().unwrap().insert(
pid,
ManagedProcess {
cmd: cmd.to_string(),
},
);
self.children.lock().await.insert(pid, child);
tracing::info!("process spawned: pid={} cmd={}", pid, cmd);
Ok(pid)
}
pub async fn kill(&self, pid: u64) -> Result<(), String> {
let mut child = {
let mut children = self.children.lock().await;
children.remove(&pid).ok_or_else(|| format!("pid {} not found", pid))?
};
child.kill().await.map_err(|e| e.to_string())?;
self.procs.lock().unwrap().remove(&pid);
tracing::info!("process killed: pid={}", pid);
Ok(())
}
pub async fn wait(&self, pid: u64) -> Result<i32, String> {
let mut child = {
let mut children = self.children.lock().await;
children.remove(&pid).ok_or_else(|| format!("pid {} not found", pid))?
};
self.procs.lock().unwrap().remove(&pid);
let status = child.wait().await.map_err(|e| e.to_string())?;
Ok(status.code().unwrap_or(-1))
}
pub async fn write_stdin(&self, pid: u64, data: &[u8]) -> Result<(), String> {
use tokio::io::AsyncWriteExt;
let mut children = self.children.lock().await;
if let Some(child) = children.get_mut(&pid) {
if let Some(stdin) = child.stdin.as_mut() {
stdin.write_all(data).await.map_err(|e| e.to_string())?;
stdin.flush().await.map_err(|e| e.to_string())?;
Ok(())
} else {
Err("stdin not available".to_string())
}
} else {
Err(format!("pid {} not found", pid))
}
}
pub async fn read_stdout(&self, pid: u64, buf: &mut [u8]) -> Result<usize, String> {
use tokio::io::AsyncReadExt;
let mut children = self.children.lock().await;
if let Some(child) = children.get_mut(&pid) {
if let Some(stdout) = child.stdout.as_mut() {
stdout.read(buf).await.map_err(|e| e.to_string())
} else {
Err("stdout not available".to_string())
}
} else {
Err(format!("pid {} not found", pid))
}
}
pub async fn signal(&self, pid: u64, _signal: i32) -> Result<(), String> {
self.kill(pid).await
}
pub fn list(&self) -> Vec<(u64, String, String)> {
let procs = self.procs.lock().unwrap();
procs
.iter()
.map(|(pid, mp)| (*pid, mp.cmd.clone(), "running".to_string()))
.collect()
}
}