mirror of
https://github.com/pchuan98/codex.git
synced 2026-07-01 00:31:56 +08:00
app-server: preserve target-native environment cwd (#28146)
## Why app-server may run on a different OS from the selected exec-server environment. Parsing that environment’s cwd with the Codex host’s path rules prevents thread startup. ## What Carry environment cwd values as `LegacyAppPathString` at the app-server boundary and `PathUri` internally. Existing tool-call schemas and relative-path behavior stay host-native; remaining local-only consumers convert explicitly and leave follow-up TODOs. The Wine integration test verifies app-server can start a thread and complete an ordinary turn with a Windows environment cwd from Linux. ## Validation - `bazel test //codex-rs/core/tests/remote_env_windows:smoke-test --test_output=errors` - focused app-server environment-selection and protocol schema tests - scoped Clippy for `codex-core` and `codex-app-server-protocol`
This commit is contained in:
@@ -318,17 +318,18 @@ impl Session {
|
||||
)
|
||||
.await;
|
||||
let environment_manager = self.services.turn_environments.environment_manager();
|
||||
let mcp_runtime_context = match turn_context.environments.primary() {
|
||||
Some(turn_environment) => McpRuntimeContext::new(
|
||||
Arc::clone(&environment_manager),
|
||||
turn_environment.cwd().to_path_buf(),
|
||||
),
|
||||
None => McpRuntimeContext::new(
|
||||
environment_manager,
|
||||
// TODO(anp): Migrate MCP runtime cwd plumbing to PathUri so foreign environment cwd
|
||||
// values can be used without falling back to the legacy host cwd.
|
||||
let cwd = turn_context
|
||||
.environments
|
||||
.primary()
|
||||
.and_then(|turn_environment| turn_environment.cwd().to_abs_path().ok())
|
||||
.map(|cwd| cwd.to_path_buf())
|
||||
.unwrap_or_else(|| {
|
||||
#[allow(deprecated)]
|
||||
turn_context.cwd.to_path_buf(),
|
||||
),
|
||||
};
|
||||
turn_context.cwd.to_path_buf()
|
||||
});
|
||||
let mcp_runtime_context = McpRuntimeContext::new(environment_manager, cwd);
|
||||
let mcp_startup_cancellation_token = {
|
||||
let mut guard = self.services.mcp_startup_cancellation_token.lock().await;
|
||||
guard.cancel();
|
||||
|
||||
@@ -1119,9 +1119,12 @@ impl Session {
|
||||
};
|
||||
let mcp_runtime_context = {
|
||||
let turn_environments = sess.services.turn_environments.snapshot().await;
|
||||
// TODO(anp): Migrate MCP runtime cwd plumbing to PathUri so foreign environment
|
||||
// cwd values can be used without falling back to the session host cwd.
|
||||
let cwd = turn_environments
|
||||
.primary()
|
||||
.map(|turn_environment| turn_environment.cwd().to_path_buf())
|
||||
.and_then(|turn_environment| turn_environment.cwd().to_abs_path().ok())
|
||||
.map(|cwd| cwd.to_path_buf())
|
||||
.unwrap_or_else(|| session_configuration.cwd().to_path_buf());
|
||||
McpRuntimeContext::new(
|
||||
sess.services.turn_environments.environment_manager(),
|
||||
|
||||
@@ -5697,7 +5697,7 @@ async fn request_permissions_tool_resolves_relative_paths_against_selected_envir
|
||||
turn_context_mut.environments.turn_environments[0] = TurnEnvironment::new(
|
||||
"remote".to_string(),
|
||||
current_environment.environment,
|
||||
environment_cwd.clone(),
|
||||
PathUri::from_abs_path(&environment_cwd),
|
||||
current_environment.shell,
|
||||
);
|
||||
|
||||
@@ -6316,13 +6316,14 @@ async fn primary_environment_uses_first_turn_environment() {
|
||||
let first_environment = turn_context.environments.turn_environments[0].clone();
|
||||
#[allow(deprecated)]
|
||||
let second_cwd = turn_context.cwd.join("second");
|
||||
let second_cwd_uri = codex_utils_path_uri::PathUri::from_abs_path(&second_cwd);
|
||||
turn_context
|
||||
.environments
|
||||
.turn_environments
|
||||
.push(TurnEnvironment::new(
|
||||
"second".to_string(),
|
||||
Arc::clone(&first_environment.environment),
|
||||
second_cwd.clone(),
|
||||
second_cwd_uri.clone(),
|
||||
/*shell*/ None,
|
||||
));
|
||||
|
||||
@@ -6342,12 +6343,12 @@ async fn primary_environment_uses_first_turn_environment() {
|
||||
.find(|environment| environment.environment_id == "second")
|
||||
.expect("second environment")
|
||||
.cwd(),
|
||||
&second_cwd
|
||||
&second_cwd_uri
|
||||
);
|
||||
assert_eq!(turn_context.environments.turn_environments.len(), 2);
|
||||
assert_eq!(
|
||||
turn_context.environments.turn_environments[1].cwd(),
|
||||
&second_cwd
|
||||
&second_cwd_uri
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -414,13 +414,16 @@ pub(crate) async fn run_turn(
|
||||
async fn turn_diff_display_roots(turn_context: &TurnContext) -> Vec<(String, PathBuf)> {
|
||||
let mut display_roots = Vec::new();
|
||||
for turn_environment in &turn_context.environments.turn_environments {
|
||||
let root = get_git_repo_root_with_fs(
|
||||
turn_environment.environment.get_filesystem().as_ref(),
|
||||
turn_environment.cwd(),
|
||||
)
|
||||
.await
|
||||
.unwrap_or_else(|| turn_environment.cwd().clone())
|
||||
.into_path_buf();
|
||||
// TODO(anp): Migrate git-root discovery and diff display roots to PathUri so foreign
|
||||
// environment roots can participate without host-native conversion.
|
||||
let Ok(cwd) = turn_environment.cwd().to_abs_path() else {
|
||||
continue;
|
||||
};
|
||||
let root =
|
||||
get_git_repo_root_with_fs(turn_environment.environment.get_filesystem().as_ref(), &cwd)
|
||||
.await
|
||||
.unwrap_or(cwd)
|
||||
.into_path_buf();
|
||||
display_roots.push((turn_environment.environment_id.clone(), root));
|
||||
}
|
||||
display_roots
|
||||
@@ -634,10 +637,14 @@ async fn build_extension_turn_input_items(
|
||||
.turn_environments
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(index, environment)| TurnInputEnvironment {
|
||||
environment_id: environment.environment_id.clone(),
|
||||
cwd: environment.cwd().as_path().to_path_buf(),
|
||||
is_primary: index == 0,
|
||||
.filter_map(|(index, environment)| {
|
||||
// TODO(anp): Migrate extension turn-input environments to PathUri so foreign cwd
|
||||
// values are not omitted from extension context.
|
||||
Some(TurnInputEnvironment {
|
||||
environment_id: environment.environment_id.clone(),
|
||||
cwd: environment.cwd().to_abs_path().ok()?.into_path_buf(),
|
||||
is_primary: index == 0,
|
||||
})
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
|
||||
@@ -3,7 +3,6 @@ use crate::SkillLoadOutcome;
|
||||
use crate::agents_md::LoadedAgentsMd;
|
||||
use crate::config::GhostSnapshotConfig;
|
||||
use crate::environment_selection::TurnEnvironmentSnapshot;
|
||||
use crate::path_utils;
|
||||
use crate::shell_snapshot::ShellSnapshotFile;
|
||||
use codex_core_skills::HostLoadedSkills;
|
||||
use codex_file_system::FileSystemSandboxContext;
|
||||
@@ -49,13 +48,7 @@ pub(crate) type ShellSnapshotTask = Shared<BoxFuture<'static, Option<Arc<ShellSn
|
||||
pub(crate) struct TurnEnvironment {
|
||||
pub(crate) environment_id: String,
|
||||
pub(crate) environment: Arc<Environment>,
|
||||
// Keep both representations together while cwd consumers migrate to URI semantics. Keeping
|
||||
// them synchronized means neither representation can be exposed through a mutable reference;
|
||||
// updates must rebuild the validated pair through `TurnEnvironment::new`. Once
|
||||
// `TurnEnvironment::cwd` itself becomes a `PathUri`, convert only at native filesystem and
|
||||
// process-launch boundaries and remove this paired migration state.
|
||||
cwd: AbsolutePathBuf,
|
||||
cwd_uri: PathUri,
|
||||
cwd: PathUri,
|
||||
pub(crate) shell: Option<shell::Shell>,
|
||||
pub(crate) shell_snapshot: ShellSnapshotTask,
|
||||
}
|
||||
@@ -64,22 +57,20 @@ impl TurnEnvironment {
|
||||
pub(crate) fn new(
|
||||
environment_id: String,
|
||||
environment: Arc<Environment>,
|
||||
cwd: AbsolutePathBuf,
|
||||
cwd: PathUri,
|
||||
shell: Option<shell::Shell>,
|
||||
) -> Self {
|
||||
let cwd_uri = PathUri::from_abs_path(&cwd);
|
||||
Self {
|
||||
environment_id,
|
||||
environment,
|
||||
cwd,
|
||||
cwd_uri,
|
||||
shell,
|
||||
shell_snapshot: futures::future::ready(None).boxed().shared(),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn shell_snapshot(&self, cwd: &AbsolutePathBuf) -> Option<AbsolutePathBuf> {
|
||||
if !path_utils::paths_match_after_normalization(self.cwd.as_path(), cwd.as_path()) {
|
||||
if self.cwd != PathUri::from_abs_path(cwd) {
|
||||
return None;
|
||||
}
|
||||
self.shell_snapshot
|
||||
@@ -88,18 +79,14 @@ impl TurnEnvironment {
|
||||
.map(ShellSnapshotFile::path)
|
||||
}
|
||||
|
||||
pub(crate) fn cwd(&self) -> &AbsolutePathBuf {
|
||||
pub(crate) fn cwd(&self) -> &PathUri {
|
||||
&self.cwd
|
||||
}
|
||||
|
||||
pub(crate) fn cwd_uri(&self) -> &PathUri {
|
||||
&self.cwd_uri
|
||||
}
|
||||
|
||||
pub(crate) fn selection(&self) -> TurnEnvironmentSelection {
|
||||
TurnEnvironmentSelection {
|
||||
environment_id: self.environment_id.clone(),
|
||||
cwd: self.cwd_uri.clone(),
|
||||
cwd: self.cwd.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -110,7 +97,6 @@ impl std::fmt::Debug for TurnEnvironment {
|
||||
.field("environment_id", &self.environment_id)
|
||||
.field("environment", &self.environment)
|
||||
.field("cwd", &self.cwd)
|
||||
.field("cwd_uri", &self.cwd_uri)
|
||||
.field("shell", &self.shell)
|
||||
.finish_non_exhaustive()
|
||||
}
|
||||
@@ -755,9 +741,11 @@ impl Session {
|
||||
) -> Arc<TurnContext> {
|
||||
let turn_environments = self.services.turn_environments.snapshot().await;
|
||||
let primary_turn_environment = turn_environments.primary().cloned();
|
||||
// TODO(anp): Migrate per-turn config and legacy TurnContext cwd consumers to PathUri so
|
||||
// a foreign primary environment does not fall back to the session's host cwd.
|
||||
let cwd = primary_turn_environment
|
||||
.as_ref()
|
||||
.map(|turn_environment| turn_environment.cwd().clone())
|
||||
.and_then(|turn_environment| turn_environment.cwd().to_abs_path().ok())
|
||||
.unwrap_or_else(|| session_configuration.cwd().clone());
|
||||
let per_turn_config = Self::build_per_turn_config(&session_configuration, cwd.clone());
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user