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:
@@ -299,7 +299,7 @@ pub async fn handle(
|
||||
Ok(FunctionToolOutput::from_text(content, Some(true)))
|
||||
}
|
||||
|
||||
fn single_local_environment_cwd(turn: &TurnContext) -> Result<&AbsolutePathBuf, FunctionCallError> {
|
||||
fn single_local_environment_cwd(turn: &TurnContext) -> Result<AbsolutePathBuf, FunctionCallError> {
|
||||
let [turn_environment] = turn.environments.turn_environments.as_slice() else {
|
||||
return Err(FunctionCallError::RespondToModel(
|
||||
"spawn_agents_on_csv requires exactly one local environment".to_string(),
|
||||
@@ -312,5 +312,12 @@ fn single_local_environment_cwd(turn: &TurnContext) -> Result<&AbsolutePathBuf,
|
||||
));
|
||||
}
|
||||
|
||||
Ok(turn_environment.cwd())
|
||||
// TODO(anp): Migrate spawn_agents_on_csv filesystem access to PathUri before enabling it for
|
||||
// remote environments.
|
||||
turn_environment.cwd().to_abs_path().map_err(|err| {
|
||||
FunctionCallError::RespondToModel(format!(
|
||||
"spawn_agents_on_csv cwd `{}` is not native to the Codex host: {err}",
|
||||
turn_environment.cwd()
|
||||
))
|
||||
})
|
||||
}
|
||||
|
||||
@@ -359,11 +359,18 @@ impl ApplyPatchHandler {
|
||||
"apply_patch is unavailable in this session".to_string(),
|
||||
));
|
||||
};
|
||||
let cwd = turn_environment.cwd().clone();
|
||||
// TODO(anp): Migrate apply-patch verification and permission accounting to PathUri so
|
||||
// patches can target environment-native foreign paths without host projection.
|
||||
let cwd = turn_environment.cwd().to_abs_path().map_err(|err| {
|
||||
FunctionCallError::RespondToModel(format!(
|
||||
"apply_patch cwd `{}` is not native to the Codex host: {err}",
|
||||
turn_environment.cwd()
|
||||
))
|
||||
})?;
|
||||
let fs = turn_environment.environment.get_filesystem();
|
||||
let sandbox = turn.file_system_sandbox_context(
|
||||
/*additional_permissions*/ None,
|
||||
turn_environment.cwd_uri(),
|
||||
turn_environment.cwd(),
|
||||
);
|
||||
match codex_apply_patch::verify_apply_patch_args(args, &cwd, fs.as_ref(), Some(&sandbox))
|
||||
.await
|
||||
|
||||
@@ -114,10 +114,15 @@ async fn to_extension_call(invocation: &ToolInvocation) -> ExtensionToolCall {
|
||||
ConversationHistory::new(invocation.session.clone_history().await.into_raw_items());
|
||||
let mut environments = Vec::with_capacity(invocation.turn.environments.turn_environments.len());
|
||||
for environment in &invocation.turn.environments.turn_environments {
|
||||
// TODO(anp): Migrate extension ToolEnvironment and granted-permission lookup to PathUri
|
||||
// so extensions can receive foreign environment cwd values.
|
||||
let Ok(native_cwd) = environment.cwd().to_abs_path() else {
|
||||
continue;
|
||||
};
|
||||
let additional_permissions = apply_granted_turn_permissions(
|
||||
invocation.session.as_ref(),
|
||||
&environment.environment_id,
|
||||
environment.cwd().as_path(),
|
||||
native_cwd.as_path(),
|
||||
SandboxPermissions::UseDefault,
|
||||
/*additional_permissions*/ None,
|
||||
)
|
||||
@@ -125,10 +130,10 @@ async fn to_extension_call(invocation: &ToolInvocation) -> ExtensionToolCall {
|
||||
.additional_permissions;
|
||||
let file_system_sandbox_context = invocation
|
||||
.turn
|
||||
.file_system_sandbox_context(additional_permissions, environment.cwd_uri());
|
||||
.file_system_sandbox_context(additional_permissions, environment.cwd());
|
||||
environments.push(ToolEnvironment {
|
||||
environment_id: environment.environment_id.clone(),
|
||||
cwd: environment.cwd().clone(),
|
||||
cwd: native_cwd,
|
||||
file_system: environment.environment.get_filesystem(),
|
||||
file_system_sandbox_context,
|
||||
});
|
||||
@@ -315,7 +320,7 @@ mod tests {
|
||||
.environments
|
||||
.turn_environments
|
||||
.iter()
|
||||
.map(|environment| Some(environment.cwd_uri().clone()))
|
||||
.map(|environment| Some(environment.cwd().clone()))
|
||||
.collect::<Vec<_>>();
|
||||
let history_item = ResponseItem::Message {
|
||||
id: None,
|
||||
|
||||
@@ -70,8 +70,16 @@ impl RequestPermissionsHandler {
|
||||
"request_permissions requires a primary environment".to_string(),
|
||||
));
|
||||
};
|
||||
// TODO(anp): Migrate request_permissions parsing and permission profiles to PathUri so
|
||||
// environment-native foreign paths do not require host conversion.
|
||||
let native_cwd = turn_environment.cwd().to_abs_path().map_err(|err| {
|
||||
FunctionCallError::RespondToModel(format!(
|
||||
"request_permissions cwd `{}` is not native to the Codex host: {err}",
|
||||
turn_environment.cwd()
|
||||
))
|
||||
})?;
|
||||
let mut args: RequestPermissionsArgs =
|
||||
parse_arguments_with_base_path(&arguments, turn_environment.cwd())?;
|
||||
parse_arguments_with_base_path(&arguments, &native_cwd)?;
|
||||
args.permissions = normalize_additional_permissions(args.permissions.into())
|
||||
.map(codex_protocol::request_permissions::RequestPermissionProfile::from)
|
||||
.map_err(FunctionCallError::RespondToModel)?;
|
||||
|
||||
@@ -129,13 +129,21 @@ impl ExecCommandHandler {
|
||||
"unified exec is unavailable in this session".to_string(),
|
||||
));
|
||||
};
|
||||
// TODO(anp): Resolve tool paths using the selected environment's native path convention
|
||||
// so unified exec can support relative paths in foreign environments.
|
||||
let native_environment_cwd = turn_environment.cwd().to_abs_path().map_err(|err| {
|
||||
FunctionCallError::RespondToModel(format!(
|
||||
"environment cwd `{}` is not native to the Codex host: {err}",
|
||||
turn_environment.cwd()
|
||||
))
|
||||
})?;
|
||||
let cwd = environment_args
|
||||
.workdir
|
||||
.as_deref()
|
||||
.filter(|workdir| !workdir.is_empty())
|
||||
.map_or_else(
|
||||
|| turn_environment.cwd().clone(),
|
||||
|workdir| turn_environment.cwd().join(workdir),
|
||||
|| native_environment_cwd.clone(),
|
||||
|workdir| native_environment_cwd.join(workdir),
|
||||
);
|
||||
let environment = Arc::clone(&turn_environment.environment);
|
||||
let fs = environment.get_filesystem();
|
||||
@@ -277,7 +285,7 @@ impl ExecCommandHandler {
|
||||
yield_time_ms,
|
||||
max_output_tokens,
|
||||
cwd,
|
||||
sandbox_cwd: turn_environment.cwd().clone(),
|
||||
sandbox_cwd: native_environment_cwd,
|
||||
turn_environment: turn_environment.clone(),
|
||||
shell_mode,
|
||||
network: context.turn.network.clone(),
|
||||
|
||||
@@ -143,11 +143,18 @@ impl ViewImageHandler {
|
||||
"view_image is unavailable in this session".to_string(),
|
||||
));
|
||||
};
|
||||
let cwd = turn_environment.cwd().clone();
|
||||
// TODO(anp): Resolve tool paths using the selected environment's native path convention
|
||||
// so view_image can support relative paths in foreign environments.
|
||||
let cwd = turn_environment.cwd().to_abs_path().map_err(|err| {
|
||||
FunctionCallError::RespondToModel(format!(
|
||||
"environment cwd `{}` is not native to the Codex host: {err}",
|
||||
turn_environment.cwd()
|
||||
))
|
||||
})?;
|
||||
let abs_path = cwd.join(path);
|
||||
let sandbox = turn.file_system_sandbox_context(
|
||||
/*additional_permissions*/ None,
|
||||
turn_environment.cwd_uri(),
|
||||
turn_environment.cwd(),
|
||||
);
|
||||
let fs = turn_environment.environment.get_filesystem();
|
||||
let path_uri = PathUri::from_abs_path(&abs_path);
|
||||
@@ -272,6 +279,7 @@ mod tests {
|
||||
use crate::turn_diff_tracker::TurnDiffTracker;
|
||||
use codex_protocol::models::PermissionProfile;
|
||||
use codex_utils_absolute_path::AbsolutePathBuf;
|
||||
use codex_utils_path_uri::PathUri;
|
||||
use core_test_support::TempDirExt;
|
||||
use pretty_assertions::assert_eq;
|
||||
use serde_json::json;
|
||||
@@ -288,7 +296,7 @@ mod tests {
|
||||
turn.environments.turn_environments[0] = TurnEnvironment::new(
|
||||
current.environment_id,
|
||||
current.environment,
|
||||
cwd,
|
||||
PathUri::from_abs_path(&cwd),
|
||||
current.shell,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -19,7 +19,7 @@ fn test_turn_environment(environment_id: &str) -> crate::session::turn_context::
|
||||
crate::session::turn_context::TurnEnvironment::new(
|
||||
environment_id.to_string(),
|
||||
std::sync::Arc::new(codex_exec_server::Environment::default_for_tests()),
|
||||
std::env::temp_dir().abs(),
|
||||
PathUri::from_abs_path(&std::env::temp_dir().abs()),
|
||||
/*shell*/ None,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -427,6 +427,7 @@ mod tests {
|
||||
use codex_exec_server::Environment;
|
||||
use codex_exec_server::LOCAL_ENVIRONMENT_ID;
|
||||
use codex_tools::ZshForkConfig;
|
||||
use codex_utils_path_uri::PathUri;
|
||||
use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
use tempfile::tempdir;
|
||||
@@ -435,7 +436,7 @@ mod tests {
|
||||
TurnEnvironment::new(
|
||||
LOCAL_ENVIRONMENT_ID.to_string(),
|
||||
Arc::new(Environment::default_for_tests()),
|
||||
cwd,
|
||||
PathUri::from_abs_path(&cwd),
|
||||
/*shell*/ None,
|
||||
)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user