systemprompt_agent/services/agent_orchestration/process/
command.rs1use crate::services::shared::Result;
8use std::fs::{self, File};
9use std::path::{Path, PathBuf};
10use std::process::Command;
11use systemprompt_models::{CliPaths, Config, Secrets};
12
13use crate::services::agent_orchestration::{OrchestrationError, OrchestrationResult};
14
15const MAX_LOG_SIZE: u64 = 10 * 1024 * 1024;
16
17pub fn rotate_log_if_needed(log_path: &Path) -> Result<()> {
18 if let Ok(metadata) = fs::metadata(log_path)
19 && metadata.len() > MAX_LOG_SIZE
20 {
21 let backup_path = log_path.with_extension("log.old");
22 fs::rename(log_path, &backup_path)?;
23 }
24 Ok(())
25}
26
27pub fn prepare_agent_log_file(agent_name: &str, log_dir: &Path) -> OrchestrationResult<File> {
28 if let Err(e) = fs::create_dir_all(log_dir) {
29 tracing::error!(
30 error = %e,
31 path = %log_dir.display(),
32 "Failed to create agent log directory - agent may fail to start"
33 );
34 }
35
36 let log_file_path = log_dir.join(format!("agent-{}.log", agent_name));
37 if let Err(e) = rotate_log_if_needed(&log_file_path) {
38 tracing::warn!(
39 error = %e,
40 path = %log_file_path.display(),
41 "Failed to rotate agent log file"
42 );
43 }
44
45 fs::OpenOptions::new()
46 .create(true)
47 .append(true)
48 .open(&log_file_path)
49 .map_err(|e| {
50 OrchestrationError::ProcessSpawnFailed(format!(
51 "Failed to create log file {}: {}",
52 log_file_path.display(),
53 e
54 ))
55 })
56}
57
58#[derive(Debug)]
59pub struct BuildAgentCommandParams<'a> {
60 pub binary_path: &'a PathBuf,
61 pub agent_name: &'a str,
62 pub port: u16,
63 pub profile_path: &'a str,
64 pub secrets: &'a Secrets,
65 pub config: &'a Config,
66 pub log_file: File,
67}
68
69pub fn build_agent_command(params: BuildAgentCommandParams<'_>) -> Command {
70 let BuildAgentCommandParams {
71 binary_path,
72 agent_name,
73 port,
74 profile_path,
75 secrets,
76 config,
77 log_file,
78 } = params;
79 let mut command = Command::new(binary_path);
80 for arg in CliPaths::agent_run_args() {
81 command.arg(arg);
82 }
83 command
84 .arg("--agent-name")
85 .arg(agent_name)
86 .arg("--port")
87 .arg(port.to_string())
88 .env_clear();
89 if let Ok(path) = std::env::var("PATH") {
90 command.env("PATH", path);
91 }
92 if let Ok(home) = std::env::var("HOME") {
93 command.env("HOME", home);
94 }
95 if let Ok(trusted) = std::env::var(systemprompt_models::net::TRUSTED_HTTP_HOSTS_ENV) {
102 command.env(systemprompt_models::net::TRUSTED_HTTP_HOSTS_ENV, trusted);
103 }
104 command
105 .env("SYSTEMPROMPT_PROFILE", profile_path)
106 .env(systemprompt_models::subprocess::SUBPROCESS_MARKER_ENV, "1")
107 .env(systemprompt_models::subprocess::AGENT_NAME_ENV, agent_name)
108 .env("AGENT_PORT", port.to_string())
109 .env("DATABASE_TYPE", &config.database_type)
110 .stdout(std::process::Stdio::null())
111 .stderr(std::process::Stdio::from(log_file))
112 .stdin(std::process::Stdio::null());
113
114 for (k, v) in secrets.to_subprocess_env() {
115 command.env(k, v);
116 }
117
118 if let Ok(fly_app) = std::env::var("FLY_APP_NAME") {
119 command.env("FLY_APP_NAME", fly_app);
120 }
121
122 place_in_own_process_group(&mut command);
123
124 command
125}
126
127#[cfg(unix)]
128fn place_in_own_process_group(command: &mut Command) {
129 use std::os::unix::process::CommandExt;
130 command.process_group(0);
134}
135
136#[cfg(windows)]
137fn place_in_own_process_group(command: &mut Command) {
138 use std::os::windows::process::CommandExt;
139 const CREATE_NEW_PROCESS_GROUP: u32 = 0x0000_0200;
140 command.creation_flags(CREATE_NEW_PROCESS_GROUP);
141}