2025-08-20 16:30:34 -07:00
|
|
|
use serde::Deserialize;
|
|
|
|
|
use serde::Serialize;
|
|
|
|
|
use std::path::PathBuf;
|
2025-09-08 18:09:45 -07:00
|
|
|
|
2025-09-10 12:40:24 -07:00
|
|
|
#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize)]
|
|
|
|
|
pub struct ZshShell {
|
2025-09-16 11:32:20 -07:00
|
|
|
pub(crate) shell_path: String,
|
|
|
|
|
pub(crate) zshrc_path: String,
|
2025-07-25 11:45:23 -07:00
|
|
|
}
|
|
|
|
|
|
2025-09-05 09:51:01 -07:00
|
|
|
#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize)]
|
2025-09-10 12:40:24 -07:00
|
|
|
pub struct BashShell {
|
2025-09-16 11:32:20 -07:00
|
|
|
pub(crate) shell_path: String,
|
|
|
|
|
pub(crate) bashrc_path: String,
|
2025-09-05 09:51:01 -07:00
|
|
|
}
|
|
|
|
|
|
2025-08-20 16:30:34 -07:00
|
|
|
#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize)]
|
|
|
|
|
pub struct PowerShellConfig {
|
2025-09-16 11:32:20 -07:00
|
|
|
pub(crate) exe: String, // Executable name or path, e.g. "pwsh" or "powershell.exe".
|
|
|
|
|
pub(crate) bash_exe_fallback: Option<PathBuf>, // In case the model generates a bash command.
|
2025-08-20 16:30:34 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize)]
|
2025-07-25 11:45:23 -07:00
|
|
|
pub enum Shell {
|
2025-09-10 12:40:24 -07:00
|
|
|
Zsh(ZshShell),
|
|
|
|
|
Bash(BashShell),
|
2025-08-20 16:30:34 -07:00
|
|
|
PowerShell(PowerShellConfig),
|
2025-07-25 11:45:23 -07:00
|
|
|
Unknown,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl Shell {
|
2025-08-20 16:30:34 -07:00
|
|
|
pub fn name(&self) -> Option<String> {
|
|
|
|
|
match self {
|
2025-09-10 12:40:24 -07:00
|
|
|
Shell::Zsh(zsh) => std::path::Path::new(&zsh.shell_path)
|
|
|
|
|
.file_name()
|
|
|
|
|
.map(|s| s.to_string_lossy().to_string()),
|
|
|
|
|
Shell::Bash(bash) => std::path::Path::new(&bash.shell_path)
|
2025-09-05 09:51:01 -07:00
|
|
|
.file_name()
|
|
|
|
|
.map(|s| s.to_string_lossy().to_string()),
|
2025-08-20 16:30:34 -07:00
|
|
|
Shell::PowerShell(ps) => Some(ps.exe.clone()),
|
2025-07-25 11:45:23 -07:00
|
|
|
Shell::Unknown => None,
|
|
|
|
|
}
|
|
|
|
|
}
|
2025-09-10 12:40:24 -07:00
|
|
|
}
|
2025-09-05 09:51:01 -07:00
|
|
|
|
|
|
|
|
#[cfg(unix)]
|
2025-09-10 12:40:24 -07:00
|
|
|
fn detect_default_user_shell() -> Shell {
|
2025-09-05 09:51:01 -07:00
|
|
|
use libc::getpwuid;
|
|
|
|
|
use libc::getuid;
|
|
|
|
|
use std::ffi::CStr;
|
2025-07-25 11:45:23 -07:00
|
|
|
|
2025-09-05 09:51:01 -07:00
|
|
|
unsafe {
|
|
|
|
|
let uid = getuid();
|
|
|
|
|
let pw = getpwuid(uid);
|
|
|
|
|
|
|
|
|
|
if !pw.is_null() {
|
|
|
|
|
let shell_path = CStr::from_ptr((*pw).pw_shell)
|
|
|
|
|
.to_string_lossy()
|
|
|
|
|
.into_owned();
|
|
|
|
|
let home_path = CStr::from_ptr((*pw).pw_dir).to_string_lossy().into_owned();
|
|
|
|
|
|
2025-09-10 12:40:24 -07:00
|
|
|
if shell_path.ends_with("/zsh") {
|
|
|
|
|
return Shell::Zsh(ZshShell {
|
|
|
|
|
shell_path,
|
|
|
|
|
zshrc_path: format!("{home_path}/.zshrc"),
|
|
|
|
|
});
|
2025-07-25 11:45:23 -07:00
|
|
|
}
|
|
|
|
|
|
2025-09-10 12:40:24 -07:00
|
|
|
if shell_path.ends_with("/bash") {
|
|
|
|
|
return Shell::Bash(BashShell {
|
|
|
|
|
shell_path,
|
|
|
|
|
bashrc_path: format!("{home_path}/.bashrc"),
|
|
|
|
|
});
|
|
|
|
|
}
|
2025-07-25 11:45:23 -07:00
|
|
|
}
|
|
|
|
|
}
|
2025-09-05 09:51:01 -07:00
|
|
|
Shell::Unknown
|
2025-07-25 11:45:23 -07:00
|
|
|
}
|
|
|
|
|
|
2025-09-05 09:51:01 -07:00
|
|
|
#[cfg(unix)]
|
2025-09-10 12:40:24 -07:00
|
|
|
pub async fn default_user_shell() -> Shell {
|
|
|
|
|
detect_default_user_shell()
|
2025-07-25 11:45:23 -07:00
|
|
|
}
|
|
|
|
|
|
2025-08-20 16:30:34 -07:00
|
|
|
#[cfg(target_os = "windows")]
|
2025-09-10 12:40:24 -07:00
|
|
|
pub async fn default_user_shell() -> Shell {
|
2025-08-20 16:30:34 -07:00
|
|
|
use tokio::process::Command;
|
|
|
|
|
|
|
|
|
|
// Prefer PowerShell 7+ (`pwsh`) if available, otherwise fall back to Windows PowerShell.
|
|
|
|
|
let has_pwsh = Command::new("pwsh")
|
|
|
|
|
.arg("-NoLogo")
|
|
|
|
|
.arg("-NoProfile")
|
|
|
|
|
.arg("-Command")
|
|
|
|
|
.arg("$PSVersionTable.PSVersion.Major")
|
|
|
|
|
.output()
|
|
|
|
|
.await
|
|
|
|
|
.map(|o| o.status.success())
|
|
|
|
|
.unwrap_or(false);
|
|
|
|
|
let bash_exe = if Command::new("bash.exe")
|
|
|
|
|
.arg("--version")
|
|
|
|
|
.output()
|
|
|
|
|
.await
|
|
|
|
|
.ok()
|
|
|
|
|
.map(|o| o.status.success())
|
|
|
|
|
.unwrap_or(false)
|
|
|
|
|
{
|
|
|
|
|
which::which("bash.exe").ok()
|
|
|
|
|
} else {
|
|
|
|
|
None
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
if has_pwsh {
|
|
|
|
|
Shell::PowerShell(PowerShellConfig {
|
|
|
|
|
exe: "pwsh.exe".to_string(),
|
|
|
|
|
bash_exe_fallback: bash_exe,
|
|
|
|
|
})
|
|
|
|
|
} else {
|
|
|
|
|
Shell::PowerShell(PowerShellConfig {
|
|
|
|
|
exe: "powershell.exe".to_string(),
|
|
|
|
|
bash_exe_fallback: bash_exe,
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-09-05 09:51:01 -07:00
|
|
|
#[cfg(all(not(target_os = "windows"), not(unix)))]
|
2025-09-10 12:40:24 -07:00
|
|
|
pub async fn default_user_shell() -> Shell {
|
2025-09-05 09:51:01 -07:00
|
|
|
Shell::Unknown
|
|
|
|
|
}
|
|
|
|
|
|
2025-09-10 12:40:24 -07:00
|
|
|
#[cfg(test)]
|
2025-09-05 09:51:01 -07:00
|
|
|
#[cfg(unix)]
|
2025-09-10 12:40:24 -07:00
|
|
|
mod tests {
|
2025-07-25 11:45:23 -07:00
|
|
|
use super::*;
|
2025-10-28 14:52:52 +00:00
|
|
|
use std::path::PathBuf;
|
2025-09-10 12:40:24 -07:00
|
|
|
use std::process::Command;
|
2025-07-25 11:45:23 -07:00
|
|
|
|
2025-09-10 12:40:24 -07:00
|
|
|
#[tokio::test]
|
|
|
|
|
async fn test_current_shell_detects_zsh() {
|
|
|
|
|
let shell = Command::new("sh")
|
|
|
|
|
.arg("-c")
|
|
|
|
|
.arg("echo $SHELL")
|
2025-07-25 11:45:23 -07:00
|
|
|
.output()
|
2025-09-10 12:40:24 -07:00
|
|
|
.unwrap();
|
2025-09-08 18:09:45 -07:00
|
|
|
|
2025-09-10 12:40:24 -07:00
|
|
|
let home = std::env::var("HOME").unwrap();
|
|
|
|
|
let shell_path = String::from_utf8_lossy(&shell.stdout).trim().to_string();
|
|
|
|
|
if shell_path.ends_with("/zsh") {
|
|
|
|
|
assert_eq!(
|
|
|
|
|
default_user_shell().await,
|
|
|
|
|
Shell::Zsh(ZshShell {
|
|
|
|
|
shell_path: shell_path.to_string(),
|
|
|
|
|
zshrc_path: format!("{home}/.zshrc",),
|
|
|
|
|
})
|
|
|
|
|
);
|
2025-07-25 11:45:23 -07:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-09-05 09:51:01 -07:00
|
|
|
#[tokio::test]
|
|
|
|
|
async fn test_run_with_profile_bash_escaping_and_execution() {
|
|
|
|
|
let shell_path = "/bin/bash";
|
|
|
|
|
|
|
|
|
|
let cases = vec![
|
|
|
|
|
(
|
|
|
|
|
vec!["myecho"],
|
2025-09-10 12:40:24 -07:00
|
|
|
vec![shell_path, "-lc", "source BASHRC_PATH && (myecho)"],
|
2025-09-05 09:51:01 -07:00
|
|
|
Some("It works!\n"),
|
|
|
|
|
),
|
|
|
|
|
(
|
|
|
|
|
vec!["bash", "-lc", "echo 'single' \"double\""],
|
|
|
|
|
vec![
|
|
|
|
|
shell_path,
|
|
|
|
|
"-lc",
|
2025-09-10 12:40:24 -07:00
|
|
|
"source BASHRC_PATH && (echo 'single' \"double\")",
|
2025-09-05 09:51:01 -07:00
|
|
|
],
|
|
|
|
|
Some("single double\n"),
|
|
|
|
|
),
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
for (input, expected_cmd, expected_output) in cases {
|
|
|
|
|
use std::collections::HashMap;
|
|
|
|
|
|
|
|
|
|
use crate::exec::ExecParams;
|
|
|
|
|
use crate::exec::SandboxType;
|
|
|
|
|
use crate::exec::process_exec_tool_call;
|
|
|
|
|
use crate::protocol::SandboxPolicy;
|
|
|
|
|
|
|
|
|
|
let temp_home = tempfile::tempdir().unwrap();
|
|
|
|
|
let bashrc_path = temp_home.path().join(".bashrc");
|
|
|
|
|
std::fs::write(
|
|
|
|
|
&bashrc_path,
|
|
|
|
|
r#"
|
2025-10-28 14:52:52 +00:00
|
|
|
set -x
|
|
|
|
|
function myecho {
|
|
|
|
|
echo 'It works!'
|
|
|
|
|
}
|
|
|
|
|
"#,
|
2025-09-05 09:51:01 -07:00
|
|
|
)
|
|
|
|
|
.unwrap();
|
2025-10-28 14:52:52 +00:00
|
|
|
let command = expected_cmd
|
2025-09-05 09:51:01 -07:00
|
|
|
.iter()
|
2025-09-11 11:59:37 -07:00
|
|
|
.map(|s| s.replace("BASHRC_PATH", bashrc_path.to_str().unwrap()))
|
2025-10-28 14:52:52 +00:00
|
|
|
.collect::<Vec<_>>();
|
2025-09-05 09:51:01 -07:00
|
|
|
|
|
|
|
|
let output = process_exec_tool_call(
|
|
|
|
|
ExecParams {
|
2025-10-28 14:52:52 +00:00
|
|
|
command: command.clone(),
|
2025-09-05 09:51:01 -07:00
|
|
|
cwd: PathBuf::from(temp_home.path()),
|
|
|
|
|
timeout_ms: None,
|
|
|
|
|
env: HashMap::from([(
|
|
|
|
|
"HOME".to_string(),
|
|
|
|
|
temp_home.path().to_str().unwrap().to_string(),
|
|
|
|
|
)]),
|
|
|
|
|
with_escalated_permissions: None,
|
|
|
|
|
justification: None,
|
2025-10-20 20:57:37 +01:00
|
|
|
arg0: None,
|
2025-09-05 09:51:01 -07:00
|
|
|
},
|
|
|
|
|
SandboxType::None,
|
|
|
|
|
&SandboxPolicy::DangerFullAccess,
|
2025-09-18 14:37:06 -07:00
|
|
|
temp_home.path(),
|
2025-09-05 09:51:01 -07:00
|
|
|
&None,
|
|
|
|
|
None,
|
|
|
|
|
)
|
|
|
|
|
.await
|
|
|
|
|
.unwrap();
|
|
|
|
|
|
|
|
|
|
assert_eq!(output.exit_code, 0, "input: {input:?} output: {output:?}");
|
|
|
|
|
if let Some(expected) = expected_output {
|
|
|
|
|
assert_eq!(
|
|
|
|
|
output.stdout.text, expected,
|
|
|
|
|
"input: {input:?} output: {output:?}"
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[cfg(test)]
|
|
|
|
|
#[cfg(target_os = "macos")]
|
|
|
|
|
mod macos_tests {
|
2025-10-28 14:52:52 +00:00
|
|
|
use std::path::PathBuf;
|
2025-09-05 09:51:01 -07:00
|
|
|
|
2025-07-25 11:45:23 -07:00
|
|
|
#[tokio::test]
|
|
|
|
|
async fn test_run_with_profile_escaping_and_execution() {
|
|
|
|
|
let shell_path = "/bin/zsh";
|
|
|
|
|
|
|
|
|
|
let cases = vec![
|
|
|
|
|
(
|
|
|
|
|
vec!["myecho"],
|
2025-09-10 12:40:24 -07:00
|
|
|
vec![shell_path, "-lc", "source ZSHRC_PATH && (myecho)"],
|
2025-07-29 16:49:02 -07:00
|
|
|
Some("It works!\n"),
|
|
|
|
|
),
|
|
|
|
|
(
|
|
|
|
|
vec!["myecho"],
|
2025-09-10 12:40:24 -07:00
|
|
|
vec![shell_path, "-lc", "source ZSHRC_PATH && (myecho)"],
|
2025-07-25 11:45:23 -07:00
|
|
|
Some("It works!\n"),
|
|
|
|
|
),
|
2025-07-29 16:49:02 -07:00
|
|
|
(
|
|
|
|
|
vec!["bash", "-c", "echo 'single' \"double\""],
|
|
|
|
|
vec![
|
|
|
|
|
shell_path,
|
|
|
|
|
"-lc",
|
2025-09-10 12:40:24 -07:00
|
|
|
"source ZSHRC_PATH && (bash -c \"echo 'single' \\\"double\\\"\")",
|
2025-07-29 16:49:02 -07:00
|
|
|
],
|
|
|
|
|
Some("single double\n"),
|
|
|
|
|
),
|
2025-07-25 11:45:23 -07:00
|
|
|
(
|
|
|
|
|
vec!["bash", "-lc", "echo 'single' \"double\""],
|
|
|
|
|
vec![
|
|
|
|
|
shell_path,
|
2025-07-29 16:49:02 -07:00
|
|
|
"-lc",
|
2025-09-10 12:40:24 -07:00
|
|
|
"source ZSHRC_PATH && (echo 'single' \"double\")",
|
2025-07-25 11:45:23 -07:00
|
|
|
],
|
|
|
|
|
Some("single double\n"),
|
|
|
|
|
),
|
|
|
|
|
];
|
|
|
|
|
for (input, expected_cmd, expected_output) in cases {
|
|
|
|
|
use std::collections::HashMap;
|
|
|
|
|
|
|
|
|
|
use crate::exec::ExecParams;
|
|
|
|
|
use crate::exec::SandboxType;
|
|
|
|
|
use crate::exec::process_exec_tool_call;
|
|
|
|
|
use crate::protocol::SandboxPolicy;
|
|
|
|
|
|
|
|
|
|
let temp_home = tempfile::tempdir().unwrap();
|
|
|
|
|
let zshrc_path = temp_home.path().join(".zshrc");
|
|
|
|
|
std::fs::write(
|
|
|
|
|
&zshrc_path,
|
|
|
|
|
r#"
|
2025-10-28 14:52:52 +00:00
|
|
|
set -x
|
|
|
|
|
function myecho {
|
|
|
|
|
echo 'It works!'
|
|
|
|
|
}
|
|
|
|
|
"#,
|
2025-07-25 11:45:23 -07:00
|
|
|
)
|
|
|
|
|
.unwrap();
|
2025-10-28 14:52:52 +00:00
|
|
|
let command = expected_cmd
|
2025-07-25 11:45:23 -07:00
|
|
|
.iter()
|
2025-09-11 11:59:37 -07:00
|
|
|
.map(|s| s.replace("ZSHRC_PATH", zshrc_path.to_str().unwrap()))
|
2025-10-28 14:52:52 +00:00
|
|
|
.collect::<Vec<_>>();
|
2025-07-25 11:45:23 -07:00
|
|
|
|
|
|
|
|
let output = process_exec_tool_call(
|
|
|
|
|
ExecParams {
|
2025-10-28 14:52:52 +00:00
|
|
|
command: command.clone(),
|
2025-07-25 11:45:23 -07:00
|
|
|
cwd: PathBuf::from(temp_home.path()),
|
|
|
|
|
timeout_ms: None,
|
|
|
|
|
env: HashMap::from([(
|
|
|
|
|
"HOME".to_string(),
|
|
|
|
|
temp_home.path().to_str().unwrap().to_string(),
|
|
|
|
|
)]),
|
2025-08-05 20:44:20 -07:00
|
|
|
with_escalated_permissions: None,
|
|
|
|
|
justification: None,
|
2025-10-20 20:57:37 +01:00
|
|
|
arg0: None,
|
2025-07-25 11:45:23 -07:00
|
|
|
},
|
|
|
|
|
SandboxType::None,
|
|
|
|
|
&SandboxPolicy::DangerFullAccess,
|
2025-09-18 14:37:06 -07:00
|
|
|
temp_home.path(),
|
2025-07-25 11:45:23 -07:00
|
|
|
&None,
|
2025-08-01 13:04:34 -07:00
|
|
|
None,
|
2025-07-25 11:45:23 -07:00
|
|
|
)
|
|
|
|
|
.await
|
|
|
|
|
.unwrap();
|
|
|
|
|
|
|
|
|
|
assert_eq!(output.exit_code, 0, "input: {input:?} output: {output:?}");
|
|
|
|
|
if let Some(expected) = expected_output {
|
|
|
|
|
assert_eq!(
|
2025-08-11 11:52:05 -07:00
|
|
|
output.stdout.text, expected,
|
2025-07-25 11:45:23 -07:00
|
|
|
"input: {input:?} output: {output:?}"
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2025-08-20 16:30:34 -07:00
|
|
|
|
|
|
|
|
#[cfg(test)]
|
|
|
|
|
#[cfg(target_os = "windows")]
|
|
|
|
|
mod tests_windows {
|
|
|
|
|
use super::*;
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn test_format_default_shell_invocation_powershell() {
|
2025-10-28 14:52:52 +00:00
|
|
|
use std::path::PathBuf;
|
|
|
|
|
|
2025-08-20 16:30:34 -07:00
|
|
|
let cases = vec![
|
|
|
|
|
(
|
2025-10-28 14:52:52 +00:00
|
|
|
PowerShellConfig {
|
2025-08-20 16:30:34 -07:00
|
|
|
exe: "pwsh.exe".to_string(),
|
|
|
|
|
bash_exe_fallback: None,
|
2025-10-28 14:52:52 +00:00
|
|
|
},
|
2025-08-20 16:30:34 -07:00
|
|
|
vec!["bash", "-lc", "echo hello"],
|
|
|
|
|
vec!["pwsh.exe", "-NoProfile", "-Command", "echo hello"],
|
|
|
|
|
),
|
|
|
|
|
(
|
2025-10-28 14:52:52 +00:00
|
|
|
PowerShellConfig {
|
2025-08-20 16:30:34 -07:00
|
|
|
exe: "powershell.exe".to_string(),
|
|
|
|
|
bash_exe_fallback: None,
|
2025-10-28 14:52:52 +00:00
|
|
|
},
|
2025-08-20 16:30:34 -07:00
|
|
|
vec!["bash", "-lc", "echo hello"],
|
|
|
|
|
vec!["powershell.exe", "-NoProfile", "-Command", "echo hello"],
|
|
|
|
|
),
|
|
|
|
|
(
|
2025-10-28 14:52:52 +00:00
|
|
|
PowerShellConfig {
|
2025-08-20 16:30:34 -07:00
|
|
|
exe: "pwsh.exe".to_string(),
|
|
|
|
|
bash_exe_fallback: Some(PathBuf::from("bash.exe")),
|
2025-10-28 14:52:52 +00:00
|
|
|
},
|
2025-08-20 16:30:34 -07:00
|
|
|
vec!["bash", "-lc", "echo hello"],
|
|
|
|
|
vec!["bash.exe", "-lc", "echo hello"],
|
|
|
|
|
),
|
|
|
|
|
(
|
2025-10-28 14:52:52 +00:00
|
|
|
PowerShellConfig {
|
2025-08-20 16:30:34 -07:00
|
|
|
exe: "pwsh.exe".to_string(),
|
|
|
|
|
bash_exe_fallback: Some(PathBuf::from("bash.exe")),
|
2025-10-28 14:52:52 +00:00
|
|
|
},
|
2025-08-20 16:30:34 -07:00
|
|
|
vec![
|
|
|
|
|
"bash",
|
|
|
|
|
"-lc",
|
|
|
|
|
"apply_patch <<'EOF'\n*** Begin Patch\n*** Update File: destination_file.txt\n-original content\n+modified content\n*** End Patch\nEOF",
|
|
|
|
|
],
|
|
|
|
|
vec![
|
|
|
|
|
"bash.exe",
|
|
|
|
|
"-lc",
|
|
|
|
|
"apply_patch <<'EOF'\n*** Begin Patch\n*** Update File: destination_file.txt\n-original content\n+modified content\n*** End Patch\nEOF",
|
|
|
|
|
],
|
|
|
|
|
),
|
|
|
|
|
(
|
2025-10-28 14:52:52 +00:00
|
|
|
PowerShellConfig {
|
2025-08-20 16:30:34 -07:00
|
|
|
exe: "pwsh.exe".to_string(),
|
|
|
|
|
bash_exe_fallback: Some(PathBuf::from("bash.exe")),
|
2025-10-28 14:52:52 +00:00
|
|
|
},
|
2025-08-20 16:30:34 -07:00
|
|
|
vec!["echo", "hello"],
|
|
|
|
|
vec!["pwsh.exe", "-NoProfile", "-Command", "echo hello"],
|
|
|
|
|
),
|
|
|
|
|
(
|
2025-10-28 14:52:52 +00:00
|
|
|
PowerShellConfig {
|
2025-08-20 16:30:34 -07:00
|
|
|
exe: "pwsh.exe".to_string(),
|
|
|
|
|
bash_exe_fallback: Some(PathBuf::from("bash.exe")),
|
2025-10-28 14:52:52 +00:00
|
|
|
},
|
2025-08-20 16:30:34 -07:00
|
|
|
vec!["pwsh.exe", "-NoProfile", "-Command", "echo hello"],
|
|
|
|
|
vec!["pwsh.exe", "-NoProfile", "-Command", "echo hello"],
|
|
|
|
|
),
|
|
|
|
|
(
|
2025-10-28 14:52:52 +00:00
|
|
|
PowerShellConfig {
|
2025-08-20 16:30:34 -07:00
|
|
|
exe: "powershell.exe".to_string(),
|
|
|
|
|
bash_exe_fallback: Some(PathBuf::from("bash.exe")),
|
2025-10-28 14:52:52 +00:00
|
|
|
},
|
2025-08-20 16:30:34 -07:00
|
|
|
vec![
|
|
|
|
|
"codex-mcp-server.exe",
|
|
|
|
|
"--codex-run-as-apply-patch",
|
|
|
|
|
"*** Begin Patch\n*** Update File: C:\\Users\\person\\destination_file.txt\n-original content\n+modified content\n*** End Patch",
|
|
|
|
|
],
|
|
|
|
|
vec![
|
|
|
|
|
"codex-mcp-server.exe",
|
|
|
|
|
"--codex-run-as-apply-patch",
|
|
|
|
|
"*** Begin Patch\n*** Update File: C:\\Users\\person\\destination_file.txt\n-original content\n+modified content\n*** End Patch",
|
|
|
|
|
],
|
|
|
|
|
),
|
|
|
|
|
];
|
|
|
|
|
|
2025-10-28 14:52:52 +00:00
|
|
|
for (config, input, expected_cmd) in cases {
|
|
|
|
|
let command = expected_cmd
|
|
|
|
|
.iter()
|
|
|
|
|
.map(|s| (*s).to_string())
|
|
|
|
|
.collect::<Vec<_>>();
|
|
|
|
|
|
|
|
|
|
// These tests assert the final command for each scenario now that the helper
|
|
|
|
|
// has been removed. The inputs remain to document the original coverage.
|
|
|
|
|
let expected = expected_cmd
|
|
|
|
|
.iter()
|
|
|
|
|
.map(|s| (*s).to_string())
|
|
|
|
|
.collect::<Vec<_>>();
|
|
|
|
|
assert_eq!(command, expected, "input: {input:?} config: {config:?}");
|
2025-08-20 16:30:34 -07:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|