use std::path::Path;
use sysinfo::{ProcessRefreshKind, ProcessesToUpdate, Signal, System};
use tokio::process::Command;
pub fn kill_pid(pid: u32) -> usize {
let mut sys = System::new();
sys.refresh_processes_specifics(ProcessesToUpdate::All, true, ProcessRefreshKind::nothing());
match sys.process(sysinfo::Pid::from_u32(pid)) {
Some(process) => {
let _ = process
.kill_with(Signal::Term)
.or_else(|| Some(process.kill()));
1
}
None => 0,
}
}
pub fn resolve_program(program: String, cwd: &Path) -> std::ffi::OsString {
let path = Path::new(&program);
if path.components().count() > 1 && path.is_relative() {
cwd.join(path).into_os_string()
} else {
program.into()
}
}
pub async fn spawn_until_lock_published(
exe: &Path,
lock_dir: &Path,
key: &str,
configure: impl FnOnce(&mut Command),
) -> Result<String, crate::error::Error> {
let lock_err = |e: std::io::Error| crate::error::Error::Lockfile {
key: key.to_string(),
source: e,
};
if let Some(listening) = objectiveai_sdk::lockfile::try_read(lock_dir, key)
.await
.map_err(lock_err)?
{
return Ok(listening);
}
let name = exe
.file_name()
.map(|s| s.to_string_lossy().into_owned())
.unwrap_or_else(|| exe.display().to_string());
let mut cmd = Command::new(exe);
cmd.stdin(std::process::Stdio::null())
.stdout(std::process::Stdio::piped())
.stderr(std::process::Stdio::piped());
#[cfg(windows)]
{
cmd.creation_flags(0x0800_0008);
}
configure(&mut cmd);
let mut child = cmd
.spawn()
.map_err(|e| crate::error::Error::Spawn(name.clone(), e))?;
let mut stdout_pipe = child.stdout.take();
let mut stderr_pipe = child.stderr.take();
let stdout_task = tokio::spawn(async move {
use tokio::io::AsyncReadExt;
let mut buf = Vec::new();
if let Some(pipe) = stdout_pipe.as_mut() {
let _ = pipe.read_to_end(&mut buf).await;
}
buf
});
let stderr_task = tokio::spawn(async move {
use tokio::io::AsyncReadExt;
let mut buf = Vec::new();
if let Some(pipe) = stderr_pipe.as_mut() {
let _ = pipe.read_to_end(&mut buf).await;
}
buf
});
let listening = tokio::select! {
read = objectiveai_sdk::lockfile::wait_read(lock_dir, key) => read.map_err(lock_err)?,
status = child.wait() => {
return match objectiveai_sdk::lockfile::try_read(lock_dir, key)
.await
.map_err(lock_err)?
{
Some(listening) => Ok(listening),
None => {
let drain_timeout = std::time::Duration::from_secs(2);
let stdout = match tokio::time::timeout(drain_timeout, stdout_task).await {
Ok(Ok(buf)) => buf,
_ => Vec::new(),
};
let stderr = match tokio::time::timeout(drain_timeout, stderr_task).await {
Ok(Ok(buf)) => buf,
_ => Vec::new(),
};
if let Some(listening) =
objectiveai_sdk::lockfile::try_read(lock_dir, key)
.await
.map_err(lock_err)?
{
return Ok(listening);
}
Err(crate::error::Error::SpawnExitedBeforePublishing {
name,
status: status.map_err(|e| crate::error::Error::Spawn(key.to_string(), e))?,
stdout: String::from_utf8_lossy(&stdout).trim().to_string(),
stderr: String::from_utf8_lossy(&stderr).trim().to_string(),
})
}
};
}
};
drop(child);
Ok(listening)
}
pub fn apply_config_env(cmd: &mut Command, cfg: &crate::Config) {
if cfg.config_set_forbidden {
cmd.env("CONFIG_SET_FORBIDDEN", "true");
}
if let Some(v) = cfg.objectiveai_dir.as_deref() {
cmd.env("OBJECTIVEAI_DIR", v);
}
if let Some(v) = cfg.objectiveai_state.as_deref() {
cmd.env("OBJECTIVEAI_STATE", v);
}
if let Some(v) = cfg.commit_author_name.as_deref() {
cmd.env("COMMIT_AUTHOR_NAME", v);
}
if let Some(v) = cfg.commit_author_email.as_deref() {
cmd.env("COMMIT_AUTHOR_EMAIL", v);
}
cmd.env("OBJECTIVEAI_AGENT_INSTANCE_HIERARCHY", &cfg.agent_instance_hierarchy);
match cfg.agent_id.as_deref() {
Some(v) => {
cmd.env("OBJECTIVEAI_AGENT_ID", v);
}
None => {
cmd.env_remove("OBJECTIVEAI_AGENT_ID");
}
}
match cfg.agent_full_id.as_deref() {
Some(v) => {
cmd.env("OBJECTIVEAI_AGENT_FULL_ID", v);
}
None => {
cmd.env_remove("OBJECTIVEAI_AGENT_FULL_ID");
}
}
match cfg.agent_remote.as_deref() {
Some(v) => {
cmd.env("OBJECTIVEAI_AGENT_REMOTE", v);
}
None => {
cmd.env_remove("OBJECTIVEAI_AGENT_REMOTE");
}
}
match cfg.response_id.as_deref() {
Some(v) => {
cmd.env("OBJECTIVEAI_RESPONSE_ID", v);
}
None => {
cmd.env_remove("OBJECTIVEAI_RESPONSE_ID");
}
}
match cfg.response_ids.as_deref() {
Some(v) => {
cmd.env("OBJECTIVEAI_RESPONSE_IDS", v);
}
None => {
cmd.env_remove("OBJECTIVEAI_RESPONSE_IDS");
}
}
match cfg.mcp_session_id.as_deref() {
Some(v) => {
cmd.env(objectiveai_sdk::mcp::MCP_SESSION_ID_ENV, v);
}
None => {
cmd.env_remove(objectiveai_sdk::mcp::MCP_SESSION_ID_ENV);
}
}
match cfg.plugin_owner.as_deref() {
Some(v) => {
cmd.env("OBJECTIVEAI_PLUGIN_OWNER", v);
}
None => {
cmd.env_remove("OBJECTIVEAI_PLUGIN_OWNER");
}
}
match cfg.plugin_repository.as_deref() {
Some(v) => {
cmd.env("OBJECTIVEAI_PLUGIN_REPOSITORY", v);
}
None => {
cmd.env_remove("OBJECTIVEAI_PLUGIN_REPOSITORY");
}
}
match cfg.plugin_version.as_deref() {
Some(v) => {
cmd.env("OBJECTIVEAI_PLUGIN_VERSION", v);
}
None => {
cmd.env_remove("OBJECTIVEAI_PLUGIN_VERSION");
}
}
cmd.env("DAEMON_ADDRESS", &cfg.daemon_address);
cmd.env("DAEMON_PORT", cfg.daemon_port.to_string());
match cfg.daemon_secret.as_deref() {
Some(v) => {
cmd.env("DAEMON_SECRET", v);
}
None => {
cmd.env_remove("DAEMON_SECRET");
}
}
}