mirror of
https://github.com/pchuan98/codex.git
synced 2026-07-01 00:31:56 +08:00
6a6a5f925e
## Why
Shell detection needs to be available through the `Environment`
abstraction so callers can ask the selected local or remote environment
for shell metadata without adding a separate HTTP endpoint or parallel
info-source path. This keeps shell metadata shaped like the existing
environment-owned filesystem capability and lets remote environments
answer through exec-server JSON-RPC.
## What changed
- Added `environment/info` to the exec-server protocol/client/server and
exposed `Environment::info()`.
- Added local and remote environment info providers on `Environment`,
following the existing capability-provider pattern used for filesystem
access.
- Moved the shared shell detection logic into `codex-shell-command` and
kept core shell APIs as wrappers around that implementation.
- Returned shell metadata as `EnvironmentInfo { shell: ShellInfo }`
using the existing shell detection path.
- Added a remote environment test that calls `Environment::info()`
through an exec-server-backed environment.
## Validation
- `git diff --check`
- `just test -p codex-shell-command`
- `just test -p codex-core -E 'test(/shell::tests::/)'`\n- `just test -p
codex-exec-server environment`
114 lines
3.5 KiB
Rust
114 lines
3.5 KiB
Rust
use crate::shell_snapshot::ShellSnapshot;
|
|
use codex_shell_command::shell_detect::DetectedShell;
|
|
use serde::Deserialize;
|
|
use serde::Serialize;
|
|
use std::path::PathBuf;
|
|
use std::sync::Arc;
|
|
use tokio::sync::watch;
|
|
|
|
pub use codex_shell_command::shell_detect::ShellType;
|
|
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
pub struct Shell {
|
|
pub(crate) shell_type: ShellType,
|
|
pub(crate) shell_path: PathBuf,
|
|
#[serde(
|
|
skip_serializing,
|
|
skip_deserializing,
|
|
default = "empty_shell_snapshot_receiver"
|
|
)]
|
|
pub(crate) shell_snapshot: watch::Receiver<Option<Arc<ShellSnapshot>>>,
|
|
}
|
|
|
|
impl Shell {
|
|
pub fn name(&self) -> &'static str {
|
|
self.shell_type.name()
|
|
}
|
|
|
|
/// Takes a string of shell and returns the full list of command args to
|
|
/// use with `exec()` to run the shell command.
|
|
pub fn derive_exec_args(&self, command: &str, use_login_shell: bool) -> Vec<String> {
|
|
match self.shell_type {
|
|
ShellType::Zsh | ShellType::Bash | ShellType::Sh => {
|
|
let arg = if use_login_shell { "-lc" } else { "-c" };
|
|
vec![
|
|
self.shell_path.to_string_lossy().to_string(),
|
|
arg.to_string(),
|
|
command.to_string(),
|
|
]
|
|
}
|
|
ShellType::PowerShell => {
|
|
let mut args = vec![self.shell_path.to_string_lossy().to_string()];
|
|
if !use_login_shell {
|
|
args.push("-NoProfile".to_string());
|
|
}
|
|
|
|
args.push("-Command".to_string());
|
|
args.push(command.to_string());
|
|
args
|
|
}
|
|
ShellType::Cmd => {
|
|
let mut args = vec![self.shell_path.to_string_lossy().to_string()];
|
|
args.push("/c".to_string());
|
|
args.push(command.to_string());
|
|
args
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Return the shell snapshot if existing.
|
|
pub fn shell_snapshot(&self) -> Option<Arc<ShellSnapshot>> {
|
|
self.shell_snapshot.borrow().clone()
|
|
}
|
|
}
|
|
|
|
pub(crate) fn empty_shell_snapshot_receiver() -> watch::Receiver<Option<Arc<ShellSnapshot>>> {
|
|
let (_tx, rx) = watch::channel(None);
|
|
rx
|
|
}
|
|
|
|
impl PartialEq for Shell {
|
|
fn eq(&self, other: &Self) -> bool {
|
|
self.shell_type == other.shell_type && self.shell_path == other.shell_path
|
|
}
|
|
}
|
|
|
|
impl Eq for Shell {}
|
|
|
|
impl From<DetectedShell> for Shell {
|
|
fn from(detected: DetectedShell) -> Self {
|
|
Self {
|
|
shell_type: detected.shell_type,
|
|
shell_path: detected.shell_path,
|
|
shell_snapshot: empty_shell_snapshot_receiver(),
|
|
}
|
|
}
|
|
}
|
|
|
|
#[cfg(all(test, unix))]
|
|
fn ultimate_fallback_shell() -> Shell {
|
|
codex_shell_command::shell_detect::ultimate_fallback_shell().into()
|
|
}
|
|
|
|
pub fn get_shell_by_model_provided_path(shell_path: &PathBuf) -> Shell {
|
|
codex_shell_command::shell_detect::get_shell_by_model_provided_path(shell_path).into()
|
|
}
|
|
|
|
pub fn get_shell(shell_type: ShellType, path: Option<&PathBuf>) -> Option<Shell> {
|
|
codex_shell_command::shell_detect::get_shell(shell_type, path).map(Into::into)
|
|
}
|
|
|
|
pub fn default_user_shell() -> Shell {
|
|
codex_shell_command::shell_detect::default_user_shell().into()
|
|
}
|
|
|
|
#[cfg(all(test, target_os = "macos"))]
|
|
fn default_user_shell_from_path(user_shell_path: Option<PathBuf>) -> Shell {
|
|
codex_shell_command::shell_detect::default_user_shell_from_path(user_shell_path).into()
|
|
}
|
|
|
|
#[cfg(test)]
|
|
#[cfg(unix)]
|
|
#[path = "shell_tests.rs"]
|
|
mod tests;
|