mirror of
https://github.com/pchuan98/codex.git
synced 2026-07-01 00:31:56 +08:00
Use AbsolutePathBuf for cwd state (#15710)
Migrate `cwd` and related session/config state to `AbsolutePathBuf` so downstream consumers consistently see absolute working directories. Add test-only `.abs()` helpers for `Path`, `PathBuf`, and `TempDir`, and update branch-local tests to use them instead of `AbsolutePathBuf::try_from(...)`. For the remaining TUI/app-server snapshot coverage that renders absolute cwd values, keep the snapshots unchanged and skip the Windows-only cases where the platform-specific absolute path layout differs.
This commit is contained in:
committed by
GitHub
Unverified
parent
178c3b15b4
commit
504aeb0e09
@@ -1645,7 +1645,7 @@ impl CodexMessageProcessor {
|
||||
return;
|
||||
}
|
||||
|
||||
let cwd = cwd.unwrap_or_else(|| self.config.cwd.clone());
|
||||
let cwd = cwd.unwrap_or_else(|| self.config.cwd.to_path_buf());
|
||||
let mut env = create_env(
|
||||
&self.config.permissions.shell_environment_policy,
|
||||
/*thread_id*/ None,
|
||||
@@ -5464,7 +5464,7 @@ impl CodexMessageProcessor {
|
||||
per_cwd_extra_user_roots,
|
||||
} = params;
|
||||
let cwds = if cwds.is_empty() {
|
||||
vec![self.config.cwd.clone()]
|
||||
vec![self.config.cwd.to_path_buf()]
|
||||
} else {
|
||||
cwds
|
||||
};
|
||||
@@ -7258,7 +7258,7 @@ impl CodexMessageProcessor {
|
||||
let command_cwd = params
|
||||
.cwd
|
||||
.map(PathBuf::from)
|
||||
.unwrap_or_else(|| config.cwd.clone());
|
||||
.unwrap_or_else(|| config.cwd.to_path_buf());
|
||||
let cli_overrides = self.current_cli_overrides();
|
||||
let runtime_feature_enablement = self.current_runtime_feature_enablement();
|
||||
let outgoing = Arc::clone(&self.outgoing);
|
||||
@@ -7283,7 +7283,7 @@ impl CodexMessageProcessor {
|
||||
let setup_request = WindowsSandboxSetupRequest {
|
||||
mode,
|
||||
policy: config.permissions.sandbox_policy.get().clone(),
|
||||
policy_cwd: config.cwd.clone(),
|
||||
policy_cwd: config.cwd.to_path_buf(),
|
||||
command_cwd,
|
||||
env_map: std::env::vars().collect(),
|
||||
codex_home: config.codex_home.clone(),
|
||||
|
||||
@@ -260,7 +260,7 @@ async fn run_command_under_sandbox(
|
||||
PathBuf::from("/usr/bin/sandbox-exec"),
|
||||
args,
|
||||
/*arg0*/ None,
|
||||
cwd,
|
||||
cwd.to_path_buf(),
|
||||
network_policy,
|
||||
env,
|
||||
|env_map| {
|
||||
@@ -293,7 +293,7 @@ async fn run_command_under_sandbox(
|
||||
codex_linux_sandbox_exe,
|
||||
args,
|
||||
Some("codex-linux-sandbox"),
|
||||
cwd,
|
||||
cwd.to_path_buf(),
|
||||
network_policy,
|
||||
env,
|
||||
|env_map| {
|
||||
|
||||
@@ -252,7 +252,7 @@ mod reload {
|
||||
|
||||
fn reload_overrides(config: &Config, preserve_current_provider: bool) -> ConfigOverrides {
|
||||
ConfigOverrides {
|
||||
cwd: Some(config.cwd.clone()),
|
||||
cwd: Some(config.cwd.to_path_buf()),
|
||||
model_provider: preserve_current_provider.then(|| config.model_provider_id.clone()),
|
||||
codex_linux_sandbox_exe: config.codex_linux_sandbox_exe.clone(),
|
||||
main_execve_wrapper_exe: config.main_execve_wrapper_exe.clone(),
|
||||
|
||||
+40
-37
@@ -33,6 +33,7 @@ use crate::models_manager::manager::ModelsManager;
|
||||
use crate::models_manager::manager::RefreshStrategy;
|
||||
use crate::parse_command::parse_command;
|
||||
use crate::parse_turn_item;
|
||||
use crate::path_utils::normalize_for_native_workdir;
|
||||
use crate::realtime_conversation::RealtimeConversationManager;
|
||||
use crate::realtime_conversation::handle_audio as handle_realtime_conversation_audio;
|
||||
use crate::realtime_conversation::handle_close as handle_realtime_conversation_close;
|
||||
@@ -835,10 +836,10 @@ pub(crate) struct TurnContext {
|
||||
pub(crate) reasoning_summary: ReasoningSummaryConfig,
|
||||
pub(crate) session_source: SessionSource,
|
||||
pub(crate) environment: Arc<Environment>,
|
||||
/// The session's current working directory. All relative paths provided by
|
||||
/// the model as well as sandbox policies are resolved against this path
|
||||
/// The session's absolute working directory. All relative paths provided
|
||||
/// by the model as well as sandbox policies are resolved against this path
|
||||
/// instead of `std::env::current_dir()`.
|
||||
pub(crate) cwd: PathBuf,
|
||||
pub(crate) cwd: AbsolutePathBuf,
|
||||
pub(crate) current_date: Option<String>,
|
||||
pub(crate) timezone: Option<String>,
|
||||
pub(crate) app_server_client_name: Option<String>,
|
||||
@@ -979,7 +980,7 @@ impl TurnContext {
|
||||
pub(crate) fn resolve_path(&self, path: Option<String>) -> PathBuf {
|
||||
path.as_ref()
|
||||
.map(PathBuf::from)
|
||||
.map_or_else(|| self.cwd.clone(), |p| self.cwd.join(p))
|
||||
.map_or_else(|| self.cwd.to_path_buf(), |p| self.cwd.as_path().join(p))
|
||||
}
|
||||
|
||||
pub(crate) fn compact_prompt(&self) -> &str {
|
||||
@@ -992,7 +993,7 @@ impl TurnContext {
|
||||
TurnContextItem {
|
||||
turn_id: Some(self.sub_id.clone()),
|
||||
trace_id: self.trace_id.clone(),
|
||||
cwd: self.cwd.clone(),
|
||||
cwd: self.cwd.to_path_buf(),
|
||||
current_date: self.current_date.clone(),
|
||||
timezone: self.timezone.clone(),
|
||||
approval_policy: self.approval_policy.value(),
|
||||
@@ -1068,14 +1069,11 @@ pub(crate) struct SessionConfiguration {
|
||||
network_sandbox_policy: NetworkSandboxPolicy,
|
||||
windows_sandbox_level: WindowsSandboxLevel,
|
||||
|
||||
/// Working directory that should be treated as the *root* of the
|
||||
/// Absolute working directory that should be treated as the *root* of the
|
||||
/// session. All relative paths supplied by the model as well as the
|
||||
/// execution sandbox are resolved against this directory **instead**
|
||||
/// of the process-wide current working directory. CLI front-ends are
|
||||
/// expected to expand this to an absolute path before sending the
|
||||
/// `ConfigureSession` operation so that the business-logic layer can
|
||||
/// operate deterministically.
|
||||
cwd: PathBuf,
|
||||
/// execution sandbox are resolved against this directory **instead** of
|
||||
/// the process-wide current working directory.
|
||||
cwd: AbsolutePathBuf,
|
||||
/// Directory containing all Codex state for this session.
|
||||
codex_home: PathBuf,
|
||||
/// Optional user-facing name for the thread, updated during the session.
|
||||
@@ -1107,7 +1105,7 @@ impl SessionConfiguration {
|
||||
approval_policy: self.approval_policy.value(),
|
||||
approvals_reviewer: self.approvals_reviewer,
|
||||
sandbox_policy: self.sandbox_policy.get().clone(),
|
||||
cwd: self.cwd.clone(),
|
||||
cwd: self.cwd.to_path_buf(),
|
||||
ephemeral: self.original_config_do_not_use.ephemeral,
|
||||
reasoning_effort: self.collaboration_mode.reasoning_effort(),
|
||||
personality: self.personality,
|
||||
@@ -1150,11 +1148,23 @@ impl SessionConfiguration {
|
||||
if let Some(windows_sandbox_level) = updates.windows_sandbox_level {
|
||||
next_configuration.windows_sandbox_level = windows_sandbox_level;
|
||||
}
|
||||
let mut cwd_changed = false;
|
||||
if let Some(cwd) = updates.cwd.clone() {
|
||||
next_configuration.cwd = cwd;
|
||||
cwd_changed = true;
|
||||
}
|
||||
|
||||
let absolute_cwd = updates
|
||||
.cwd
|
||||
.as_ref()
|
||||
.map(|cwd| {
|
||||
AbsolutePathBuf::relative_to_current_dir(normalize_for_native_workdir(
|
||||
cwd.as_path(),
|
||||
))
|
||||
.unwrap_or_else(|e| {
|
||||
warn!("failed to normalize update cwd: {cwd:?}: {e}");
|
||||
self.cwd.clone()
|
||||
})
|
||||
})
|
||||
.unwrap_or_else(|| self.cwd.clone());
|
||||
|
||||
let cwd_changed = absolute_cwd.as_path() != self.cwd.as_path();
|
||||
next_configuration.cwd = absolute_cwd;
|
||||
if sandbox_policy_changed || (cwd_changed && file_system_policy_matches_legacy) {
|
||||
// Preserve richer split policies across cwd-only updates; only
|
||||
// rederive when the session is already using the legacy bridge.
|
||||
@@ -1351,8 +1361,6 @@ impl Session {
|
||||
let auth_manager_for_context = auth_manager;
|
||||
let provider_for_context = provider;
|
||||
let session_telemetry_for_context = session_telemetry;
|
||||
let per_turn_config = Arc::new(per_turn_config);
|
||||
|
||||
let tools_config = ToolsConfig::new(&ToolsConfigParams {
|
||||
model_info: &model_info,
|
||||
available_models: &models_manager.try_list_models().unwrap_or_default(),
|
||||
@@ -1372,10 +1380,12 @@ impl Session {
|
||||
.with_agent_roles(per_turn_config.agent_roles.clone());
|
||||
|
||||
let cwd = session_configuration.cwd.clone();
|
||||
|
||||
let per_turn_config = Arc::new(per_turn_config);
|
||||
let turn_metadata_state = Arc::new(TurnMetadataState::new(
|
||||
conversation_id.to_string(),
|
||||
sub_id.clone(),
|
||||
cwd.clone(),
|
||||
cwd.to_path_buf(),
|
||||
session_configuration.sandbox_policy.get(),
|
||||
session_configuration.windows_sandbox_level,
|
||||
));
|
||||
@@ -1447,13 +1457,6 @@ impl Session {
|
||||
session_configuration.collaboration_mode.model(),
|
||||
session_configuration.provider
|
||||
);
|
||||
if !session_configuration.cwd.is_absolute() {
|
||||
return Err(anyhow::anyhow!(
|
||||
"cwd is not absolute: {:?}",
|
||||
session_configuration.cwd
|
||||
));
|
||||
}
|
||||
|
||||
let forked_from_id = initial_history.forked_from_id();
|
||||
|
||||
let (conversation_id, rollout_params) = match &initial_history {
|
||||
@@ -1723,7 +1726,7 @@ impl Session {
|
||||
ShellSnapshot::start_snapshotting(
|
||||
config.codex_home.clone(),
|
||||
conversation_id,
|
||||
session_configuration.cwd.clone(),
|
||||
session_configuration.cwd.to_path_buf(),
|
||||
&mut default_shell,
|
||||
session_telemetry.clone(),
|
||||
)
|
||||
@@ -1922,7 +1925,7 @@ impl Session {
|
||||
approval_policy: session_configuration.approval_policy.value(),
|
||||
approvals_reviewer: session_configuration.approvals_reviewer,
|
||||
sandbox_policy: session_configuration.sandbox_policy.get().clone(),
|
||||
cwd: session_configuration.cwd.clone(),
|
||||
cwd: session_configuration.cwd.to_path_buf(),
|
||||
reasoning_effort: session_configuration.collaboration_mode.reasoning_effort(),
|
||||
history_log_id,
|
||||
history_entry_count,
|
||||
@@ -1943,7 +1946,7 @@ impl Session {
|
||||
let sandbox_state = SandboxState {
|
||||
sandbox_policy: session_configuration.sandbox_policy.get().clone(),
|
||||
codex_linux_sandbox_exe: config.codex_linux_sandbox_exe.clone(),
|
||||
sandbox_cwd: session_configuration.cwd.clone(),
|
||||
sandbox_cwd: session_configuration.cwd.to_path_buf(),
|
||||
use_legacy_landlock: config.features.use_legacy_landlock(),
|
||||
};
|
||||
let mut required_mcp_servers: Vec<String> = mcp_servers
|
||||
@@ -2407,7 +2410,7 @@ impl Session {
|
||||
let sandbox_state = SandboxState {
|
||||
sandbox_policy: per_turn_config.permissions.sandbox_policy.get().clone(),
|
||||
codex_linux_sandbox_exe: per_turn_config.codex_linux_sandbox_exe.clone(),
|
||||
sandbox_cwd: per_turn_config.cwd.clone(),
|
||||
sandbox_cwd: per_turn_config.cwd.to_path_buf(),
|
||||
use_legacy_landlock: per_turn_config.features.use_legacy_landlock(),
|
||||
};
|
||||
if let Err(e) = self
|
||||
@@ -4138,7 +4141,7 @@ impl Session {
|
||||
let sandbox_state = SandboxState {
|
||||
sandbox_policy: turn_context.sandbox_policy.get().clone(),
|
||||
codex_linux_sandbox_exe: turn_context.codex_linux_sandbox_exe.clone(),
|
||||
sandbox_cwd: turn_context.cwd.clone(),
|
||||
sandbox_cwd: turn_context.cwd.to_path_buf(),
|
||||
use_legacy_landlock: turn_context.features.use_legacy_landlock(),
|
||||
};
|
||||
{
|
||||
@@ -4919,7 +4922,7 @@ mod handlers {
|
||||
) {
|
||||
let cwds = if cwds.is_empty() {
|
||||
let state = sess.state.lock().await;
|
||||
vec![state.session_configuration.cwd.clone()]
|
||||
vec![state.session_configuration.cwd.to_path_buf()]
|
||||
} else {
|
||||
cwds
|
||||
};
|
||||
@@ -5363,7 +5366,7 @@ async fn spawn_review_thread(
|
||||
let turn_metadata_state = Arc::new(TurnMetadataState::new(
|
||||
sess.conversation_id.to_string(),
|
||||
review_turn_id.clone(),
|
||||
parent_turn_context.cwd.clone(),
|
||||
parent_turn_context.cwd.to_path_buf(),
|
||||
parent_turn_context.sandbox_policy.get(),
|
||||
parent_turn_context.windows_sandbox_level,
|
||||
));
|
||||
@@ -5852,7 +5855,7 @@ pub(crate) async fn run_turn(
|
||||
let stop_request = codex_hooks::StopRequest {
|
||||
session_id: sess.conversation_id,
|
||||
turn_id: turn_context.sub_id.clone(),
|
||||
cwd: turn_context.cwd.clone(),
|
||||
cwd: turn_context.cwd.to_path_buf(),
|
||||
transcript_path: sess.hook_transcript_path().await,
|
||||
model: turn_context.model_info.slug.clone(),
|
||||
permission_mode: stop_hook_permission_mode,
|
||||
@@ -5902,7 +5905,7 @@ pub(crate) async fn run_turn(
|
||||
.hooks()
|
||||
.dispatch(HookPayload {
|
||||
session_id: sess.conversation_id,
|
||||
cwd: turn_context.cwd.clone(),
|
||||
cwd: turn_context.cwd.to_path_buf(),
|
||||
client: turn_context.app_server_client_name.clone(),
|
||||
triggered_at: chrono::Utc::now(),
|
||||
hook_event: HookEvent::AfterAgent {
|
||||
|
||||
@@ -62,7 +62,7 @@ async fn record_initial_history_resumed_bare_turn_context_does_not_hydrate_previ
|
||||
let previous_context_item = TurnContextItem {
|
||||
turn_id: Some(turn_context.sub_id.clone()),
|
||||
trace_id: turn_context.trace_id.clone(),
|
||||
cwd: turn_context.cwd.clone(),
|
||||
cwd: turn_context.cwd.to_path_buf(),
|
||||
current_date: turn_context.current_date.clone(),
|
||||
timezone: turn_context.timezone.clone(),
|
||||
approval_policy: turn_context.approval_policy.value(),
|
||||
@@ -101,7 +101,7 @@ async fn record_initial_history_resumed_hydrates_previous_turn_settings_from_lif
|
||||
let mut previous_context_item = TurnContextItem {
|
||||
turn_id: Some(turn_context.sub_id.clone()),
|
||||
trace_id: turn_context.trace_id.clone(),
|
||||
cwd: turn_context.cwd.clone(),
|
||||
cwd: turn_context.cwd.to_path_buf(),
|
||||
current_date: turn_context.current_date.clone(),
|
||||
timezone: turn_context.timezone.clone(),
|
||||
approval_policy: turn_context.approval_policy.value(),
|
||||
@@ -851,7 +851,7 @@ async fn record_initial_history_resumed_turn_context_after_compaction_reestablis
|
||||
let previous_context_item = TurnContextItem {
|
||||
turn_id: Some(turn_context.sub_id.clone()),
|
||||
trace_id: turn_context.trace_id.clone(),
|
||||
cwd: turn_context.cwd.clone(),
|
||||
cwd: turn_context.cwd.to_path_buf(),
|
||||
current_date: turn_context.current_date.clone(),
|
||||
timezone: turn_context.timezone.clone(),
|
||||
approval_policy: turn_context.approval_policy.value(),
|
||||
@@ -923,7 +923,7 @@ async fn record_initial_history_resumed_turn_context_after_compaction_reestablis
|
||||
serde_json::to_value(Some(TurnContextItem {
|
||||
turn_id: Some(turn_context.sub_id.clone()),
|
||||
trace_id: turn_context.trace_id.clone(),
|
||||
cwd: turn_context.cwd.clone(),
|
||||
cwd: turn_context.cwd.to_path_buf(),
|
||||
current_date: turn_context.current_date.clone(),
|
||||
timezone: turn_context.timezone.clone(),
|
||||
approval_policy: turn_context.approval_policy.value(),
|
||||
@@ -952,7 +952,7 @@ async fn record_initial_history_resumed_aborted_turn_without_id_clears_active_tu
|
||||
let previous_context_item = TurnContextItem {
|
||||
turn_id: Some(turn_context.sub_id.clone()),
|
||||
trace_id: turn_context.trace_id.clone(),
|
||||
cwd: turn_context.cwd.clone(),
|
||||
cwd: turn_context.cwd.to_path_buf(),
|
||||
current_date: turn_context.current_date.clone(),
|
||||
timezone: turn_context.timezone.clone(),
|
||||
approval_policy: turn_context.approval_policy.value(),
|
||||
@@ -1058,7 +1058,7 @@ async fn record_initial_history_resumed_unmatched_abort_preserves_active_turn_fo
|
||||
let current_context_item = TurnContextItem {
|
||||
turn_id: Some(current_turn_id.clone()),
|
||||
trace_id: turn_context.trace_id.clone(),
|
||||
cwd: turn_context.cwd.clone(),
|
||||
cwd: turn_context.cwd.to_path_buf(),
|
||||
current_date: turn_context.current_date.clone(),
|
||||
timezone: turn_context.timezone.clone(),
|
||||
approval_policy: turn_context.approval_policy.value(),
|
||||
@@ -1160,7 +1160,7 @@ async fn record_initial_history_resumed_trailing_incomplete_turn_compaction_clea
|
||||
let previous_context_item = TurnContextItem {
|
||||
turn_id: Some(turn_context.sub_id.clone()),
|
||||
trace_id: turn_context.trace_id.clone(),
|
||||
cwd: turn_context.cwd.clone(),
|
||||
cwd: turn_context.cwd.to_path_buf(),
|
||||
current_date: turn_context.current_date.clone(),
|
||||
timezone: turn_context.timezone.clone(),
|
||||
approval_policy: turn_context.approval_policy.value(),
|
||||
@@ -1304,7 +1304,7 @@ async fn record_initial_history_resumed_replaced_incomplete_compacted_turn_clear
|
||||
let previous_context_item = TurnContextItem {
|
||||
turn_id: Some(turn_context.sub_id.clone()),
|
||||
trace_id: turn_context.trace_id.clone(),
|
||||
cwd: turn_context.cwd.clone(),
|
||||
cwd: turn_context.cwd.to_path_buf(),
|
||||
current_date: turn_context.current_date.clone(),
|
||||
timezone: turn_context.timezone.clone(),
|
||||
approval_policy: turn_context.approval_policy.value(),
|
||||
|
||||
@@ -22,7 +22,6 @@ use codex_protocol::request_permissions::RequestPermissionsResponse;
|
||||
use codex_protocol::request_user_input::RequestUserInputArgs;
|
||||
use codex_protocol::request_user_input::RequestUserInputResponse;
|
||||
use codex_protocol::user_input::UserInput;
|
||||
use codex_utils_absolute_path::AbsolutePathBuf;
|
||||
use serde_json::Value;
|
||||
use std::time::Duration;
|
||||
use tokio::sync::Mutex;
|
||||
@@ -518,7 +517,7 @@ async fn handle_patch_approval(
|
||||
let change_count = changes.len();
|
||||
let maybe_files = changes
|
||||
.keys()
|
||||
.map(|path| AbsolutePathBuf::from_absolute_path(parent_ctx.cwd.join(path)).ok())
|
||||
.map(|path| parent_ctx.cwd.join(path).ok())
|
||||
.collect::<Option<Vec<_>>>();
|
||||
if let Some(files) = maybe_files {
|
||||
let review_cancel = cancel_token.child_token();
|
||||
@@ -554,7 +553,7 @@ async fn handle_patch_approval(
|
||||
Arc::clone(parent_ctx),
|
||||
GuardianApprovalRequest::ApplyPatch {
|
||||
id: approval_id.clone(),
|
||||
cwd: parent_ctx.cwd.clone(),
|
||||
cwd: parent_ctx.cwd.to_path_buf(),
|
||||
files,
|
||||
change_count,
|
||||
patch,
|
||||
|
||||
@@ -80,6 +80,7 @@ use codex_protocol::protocol::ConversationAudioParams;
|
||||
use codex_protocol::protocol::RealtimeAudioFrame;
|
||||
use codex_protocol::protocol::Submission;
|
||||
use codex_protocol::protocol::W3cTraceContext;
|
||||
use core_test_support::PathBufExt;
|
||||
use core_test_support::context_snapshot;
|
||||
use core_test_support::context_snapshot::ContextSnapshotOptions;
|
||||
use core_test_support::context_snapshot::ContextSnapshotRenderMode;
|
||||
@@ -1266,7 +1267,7 @@ async fn record_initial_history_forked_hydrates_previous_turn_settings() {
|
||||
let previous_context_item = TurnContextItem {
|
||||
turn_id: Some(turn_context.sub_id.clone()),
|
||||
trace_id: turn_context.trace_id.clone(),
|
||||
cwd: turn_context.cwd.clone(),
|
||||
cwd: turn_context.cwd.to_path_buf(),
|
||||
current_date: turn_context.current_date.clone(),
|
||||
timezone: turn_context.timezone.clone(),
|
||||
approval_policy: turn_context.approval_policy.value(),
|
||||
@@ -2282,10 +2283,9 @@ async fn session_configuration_apply_preserves_split_file_system_policy_on_cwd_o
|
||||
let original_cwd = project_root.join("subdir");
|
||||
let docs_dir = original_cwd.join("docs");
|
||||
std::fs::create_dir_all(&docs_dir).expect("create docs dir");
|
||||
let docs_dir =
|
||||
codex_utils_absolute_path::AbsolutePathBuf::from_absolute_path(&docs_dir).expect("docs");
|
||||
let docs_dir = docs_dir.abs();
|
||||
|
||||
session_configuration.cwd = original_cwd;
|
||||
session_configuration.cwd = original_cwd.abs();
|
||||
session_configuration.sandbox_policy =
|
||||
codex_config::Constrained::allow_any(SandboxPolicy::WorkspaceWrite {
|
||||
writable_roots: Vec::new(),
|
||||
@@ -2407,10 +2407,9 @@ async fn session_configuration_apply_rederives_legacy_file_system_policy_on_cwd_
|
||||
let original_cwd = project_root.join("subdir");
|
||||
let docs_dir = original_cwd.join("docs");
|
||||
std::fs::create_dir_all(&docs_dir).expect("create docs dir");
|
||||
let docs_dir =
|
||||
codex_utils_absolute_path::AbsolutePathBuf::from_absolute_path(&docs_dir).expect("docs");
|
||||
let docs_dir = docs_dir.abs();
|
||||
|
||||
session_configuration.cwd = original_cwd;
|
||||
session_configuration.cwd = original_cwd.abs();
|
||||
session_configuration.sandbox_policy =
|
||||
codex_config::Constrained::allow_any(SandboxPolicy::WorkspaceWrite {
|
||||
writable_roots: Vec::new(),
|
||||
@@ -2444,6 +2443,36 @@ async fn session_configuration_apply_rederives_legacy_file_system_policy_on_cwd_
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn session_update_settings_keeps_runtime_cwds_absolute() {
|
||||
let (session, turn_context) = make_session_and_context().await;
|
||||
let updated_cwd = turn_context
|
||||
.cwd
|
||||
.join("project")
|
||||
.expect("resolve project dir");
|
||||
std::fs::create_dir_all(updated_cwd.as_path()).expect("create project dir");
|
||||
|
||||
session
|
||||
.update_settings(SessionSettingsUpdate {
|
||||
cwd: Some(PathBuf::from("project")),
|
||||
..Default::default()
|
||||
})
|
||||
.await
|
||||
.expect("cwd update should succeed");
|
||||
|
||||
let session_cwd = {
|
||||
let state = session.state.lock().await;
|
||||
state.session_configuration.cwd.clone()
|
||||
};
|
||||
let config = session.get_config().await;
|
||||
let next_turn = session.new_default_turn().await;
|
||||
|
||||
assert_eq!(session_cwd, updated_cwd);
|
||||
assert_eq!(config.cwd, turn_context.cwd);
|
||||
assert_eq!(next_turn.cwd, updated_cwd);
|
||||
assert_eq!(next_turn.config.cwd, updated_cwd);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn session_new_fails_when_zsh_fork_enabled_without_zsh_path() {
|
||||
let codex_home = tempfile::tempdir().expect("create temp dir");
|
||||
@@ -3058,7 +3087,7 @@ async fn user_turn_updates_approvals_reviewer() {
|
||||
text: "hello".to_string(),
|
||||
text_elements: Vec::new(),
|
||||
}],
|
||||
cwd: config.cwd.clone(),
|
||||
cwd: config.cwd.to_path_buf(),
|
||||
approval_policy: config.permissions.approval_policy.value(),
|
||||
approvals_reviewer: Some(crate::config::types::ApprovalsReviewer::GuardianSubagent),
|
||||
sandbox_policy: config.permissions.sandbox_policy.get().clone(),
|
||||
@@ -5060,7 +5089,7 @@ async fn rejects_escalated_permissions_when_policy_not_on_request() {
|
||||
"echo hi".to_string(),
|
||||
]
|
||||
},
|
||||
cwd: turn_context.cwd.clone(),
|
||||
cwd: turn_context.cwd.to_path_buf(),
|
||||
expiration: timeout_ms.into(),
|
||||
capture_policy: ExecCapturePolicy::ShellTool,
|
||||
env: HashMap::new(),
|
||||
|
||||
@@ -23,7 +23,8 @@ use codex_protocol::models::ResponseItem;
|
||||
use codex_protocol::models::function_call_output_content_items_to_text;
|
||||
use codex_protocol::permissions::FileSystemSandboxPolicy;
|
||||
use codex_protocol::permissions::NetworkSandboxPolicy;
|
||||
use codex_utils_absolute_path::AbsolutePathBuf;
|
||||
use core_test_support::PathExt;
|
||||
use core_test_support::TempDirExt;
|
||||
use core_test_support::codex_linux_sandbox_exe_or_skip;
|
||||
use core_test_support::responses::ev_assistant_message;
|
||||
use core_test_support::responses::ev_completed;
|
||||
@@ -123,7 +124,7 @@ async fn guardian_allows_shell_additional_permissions_requests_past_policy_valid
|
||||
"echo hi".to_string(),
|
||||
]
|
||||
},
|
||||
cwd: turn_context.cwd.clone(),
|
||||
cwd: turn_context.cwd.to_path_buf(),
|
||||
expiration: expiration_ms.into(),
|
||||
capture_policy: ExecCapturePolicy::ShellTool,
|
||||
env: HashMap::new(),
|
||||
@@ -388,12 +389,11 @@ async fn guardian_subagent_does_not_inherit_parent_exec_policy_rules() {
|
||||
.expect("write policy file");
|
||||
|
||||
let mut config = build_test_config(codex_home.path()).await;
|
||||
config.cwd = project_dir.path().to_path_buf();
|
||||
config.cwd = project_dir.abs();
|
||||
config.config_layer_stack = ConfigLayerStack::new(
|
||||
vec![ConfigLayerEntry::new(
|
||||
ConfigLayerSource::Project {
|
||||
dot_codex_folder: AbsolutePathBuf::from_absolute_path(project_dir.path())
|
||||
.expect("absolute project path"),
|
||||
dot_codex_folder: project_dir.path().abs(),
|
||||
},
|
||||
toml::Value::Table(Default::default()),
|
||||
)],
|
||||
|
||||
@@ -27,6 +27,9 @@ use serde::Deserialize;
|
||||
use tempfile::tempdir;
|
||||
|
||||
use super::*;
|
||||
use core_test_support::PathBufExt;
|
||||
use core_test_support::PathExt;
|
||||
use core_test_support::TempDirExt;
|
||||
use core_test_support::test_absolute_path;
|
||||
use pretty_assertions::assert_eq;
|
||||
|
||||
@@ -77,6 +80,23 @@ fn http_mcp(url: &str) -> McpServerConfig {
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn load_config_normalizes_relative_cwd_override() -> std::io::Result<()> {
|
||||
let expected_cwd = AbsolutePathBuf::relative_to_current_dir("nested")?;
|
||||
let codex_home = tempdir()?;
|
||||
let config = Config::load_from_base_config_with_overrides(
|
||||
ConfigToml::default(),
|
||||
ConfigOverrides {
|
||||
cwd: Some(PathBuf::from("nested")),
|
||||
..Default::default()
|
||||
},
|
||||
codex_home.abs().into_path_buf(),
|
||||
)?;
|
||||
|
||||
assert_eq!(config.cwd, expected_cwd);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_toml_parsing() {
|
||||
let history_with_persistence = r#"
|
||||
@@ -460,7 +480,7 @@ fn default_permissions_profile_populates_runtime_sandbox_policy() -> std::io::Re
|
||||
codex_home.path().to_path_buf(),
|
||||
)?;
|
||||
|
||||
let memories_root = AbsolutePathBuf::try_from(codex_home.path().join("memories")).unwrap();
|
||||
let memories_root = codex_home.path().join("memories").abs();
|
||||
assert_eq!(
|
||||
config.permissions.file_system_sandbox_policy,
|
||||
FileSystemSandboxPolicy::restricted(vec![
|
||||
@@ -496,9 +516,7 @@ fn default_permissions_profile_populates_runtime_sandbox_policy() -> std::io::Re
|
||||
writable_roots: vec![memories_root],
|
||||
read_only_access: ReadOnlyAccess::Restricted {
|
||||
include_platform_defaults: true,
|
||||
readable_roots: vec![
|
||||
AbsolutePathBuf::try_from(cwd.path().join("docs")).expect("absolute docs path"),
|
||||
],
|
||||
readable_roots: vec![cwd.path().join("docs").abs(),],
|
||||
},
|
||||
network_access: false,
|
||||
exclude_tmpdir_env_var: true,
|
||||
@@ -1277,7 +1295,7 @@ fn add_dir_override_extends_workspace_writable_roots() -> std::io::Result<()> {
|
||||
temp_dir.path().to_path_buf(),
|
||||
)?;
|
||||
|
||||
let expected_backend = AbsolutePathBuf::try_from(backend).unwrap();
|
||||
let expected_backend = backend.abs();
|
||||
if cfg!(target_os = "windows") {
|
||||
match config.permissions.sandbox_policy.get() {
|
||||
SandboxPolicy::ReadOnly { .. } => {}
|
||||
@@ -1327,7 +1345,7 @@ fn workspace_write_always_includes_memories_root_once() -> std::io::Result<()> {
|
||||
let config = Config::load_from_base_config_with_overrides(
|
||||
ConfigToml {
|
||||
sandbox_workspace_write: Some(SandboxWorkspaceWrite {
|
||||
writable_roots: vec![AbsolutePathBuf::from_absolute_path(&memories_root)?],
|
||||
writable_roots: vec![memories_root.abs()],
|
||||
..Default::default()
|
||||
}),
|
||||
..Default::default()
|
||||
@@ -1350,7 +1368,7 @@ fn workspace_write_always_includes_memories_root_once() -> std::io::Result<()> {
|
||||
"expected memories root directory to exist at {}",
|
||||
memories_root.display()
|
||||
);
|
||||
let expected_memories_root = AbsolutePathBuf::from_absolute_path(&memories_root)?;
|
||||
let expected_memories_root = memories_root.abs();
|
||||
match config.permissions.sandbox_policy.get() {
|
||||
SandboxPolicy::WorkspaceWrite { writable_roots, .. } => {
|
||||
assert_eq!(
|
||||
@@ -1768,7 +1786,7 @@ async fn managed_config_overrides_oauth_store_mode() -> anyhow::Result<()> {
|
||||
macos_managed_config_requirements_base64: None,
|
||||
};
|
||||
|
||||
let cwd = AbsolutePathBuf::try_from(codex_home.path())?;
|
||||
let cwd = codex_home.path().abs();
|
||||
let config_layer_stack = load_config_layers_state(
|
||||
codex_home.path(),
|
||||
Some(cwd),
|
||||
@@ -1897,7 +1915,7 @@ async fn managed_config_wins_over_cli_overrides() -> anyhow::Result<()> {
|
||||
macos_managed_config_requirements_base64: None,
|
||||
};
|
||||
|
||||
let cwd = AbsolutePathBuf::try_from(codex_home.path())?;
|
||||
let cwd = codex_home.path().abs();
|
||||
let config_layer_stack = load_config_layers_state(
|
||||
codex_home.path(),
|
||||
Some(cwd),
|
||||
@@ -2933,7 +2951,11 @@ struct PrecedenceTestFixture {
|
||||
}
|
||||
|
||||
impl PrecedenceTestFixture {
|
||||
fn cwd(&self) -> PathBuf {
|
||||
fn cwd(&self) -> AbsolutePathBuf {
|
||||
self.cwd.abs()
|
||||
}
|
||||
|
||||
fn cwd_path(&self) -> PathBuf {
|
||||
self.cwd.path().to_path_buf()
|
||||
}
|
||||
|
||||
@@ -2974,7 +2996,7 @@ fn loads_compact_prompt_from_file() -> std::io::Result<()> {
|
||||
std::fs::write(&prompt_path, " summarize differently ")?;
|
||||
|
||||
let cfg = ConfigToml {
|
||||
experimental_compact_prompt_file: Some(AbsolutePathBuf::from_absolute_path(prompt_path)?),
|
||||
experimental_compact_prompt_file: Some(prompt_path.abs()),
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
@@ -3071,7 +3093,7 @@ fn load_config_rejects_missing_agent_role_config_file() -> std::io::Result<()> {
|
||||
"researcher".to_string(),
|
||||
AgentRoleToml {
|
||||
description: Some("Research role".to_string()),
|
||||
config_file: Some(AbsolutePathBuf::from_absolute_path(missing_path)?),
|
||||
config_file: Some(missing_path.abs()),
|
||||
nickname_candidates: None,
|
||||
},
|
||||
)]),
|
||||
@@ -4082,7 +4104,7 @@ fn model_catalog_json_loads_from_path() -> std::io::Result<()> {
|
||||
)?;
|
||||
|
||||
let cfg = ConfigToml {
|
||||
model_catalog_json: Some(AbsolutePathBuf::from_absolute_path(catalog_path)?),
|
||||
model_catalog_json: Some(catalog_path.abs()),
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
@@ -4103,7 +4125,7 @@ fn model_catalog_json_rejects_empty_catalog() -> std::io::Result<()> {
|
||||
std::fs::write(&catalog_path, r#"{"models":[]}"#)?;
|
||||
|
||||
let cfg = ConfigToml {
|
||||
model_catalog_json: Some(AbsolutePathBuf::from_absolute_path(catalog_path)?),
|
||||
model_catalog_json: Some(catalog_path.abs()),
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
@@ -4240,7 +4262,7 @@ fn test_precedence_fixture_with_o3_profile() -> std::io::Result<()> {
|
||||
|
||||
let o3_profile_overrides = ConfigOverrides {
|
||||
config_profile: Some("o3".to_string()),
|
||||
cwd: Some(fixture.cwd()),
|
||||
cwd: Some(fixture.cwd_path()),
|
||||
..Default::default()
|
||||
};
|
||||
let o3_profile_config: Config = Config::load_from_base_config_with_overrides(
|
||||
@@ -4368,7 +4390,7 @@ fn metrics_exporter_defaults_to_statsig_when_missing() -> std::io::Result<()> {
|
||||
let config = Config::load_from_base_config_with_overrides(
|
||||
fixture.cfg.clone(),
|
||||
ConfigOverrides {
|
||||
cwd: Some(fixture.cwd()),
|
||||
cwd: Some(fixture.cwd_path()),
|
||||
..Default::default()
|
||||
},
|
||||
fixture.codex_home(),
|
||||
@@ -4384,7 +4406,7 @@ fn test_precedence_fixture_with_gpt3_profile() -> std::io::Result<()> {
|
||||
|
||||
let gpt3_profile_overrides = ConfigOverrides {
|
||||
config_profile: Some("gpt3".to_string()),
|
||||
cwd: Some(fixture.cwd()),
|
||||
cwd: Some(fixture.cwd_path()),
|
||||
..Default::default()
|
||||
};
|
||||
let gpt3_profile_config = Config::load_from_base_config_with_overrides(
|
||||
@@ -4505,7 +4527,7 @@ fn test_precedence_fixture_with_gpt3_profile() -> std::io::Result<()> {
|
||||
// Verify that loading without specifying a profile in ConfigOverrides
|
||||
// uses the default profile from the config file (which is "gpt3").
|
||||
let default_profile_overrides = ConfigOverrides {
|
||||
cwd: Some(fixture.cwd()),
|
||||
cwd: Some(fixture.cwd_path()),
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
@@ -4525,7 +4547,7 @@ fn test_precedence_fixture_with_zdr_profile() -> std::io::Result<()> {
|
||||
|
||||
let zdr_profile_overrides = ConfigOverrides {
|
||||
config_profile: Some("zdr".to_string()),
|
||||
cwd: Some(fixture.cwd()),
|
||||
cwd: Some(fixture.cwd_path()),
|
||||
..Default::default()
|
||||
};
|
||||
let zdr_profile_config = Config::load_from_base_config_with_overrides(
|
||||
@@ -4652,7 +4674,7 @@ fn test_precedence_fixture_with_gpt5_profile() -> std::io::Result<()> {
|
||||
|
||||
let gpt5_profile_overrides = ConfigOverrides {
|
||||
config_profile: Some("gpt5".to_string()),
|
||||
cwd: Some(fixture.cwd()),
|
||||
cwd: Some(fixture.cwd_path()),
|
||||
..Default::default()
|
||||
};
|
||||
let gpt5_profile_config = Config::load_from_base_config_with_overrides(
|
||||
@@ -4820,7 +4842,7 @@ fn test_requirements_web_search_mode_allowlist_does_not_warn_when_unset() -> any
|
||||
let config = Config::load_config_with_layer_stack(
|
||||
fixture.cfg.clone(),
|
||||
ConfigOverrides {
|
||||
cwd: Some(fixture.cwd()),
|
||||
cwd: Some(fixture.cwd_path()),
|
||||
..Default::default()
|
||||
},
|
||||
fixture.codex_home(),
|
||||
|
||||
@@ -395,10 +395,10 @@ pub struct Config {
|
||||
/// Syntax highlighting theme override (kebab-case name).
|
||||
pub tui_theme: Option<String>,
|
||||
|
||||
/// The directory that should be treated as the current working directory
|
||||
/// for the session. All relative paths inside the business-logic layer are
|
||||
/// resolved against this path.
|
||||
pub cwd: PathBuf,
|
||||
/// The absolute directory that should be treated as the current working
|
||||
/// directory for the session. All relative paths inside the business-logic
|
||||
/// layer are resolved against this path.
|
||||
pub cwd: AbsolutePathBuf,
|
||||
|
||||
/// Preferred store for CLI auth credentials.
|
||||
/// file (default): Use a file in the Codex home directory.
|
||||
@@ -683,7 +683,7 @@ impl ConfigBuilder {
|
||||
let loader_overrides = loader_overrides.unwrap_or_default();
|
||||
let cwd_override = harness_overrides.cwd.as_deref().or(fallback_cwd.as_deref());
|
||||
let cwd = match cwd_override {
|
||||
Some(path) => AbsolutePathBuf::try_from(path)?,
|
||||
Some(path) => AbsolutePathBuf::relative_to_current_dir(path)?,
|
||||
None => AbsolutePathBuf::current_dir()?,
|
||||
};
|
||||
harness_overrides.cwd = Some(cwd.to_path_buf());
|
||||
@@ -2104,7 +2104,7 @@ impl Config {
|
||||
let windows_sandbox_mode = resolve_windows_sandbox_mode(&cfg, &config_profile);
|
||||
let windows_sandbox_private_desktop =
|
||||
resolve_windows_sandbox_private_desktop(&cfg, &config_profile);
|
||||
let resolved_cwd = normalize_for_native_workdir({
|
||||
let resolved_cwd = AbsolutePathBuf::try_from(normalize_for_native_workdir({
|
||||
use std::env;
|
||||
|
||||
match cwd {
|
||||
@@ -2121,13 +2121,13 @@ impl Config {
|
||||
current
|
||||
}
|
||||
}
|
||||
});
|
||||
}))?;
|
||||
let mut additional_writable_roots: Vec<AbsolutePathBuf> = additional_writable_roots
|
||||
.into_iter()
|
||||
.map(|path| AbsolutePathBuf::resolve_path_against_base(path, &resolved_cwd))
|
||||
.map(|path| AbsolutePathBuf::resolve_path_against_base(path, resolved_cwd.as_path()))
|
||||
.collect::<Result<Vec<_>, _>>()?;
|
||||
let active_project = cfg
|
||||
.get_active_project(&resolved_cwd)
|
||||
.get_active_project(resolved_cwd.as_path())
|
||||
.unwrap_or(ProjectConfig { trust_level: None });
|
||||
let permission_config_syntax = resolve_permission_config_syntax(
|
||||
&config_layer_stack,
|
||||
@@ -2200,12 +2200,15 @@ impl Config {
|
||||
&mut startup_warnings,
|
||||
)?;
|
||||
let mut sandbox_policy = file_system_sandbox_policy
|
||||
.to_legacy_sandbox_policy(network_sandbox_policy, &resolved_cwd)?;
|
||||
.to_legacy_sandbox_policy(network_sandbox_policy, resolved_cwd.as_path())?;
|
||||
if matches!(sandbox_policy, SandboxPolicy::WorkspaceWrite { .. }) {
|
||||
file_system_sandbox_policy = file_system_sandbox_policy
|
||||
.with_additional_writable_roots(&resolved_cwd, &additional_writable_roots);
|
||||
.with_additional_writable_roots(
|
||||
resolved_cwd.as_path(),
|
||||
&additional_writable_roots,
|
||||
);
|
||||
sandbox_policy = file_system_sandbox_policy
|
||||
.to_legacy_sandbox_policy(network_sandbox_policy, &resolved_cwd)?;
|
||||
.to_legacy_sandbox_policy(network_sandbox_policy, resolved_cwd.as_path())?;
|
||||
}
|
||||
(
|
||||
configured_network_proxy_config,
|
||||
@@ -2219,7 +2222,7 @@ impl Config {
|
||||
sandbox_mode,
|
||||
config_profile.sandbox_mode,
|
||||
windows_sandbox_level,
|
||||
&resolved_cwd,
|
||||
resolved_cwd.as_path(),
|
||||
Some(&constrained_sandbox_policy),
|
||||
);
|
||||
if let SandboxPolicy::WorkspaceWrite { writable_roots, .. } = &mut sandbox_policy {
|
||||
@@ -2229,8 +2232,10 @@ impl Config {
|
||||
}
|
||||
}
|
||||
}
|
||||
let file_system_sandbox_policy =
|
||||
FileSystemSandboxPolicy::from_legacy_sandbox_policy(&sandbox_policy, &resolved_cwd);
|
||||
let file_system_sandbox_policy = FileSystemSandboxPolicy::from_legacy_sandbox_policy(
|
||||
&sandbox_policy,
|
||||
resolved_cwd.as_path(),
|
||||
);
|
||||
let network_sandbox_policy = NetworkSandboxPolicy::from(&sandbox_policy);
|
||||
(
|
||||
configured_network_proxy_config,
|
||||
@@ -2566,11 +2571,11 @@ impl Config {
|
||||
} else {
|
||||
FileSystemSandboxPolicy::from_legacy_sandbox_policy(
|
||||
&effective_sandbox_policy,
|
||||
&resolved_cwd,
|
||||
resolved_cwd.as_path(),
|
||||
)
|
||||
};
|
||||
let effective_file_system_sandbox_policy = effective_file_system_sandbox_policy
|
||||
.with_additional_readable_roots(&resolved_cwd, &helper_readable_roots);
|
||||
.with_additional_readable_roots(resolved_cwd.as_path(), &helper_readable_roots);
|
||||
let effective_network_sandbox_policy =
|
||||
if effective_sandbox_policy == original_sandbox_policy {
|
||||
network_sandbox_policy
|
||||
|
||||
@@ -70,8 +70,8 @@ impl EnvironmentContext {
|
||||
) -> Self {
|
||||
let before_network = Self::network_from_turn_context_item(before);
|
||||
let after_network = Self::network_from_turn_context(after);
|
||||
let cwd = if before.cwd != after.cwd {
|
||||
Some(after.cwd.clone())
|
||||
let cwd = if before.cwd.as_path() != after.cwd.as_path() {
|
||||
Some(after.cwd.to_path_buf())
|
||||
} else {
|
||||
None
|
||||
};
|
||||
@@ -94,7 +94,7 @@ impl EnvironmentContext {
|
||||
|
||||
pub fn from_turn_context(turn_context: &TurnContext, shell: &Shell) -> Self {
|
||||
Self::new(
|
||||
Some(turn_context.cwd.clone()),
|
||||
Some(turn_context.cwd.to_path_buf()),
|
||||
shell.clone(),
|
||||
turn_context.current_date.clone(),
|
||||
turn_context.timezone.clone(),
|
||||
|
||||
@@ -140,7 +140,7 @@ impl GuardianReviewSessionReuseKey {
|
||||
base_instructions: spawn_config.base_instructions.clone(),
|
||||
user_instructions: spawn_config.user_instructions.clone(),
|
||||
compact_prompt: spawn_config.compact_prompt.clone(),
|
||||
cwd: spawn_config.cwd.clone(),
|
||||
cwd: spawn_config.cwd.to_path_buf(),
|
||||
mcp_servers: spawn_config.mcp_servers.clone(),
|
||||
codex_linux_sandbox_exe: spawn_config.codex_linux_sandbox_exe.clone(),
|
||||
main_execve_wrapper_exe: spawn_config.main_execve_wrapper_exe.clone(),
|
||||
@@ -512,7 +512,7 @@ async fn run_review_on_session(
|
||||
.codex
|
||||
.submit(Op::UserTurn {
|
||||
items: params.prompt_items.clone(),
|
||||
cwd: params.parent_turn.cwd.clone(),
|
||||
cwd: params.parent_turn.cwd.to_path_buf(),
|
||||
approval_policy: AskForApproval::Never,
|
||||
approvals_reviewer: None,
|
||||
sandbox_policy: SandboxPolicy::new_read_only_policy(),
|
||||
|
||||
@@ -25,7 +25,8 @@ use codex_protocol::protocol::EventMsg;
|
||||
use codex_protocol::protocol::GuardianAssessmentStatus;
|
||||
use codex_protocol::protocol::GuardianRiskLevel;
|
||||
use codex_protocol::protocol::ReviewDecision;
|
||||
use codex_utils_absolute_path::AbsolutePathBuf;
|
||||
use core_test_support::PathBufExt;
|
||||
use core_test_support::TempDirExt;
|
||||
use core_test_support::context_snapshot;
|
||||
use core_test_support::context_snapshot::ContextSnapshotOptions;
|
||||
use core_test_support::responses::ev_assistant_message;
|
||||
@@ -322,7 +323,7 @@ fn guardian_assessment_action_value_redacts_apply_patch_patch_text() {
|
||||
("/tmp", "/tmp/guardian.txt")
|
||||
};
|
||||
let cwd = PathBuf::from(cwd);
|
||||
let file = AbsolutePathBuf::try_from(file).expect("absolute path");
|
||||
let file = PathBuf::from(file).abs();
|
||||
let action = GuardianApprovalRequest::ApplyPatch {
|
||||
id: "patch-1".to_string(),
|
||||
cwd: cwd.clone(),
|
||||
@@ -356,7 +357,7 @@ fn guardian_request_turn_id_prefers_network_access_owner_turn() {
|
||||
let apply_patch = GuardianApprovalRequest::ApplyPatch {
|
||||
id: "patch-1".to_string(),
|
||||
cwd: PathBuf::from("/tmp"),
|
||||
files: vec![AbsolutePathBuf::try_from("/tmp/guardian.txt").expect("absolute path")],
|
||||
files: vec![PathBuf::from("/tmp/guardian.txt").abs()],
|
||||
change_count: 1usize,
|
||||
patch: "*** Begin Patch\n*** Update File: guardian.txt\n@@\n+hello\n*** End Patch"
|
||||
.to_string(),
|
||||
@@ -384,7 +385,7 @@ async fn cancelled_guardian_review_emits_terminal_abort_without_warning() {
|
||||
GuardianApprovalRequest::ApplyPatch {
|
||||
id: "patch-1".to_string(),
|
||||
cwd: PathBuf::from("/tmp"),
|
||||
files: vec![AbsolutePathBuf::try_from("/tmp/guardian.txt").expect("absolute path")],
|
||||
files: vec![PathBuf::from("/tmp/guardian.txt").abs()],
|
||||
change_count: 1usize,
|
||||
patch: "*** Begin Patch\n*** Update File: guardian.txt\n@@\n+hello\n*** End Patch"
|
||||
.to_string(),
|
||||
@@ -512,7 +513,7 @@ async fn guardian_review_request_layout_matches_model_visible_request_snapshot()
|
||||
let (mut session, mut turn) = crate::codex::make_session_and_context().await;
|
||||
let temp_cwd = TempDir::new()?;
|
||||
let mut config = (*turn.config).clone();
|
||||
config.cwd = temp_cwd.path().to_path_buf();
|
||||
config.cwd = temp_cwd.abs();
|
||||
config.model_provider.base_url = Some(format!("{}/v1", server.uri()));
|
||||
let config = Arc::new(config);
|
||||
let models_manager = Arc::new(test_support::models_manager_with_provider(
|
||||
|
||||
@@ -92,7 +92,7 @@ pub(crate) async fn run_pending_session_start_hooks(
|
||||
|
||||
let request = codex_hooks::SessionStartRequest {
|
||||
session_id: sess.conversation_id,
|
||||
cwd: turn_context.cwd.clone(),
|
||||
cwd: turn_context.cwd.to_path_buf(),
|
||||
transcript_path: sess.hook_transcript_path().await,
|
||||
model: turn_context.model_info.slug.clone(),
|
||||
permission_mode: hook_permission_mode(turn_context),
|
||||
@@ -120,7 +120,7 @@ pub(crate) async fn run_pre_tool_use_hooks(
|
||||
let request = PreToolUseRequest {
|
||||
session_id: sess.conversation_id,
|
||||
turn_id: turn_context.sub_id.clone(),
|
||||
cwd: turn_context.cwd.clone(),
|
||||
cwd: turn_context.cwd.to_path_buf(),
|
||||
transcript_path: sess.hook_transcript_path().await,
|
||||
model: turn_context.model_info.slug.clone(),
|
||||
permission_mode: hook_permission_mode(turn_context),
|
||||
@@ -149,7 +149,7 @@ pub(crate) async fn run_user_prompt_submit_hooks(
|
||||
let request = UserPromptSubmitRequest {
|
||||
session_id: sess.conversation_id,
|
||||
turn_id: turn_context.sub_id.clone(),
|
||||
cwd: turn_context.cwd.clone(),
|
||||
cwd: turn_context.cwd.to_path_buf(),
|
||||
transcript_path: sess.hook_transcript_path().await,
|
||||
model: turn_context.model_info.slug.clone(),
|
||||
permission_mode: hook_permission_mode(turn_context),
|
||||
|
||||
@@ -266,7 +266,16 @@ mod agent {
|
||||
let root = memory_root(&config.codex_home);
|
||||
let mut agent_config = config.as_ref().clone();
|
||||
|
||||
agent_config.cwd = root;
|
||||
match AbsolutePathBuf::from_absolute_path(root) {
|
||||
Ok(root) => agent_config.cwd = root,
|
||||
Err(err) => {
|
||||
warn!(
|
||||
"memory phase-2 consolidation could not set cwd from codex_home {}: {err}",
|
||||
agent_config.codex_home.display()
|
||||
);
|
||||
return None;
|
||||
}
|
||||
}
|
||||
// Consolidation threads must never feed back into phase-1 memory generation.
|
||||
agent_config.memories.generate_memories = false;
|
||||
// Approval policy
|
||||
|
||||
@@ -435,6 +435,7 @@ mod phase2 {
|
||||
use codex_state::Phase2JobClaimOutcome;
|
||||
use codex_state::Stage1Output;
|
||||
use codex_state::ThreadMetadataBuilder;
|
||||
use core_test_support::PathBufExt;
|
||||
use std::path::PathBuf;
|
||||
use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
@@ -469,7 +470,7 @@ mod phase2 {
|
||||
let codex_home = tempfile::tempdir().expect("create temp codex home");
|
||||
let mut config = test_config();
|
||||
config.codex_home = codex_home.path().to_path_buf();
|
||||
config.cwd = config.codex_home.clone();
|
||||
config.cwd = config.codex_home.abs();
|
||||
let config = Arc::new(config);
|
||||
|
||||
let state_db = codex_state::StateRuntime::init(
|
||||
@@ -507,7 +508,7 @@ mod phase2 {
|
||||
Utc::now(),
|
||||
SessionSource::Cli,
|
||||
);
|
||||
metadata_builder.cwd = self.config.cwd.clone();
|
||||
metadata_builder.cwd = self.config.cwd.to_path_buf();
|
||||
metadata_builder.model_provider = Some(self.config.model_provider_id.clone());
|
||||
let metadata = metadata_builder.build(&self.config.model_provider_id);
|
||||
|
||||
@@ -882,7 +883,7 @@ mod phase2 {
|
||||
let codex_home = tempfile::tempdir().expect("create temp codex home");
|
||||
let mut config = test_config();
|
||||
config.codex_home = codex_home.path().to_path_buf();
|
||||
config.cwd = config.codex_home.clone();
|
||||
config.cwd = config.codex_home.abs();
|
||||
let config = Arc::new(config);
|
||||
|
||||
let state_db = codex_state::StateRuntime::init(
|
||||
@@ -904,7 +905,7 @@ mod phase2 {
|
||||
Utc::now(),
|
||||
SessionSource::Cli,
|
||||
);
|
||||
metadata_builder.cwd = config.cwd.clone();
|
||||
metadata_builder.cwd = config.cwd.to_path_buf();
|
||||
metadata_builder.model_provider = Some(config.model_provider_id.clone());
|
||||
let metadata = metadata_builder.build(&config.model_provider_id);
|
||||
state_db
|
||||
|
||||
@@ -184,7 +184,7 @@ pub async fn read_project_docs(config: &Config) -> std::io::Result<Option<String
|
||||
/// directory (inclusive). Symlinks are allowed. When `project_doc_max_bytes`
|
||||
/// is zero, returns an empty list.
|
||||
pub fn discover_project_doc_paths(config: &Config) -> std::io::Result<Vec<PathBuf>> {
|
||||
let mut dir = config.cwd.clone();
|
||||
let mut dir = config.cwd.to_path_buf();
|
||||
if let Ok(canon) = normalize_path(&dir) {
|
||||
dir = canon;
|
||||
}
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
use super::*;
|
||||
use crate::config::ConfigBuilder;
|
||||
use codex_features::Feature;
|
||||
use core_test_support::PathBufExt;
|
||||
use core_test_support::TempDirExt;
|
||||
use std::fs;
|
||||
use std::path::PathBuf;
|
||||
use tempfile::TempDir;
|
||||
@@ -18,7 +20,7 @@ async fn make_config(root: &TempDir, limit: usize, instructions: Option<&str>) -
|
||||
.await
|
||||
.expect("defaults for test should always succeed");
|
||||
|
||||
config.cwd = root.path().to_path_buf();
|
||||
config.cwd = root.abs();
|
||||
config.project_doc_max_bytes = limit;
|
||||
|
||||
config.user_instructions = instructions.map(ToOwned::to_owned);
|
||||
@@ -62,7 +64,7 @@ async fn make_config_with_project_root_markers(
|
||||
.await
|
||||
.expect("defaults for test should always succeed");
|
||||
|
||||
config.cwd = root.path().to_path_buf();
|
||||
config.cwd = root.abs();
|
||||
config.project_doc_max_bytes = limit;
|
||||
config.user_instructions = instructions.map(ToOwned::to_owned);
|
||||
config
|
||||
@@ -136,7 +138,7 @@ async fn finds_doc_in_repo_root() {
|
||||
|
||||
// Build config pointing at the nested dir.
|
||||
let mut cfg = make_config(&repo, 4096, None).await;
|
||||
cfg.cwd = nested;
|
||||
cfg.cwd = nested.abs();
|
||||
|
||||
let res = get_user_instructions(&cfg).await.expect("doc expected");
|
||||
assert_eq!(res, "root level doc");
|
||||
@@ -261,7 +263,7 @@ async fn concatenates_root_and_cwd_docs() {
|
||||
fs::write(nested.join("AGENTS.md"), "crate doc").unwrap();
|
||||
|
||||
let mut cfg = make_config(&repo, 4096, None).await;
|
||||
cfg.cwd = nested;
|
||||
cfg.cwd = nested.abs();
|
||||
|
||||
let res = get_user_instructions(&cfg).await.expect("doc expected");
|
||||
assert_eq!(res, "root doc\n\ncrate doc");
|
||||
@@ -278,13 +280,13 @@ async fn project_root_markers_are_honored_for_agents_discovery() {
|
||||
fs::write(nested.join("AGENTS.md"), "child doc").unwrap();
|
||||
|
||||
let mut cfg = make_config_with_project_root_markers(&root, 4096, None, &[".codex-root"]).await;
|
||||
cfg.cwd = nested;
|
||||
cfg.cwd = nested.abs();
|
||||
|
||||
let discovery = discover_project_doc_paths(&cfg).expect("discover paths");
|
||||
let expected_parent =
|
||||
dunce::canonicalize(root.path().join("AGENTS.md")).expect("canonical parent doc path");
|
||||
let expected_child =
|
||||
dunce::canonicalize(cfg.cwd.join("AGENTS.md")).expect("canonical child doc path");
|
||||
dunce::canonicalize(cfg.cwd.as_path().join("AGENTS.md")).expect("canonical child doc path");
|
||||
assert_eq!(discovery.len(), 2);
|
||||
assert_eq!(discovery[0], expected_parent);
|
||||
assert_eq!(discovery[1], expected_child);
|
||||
|
||||
@@ -145,7 +145,7 @@ pub(crate) async fn execute_user_shell_command(
|
||||
process_id: None,
|
||||
turn_id: turn_context.sub_id.clone(),
|
||||
command: display_command.clone(),
|
||||
cwd: cwd.clone(),
|
||||
cwd: cwd.to_path_buf(),
|
||||
parsed_cmd: parsed_cmd.clone(),
|
||||
source: ExecCommandSource::UserShell,
|
||||
interaction_input: None,
|
||||
@@ -156,7 +156,7 @@ pub(crate) async fn execute_user_shell_command(
|
||||
let sandbox_policy = SandboxPolicy::DangerFullAccess;
|
||||
let exec_env = ExecRequest {
|
||||
command: exec_command.clone(),
|
||||
cwd: cwd.clone(),
|
||||
cwd: cwd.to_path_buf(),
|
||||
env: create_env(
|
||||
&turn_context.shell_environment_policy,
|
||||
Some(session.conversation_id),
|
||||
@@ -221,7 +221,7 @@ pub(crate) async fn execute_user_shell_command(
|
||||
process_id: None,
|
||||
turn_id: turn_context.sub_id.clone(),
|
||||
command: display_command.clone(),
|
||||
cwd: cwd.clone(),
|
||||
cwd: cwd.to_path_buf(),
|
||||
parsed_cmd: parsed_cmd.clone(),
|
||||
source: ExecCommandSource::UserShell,
|
||||
interaction_input: None,
|
||||
@@ -245,7 +245,7 @@ pub(crate) async fn execute_user_shell_command(
|
||||
process_id: None,
|
||||
turn_id: turn_context.sub_id.clone(),
|
||||
command: display_command.clone(),
|
||||
cwd: cwd.clone(),
|
||||
cwd: cwd.to_path_buf(),
|
||||
parsed_cmd: parsed_cmd.clone(),
|
||||
source: ExecCommandSource::UserShell,
|
||||
interaction_input: None,
|
||||
@@ -289,7 +289,7 @@ pub(crate) async fn execute_user_shell_command(
|
||||
process_id: None,
|
||||
turn_id: turn_context.sub_id.clone(),
|
||||
command: display_command,
|
||||
cwd,
|
||||
cwd: cwd.to_path_buf(),
|
||||
parsed_cmd,
|
||||
source: ExecCommandSource::UserShell,
|
||||
interaction_input: None,
|
||||
|
||||
@@ -12,6 +12,7 @@ use codex_protocol::openai_models::ModelsResponse;
|
||||
use codex_protocol::protocol::AgentMessageEvent;
|
||||
use codex_protocol::protocol::TurnStartedEvent;
|
||||
use codex_protocol::protocol::UserMessageEvent;
|
||||
use core_test_support::PathExt;
|
||||
use core_test_support::responses::mount_models_once;
|
||||
use pretty_assertions::assert_eq;
|
||||
use std::time::Duration;
|
||||
@@ -236,7 +237,7 @@ async fn shutdown_all_threads_bounded_submits_shutdown_to_every_thread() {
|
||||
let temp_dir = tempdir().expect("tempdir");
|
||||
let mut config = test_config();
|
||||
config.codex_home = temp_dir.path().join("codex-home");
|
||||
config.cwd = config.codex_home.clone();
|
||||
config.cwd = config.codex_home.abs();
|
||||
std::fs::create_dir_all(&config.codex_home).expect("create codex home");
|
||||
|
||||
let manager = ThreadManager::with_models_provider_and_home_for_tests(
|
||||
@@ -275,7 +276,7 @@ async fn new_uses_configured_openai_provider_for_model_refresh() {
|
||||
let temp_dir = tempdir().expect("tempdir");
|
||||
let mut config = test_config();
|
||||
config.codex_home = temp_dir.path().join("codex-home");
|
||||
config.cwd = config.codex_home.clone();
|
||||
config.cwd = config.codex_home.abs();
|
||||
std::fs::create_dir_all(&config.codex_home).expect("create codex home");
|
||||
config.model_catalog = None;
|
||||
config
|
||||
@@ -408,7 +409,7 @@ async fn interrupted_fork_snapshot_does_not_synthesize_turn_id_for_legacy_histor
|
||||
let temp_dir = tempdir().expect("tempdir");
|
||||
let mut config = test_config();
|
||||
config.codex_home = temp_dir.path().join("codex-home");
|
||||
config.cwd = config.codex_home.clone();
|
||||
config.cwd = config.codex_home.abs();
|
||||
std::fs::create_dir_all(&config.codex_home).expect("create codex home");
|
||||
|
||||
let auth_manager =
|
||||
@@ -505,7 +506,7 @@ async fn interrupted_fork_snapshot_preserves_explicit_turn_id() {
|
||||
let temp_dir = tempdir().expect("tempdir");
|
||||
let mut config = test_config();
|
||||
config.codex_home = temp_dir.path().join("codex-home");
|
||||
config.cwd = config.codex_home.clone();
|
||||
config.cwd = config.codex_home.abs();
|
||||
std::fs::create_dir_all(&config.codex_home).expect("create codex home");
|
||||
|
||||
let auth_manager =
|
||||
@@ -591,7 +592,7 @@ async fn interrupted_fork_snapshot_uses_persisted_mid_turn_history_without_live_
|
||||
let temp_dir = tempdir().expect("tempdir");
|
||||
let mut config = test_config();
|
||||
config.codex_home = temp_dir.path().join("codex-home");
|
||||
config.cwd = config.codex_home.clone();
|
||||
config.cwd = config.codex_home.abs();
|
||||
std::fs::create_dir_all(&config.codex_home).expect("create codex home");
|
||||
|
||||
let auth_manager =
|
||||
|
||||
@@ -89,7 +89,7 @@ impl ToolHandler for ArtifactsHandler {
|
||||
let result = client
|
||||
.execute_build(ArtifactBuildRequest {
|
||||
source: args.source,
|
||||
cwd: turn.cwd.clone(),
|
||||
cwd: turn.cwd.to_path_buf(),
|
||||
timeout: Some(Duration::from_millis(
|
||||
args.timeout_ms
|
||||
.unwrap_or(DEFAULT_EXECUTION_TIMEOUT.as_millis() as u64),
|
||||
@@ -221,7 +221,7 @@ fn default_runtime_manager(codex_home: std::path::PathBuf) -> ArtifactRuntimeMan
|
||||
async fn emit_exec_begin(session: &Session, turn: &TurnContext, call_id: &str) {
|
||||
let emitter = ToolEmitter::shell(
|
||||
vec![ARTIFACTS_TOOL_NAME.to_string()],
|
||||
turn.cwd.clone(),
|
||||
turn.cwd.to_path_buf(),
|
||||
ExecCommandSource::Agent,
|
||||
/*freeform*/ true,
|
||||
);
|
||||
@@ -247,7 +247,7 @@ async fn emit_exec_end(
|
||||
};
|
||||
let emitter = ToolEmitter::shell(
|
||||
vec![ARTIFACTS_TOOL_NAME.to_string()],
|
||||
turn.cwd.clone(),
|
||||
turn.cwd.to_path_buf(),
|
||||
ExecCommandSource::Agent,
|
||||
/*freeform*/ true,
|
||||
);
|
||||
|
||||
@@ -61,7 +61,7 @@ async fn emit_js_repl_exec_begin(
|
||||
) {
|
||||
let emitter = ToolEmitter::shell(
|
||||
vec!["js_repl".to_string()],
|
||||
turn.cwd.clone(),
|
||||
turn.cwd.to_path_buf(),
|
||||
ExecCommandSource::Agent,
|
||||
/*freeform*/ false,
|
||||
);
|
||||
@@ -80,7 +80,7 @@ async fn emit_js_repl_exec_end(
|
||||
let exec_output = build_js_repl_exec_output(output, error, duration);
|
||||
let emitter = ToolEmitter::shell(
|
||||
vec!["js_repl".to_string()],
|
||||
turn.cwd.clone(),
|
||||
turn.cwd.to_path_buf(),
|
||||
ExecCommandSource::Agent,
|
||||
/*freeform*/ false,
|
||||
);
|
||||
|
||||
@@ -77,7 +77,7 @@ async fn emit_js_repl_exec_end_sends_event() {
|
||||
assert_eq!(event.call_id, "call-1");
|
||||
assert_eq!(event.turn_id, turn.sub_id);
|
||||
assert_eq!(event.command, vec!["js_repl".to_string()]);
|
||||
assert_eq!(event.cwd, turn.cwd);
|
||||
assert_eq!(event.cwd, turn.cwd.to_path_buf());
|
||||
assert_eq!(event.source, ExecCommandSource::Agent);
|
||||
assert_eq!(event.interaction_input, None);
|
||||
assert_eq!(event.stdout, "hello");
|
||||
|
||||
@@ -39,6 +39,7 @@ use codex_protocol::protocol::InitialHistory;
|
||||
use codex_protocol::protocol::InterAgentCommunication;
|
||||
use codex_protocol::protocol::RolloutItem;
|
||||
use codex_protocol::user_input::UserInput;
|
||||
use core_test_support::TempDirExt;
|
||||
use pretty_assertions::assert_eq;
|
||||
use serde::Deserialize;
|
||||
use serde_json::json;
|
||||
@@ -2236,7 +2237,7 @@ async fn build_agent_spawn_config_uses_turn_context_values() {
|
||||
..ShellEnvironmentPolicy::default()
|
||||
};
|
||||
let temp_dir = tempfile::tempdir().expect("temp dir");
|
||||
turn.cwd = temp_dir.path().to_path_buf();
|
||||
turn.cwd = temp_dir.abs();
|
||||
turn.codex_linux_sandbox_exe = Some(PathBuf::from("/bin/echo"));
|
||||
let sandbox_policy = pick_allowed_sandbox_policy(
|
||||
&turn.config.permissions.sandbox_policy,
|
||||
|
||||
@@ -1050,7 +1050,7 @@ impl JsReplManager {
|
||||
"--experimental-vm-modules".to_string(),
|
||||
kernel_path.to_string_lossy().to_string(),
|
||||
],
|
||||
cwd: turn.cwd.clone(),
|
||||
cwd: turn.cwd.to_path_buf(),
|
||||
env,
|
||||
additional_permissions: None,
|
||||
};
|
||||
|
||||
@@ -14,6 +14,8 @@ use codex_protocol::models::FunctionCallOutputPayload;
|
||||
use codex_protocol::models::ImageDetail;
|
||||
use codex_protocol::models::ResponseInputItem;
|
||||
use codex_protocol::openai_models::InputModality;
|
||||
use core_test_support::PathBufExt;
|
||||
use core_test_support::TempDirExt;
|
||||
use pretty_assertions::assert_eq;
|
||||
use std::fs;
|
||||
use std::path::Path;
|
||||
@@ -739,7 +741,7 @@ async fn interrupt_active_exec_stops_aborted_kernel_before_later_exec() -> anyho
|
||||
|
||||
let dir = tempdir()?;
|
||||
let (session, mut turn) = make_session_and_context().await;
|
||||
turn.cwd = dir.path().to_path_buf();
|
||||
turn.cwd = dir.abs();
|
||||
set_danger_full_access(&mut turn);
|
||||
let session = Arc::new(session);
|
||||
let turn = Arc::new(turn);
|
||||
@@ -1017,7 +1019,7 @@ async fn js_repl_waits_for_unawaited_tool_calls_before_completion() -> anyhow::R
|
||||
|
||||
let marker = turn
|
||||
.cwd
|
||||
.join(format!("js-repl-unawaited-marker-{}.txt", Uuid::new_v4()));
|
||||
.join(format!("js-repl-unawaited-marker-{}.txt", Uuid::new_v4()))?;
|
||||
let marker_json = serde_json::to_string(&marker.to_string_lossy().to_string())?;
|
||||
let result = manager
|
||||
.execute(
|
||||
@@ -1062,10 +1064,10 @@ async fn js_repl_persisted_tool_helpers_work_across_cells() -> anyhow::Result<()
|
||||
|
||||
let global_marker = turn
|
||||
.cwd
|
||||
.join(format!("js-repl-global-helper-{}.txt", Uuid::new_v4()));
|
||||
.join(format!("js-repl-global-helper-{}.txt", Uuid::new_v4()))?;
|
||||
let lexical_marker = turn
|
||||
.cwd
|
||||
.join(format!("js-repl-lexical-helper-{}.txt", Uuid::new_v4()));
|
||||
.join(format!("js-repl-lexical-helper-{}.txt", Uuid::new_v4()))?;
|
||||
let global_marker_json = serde_json::to_string(&global_marker.to_string_lossy().to_string())?;
|
||||
let lexical_marker_json = serde_json::to_string(&lexical_marker.to_string_lossy().to_string())?;
|
||||
|
||||
@@ -2101,7 +2103,7 @@ async fn js_repl_prefers_env_node_module_dirs_over_config() -> anyhow::Result<()
|
||||
"CODEX_JS_REPL_NODE_MODULE_DIRS".to_string(),
|
||||
env_base.path().to_string_lossy().to_string(),
|
||||
);
|
||||
turn.cwd = cwd_dir.path().to_path_buf();
|
||||
turn.cwd = cwd_dir.abs();
|
||||
turn.js_repl = Arc::new(JsReplHandle::with_node_path(
|
||||
turn.config.js_repl_node_path.clone(),
|
||||
vec![config_base.path().to_path_buf()],
|
||||
@@ -2145,7 +2147,7 @@ async fn js_repl_resolves_from_first_config_dir() -> anyhow::Result<()> {
|
||||
turn.shell_environment_policy
|
||||
.r#set
|
||||
.remove("CODEX_JS_REPL_NODE_MODULE_DIRS");
|
||||
turn.cwd = cwd_dir.path().to_path_buf();
|
||||
turn.cwd = cwd_dir.abs();
|
||||
turn.js_repl = Arc::new(JsReplHandle::with_node_path(
|
||||
turn.config.js_repl_node_path.clone(),
|
||||
vec![
|
||||
@@ -2189,7 +2191,7 @@ async fn js_repl_falls_back_to_cwd_node_modules() -> anyhow::Result<()> {
|
||||
turn.shell_environment_policy
|
||||
.r#set
|
||||
.remove("CODEX_JS_REPL_NODE_MODULE_DIRS");
|
||||
turn.cwd = cwd_dir.path().to_path_buf();
|
||||
turn.cwd = cwd_dir.abs();
|
||||
turn.js_repl = Arc::new(JsReplHandle::with_node_path(
|
||||
turn.config.js_repl_node_path.clone(),
|
||||
vec![config_base.path().to_path_buf()],
|
||||
@@ -2230,7 +2232,7 @@ async fn js_repl_accepts_node_modules_dir_entries() -> anyhow::Result<()> {
|
||||
turn.shell_environment_policy
|
||||
.r#set
|
||||
.remove("CODEX_JS_REPL_NODE_MODULE_DIRS");
|
||||
turn.cwd = cwd_dir.path().to_path_buf();
|
||||
turn.cwd = cwd_dir.abs();
|
||||
turn.js_repl = Arc::new(JsReplHandle::with_node_path(
|
||||
turn.config.js_repl_node_path.clone(),
|
||||
vec![base_dir.path().join("node_modules")],
|
||||
@@ -2284,7 +2286,7 @@ async fn js_repl_supports_relative_file_imports() -> anyhow::Result<()> {
|
||||
turn.shell_environment_policy
|
||||
.r#set
|
||||
.remove("CODEX_JS_REPL_NODE_MODULE_DIRS");
|
||||
turn.cwd = cwd_dir.path().to_path_buf();
|
||||
turn.cwd = cwd_dir.abs();
|
||||
turn.js_repl = Arc::new(JsReplHandle::with_node_path(
|
||||
turn.config.js_repl_node_path.clone(),
|
||||
Vec::new(),
|
||||
@@ -2331,7 +2333,7 @@ async fn js_repl_supports_absolute_file_imports() -> anyhow::Result<()> {
|
||||
turn.shell_environment_policy
|
||||
.r#set
|
||||
.remove("CODEX_JS_REPL_NODE_MODULE_DIRS");
|
||||
turn.cwd = cwd_dir.path().to_path_buf();
|
||||
turn.cwd = cwd_dir.abs();
|
||||
turn.js_repl = Arc::new(JsReplHandle::with_node_path(
|
||||
turn.config.js_repl_node_path.clone(),
|
||||
Vec::new(),
|
||||
@@ -2385,7 +2387,7 @@ async fn js_repl_imported_local_files_can_access_repl_globals() -> anyhow::Resul
|
||||
turn.shell_environment_policy
|
||||
.r#set
|
||||
.remove("CODEX_JS_REPL_NODE_MODULE_DIRS");
|
||||
turn.cwd = cwd_dir.path().to_path_buf();
|
||||
turn.cwd = cwd_dir.abs();
|
||||
turn.js_repl = Arc::new(JsReplHandle::with_node_path(
|
||||
turn.config.js_repl_node_path.clone(),
|
||||
Vec::new(),
|
||||
@@ -2429,7 +2431,7 @@ async fn js_repl_reimports_local_files_after_edit() -> anyhow::Result<()> {
|
||||
turn.shell_environment_policy
|
||||
.r#set
|
||||
.remove("CODEX_JS_REPL_NODE_MODULE_DIRS");
|
||||
turn.cwd = cwd_dir.path().to_path_buf();
|
||||
turn.cwd = cwd_dir.abs();
|
||||
turn.js_repl = Arc::new(JsReplHandle::with_node_path(
|
||||
turn.config.js_repl_node_path.clone(),
|
||||
Vec::new(),
|
||||
@@ -2485,7 +2487,7 @@ async fn js_repl_reimports_local_files_after_fixing_failure() -> anyhow::Result<
|
||||
turn.shell_environment_policy
|
||||
.r#set
|
||||
.remove("CODEX_JS_REPL_NODE_MODULE_DIRS");
|
||||
turn.cwd = cwd_dir.path().to_path_buf();
|
||||
turn.cwd = cwd_dir.abs();
|
||||
turn.js_repl = Arc::new(JsReplHandle::with_node_path(
|
||||
turn.config.js_repl_node_path.clone(),
|
||||
Vec::new(),
|
||||
@@ -2563,7 +2565,7 @@ async fn js_repl_local_files_expose_node_like_import_meta() -> anyhow::Result<()
|
||||
turn.shell_environment_policy
|
||||
.r#set
|
||||
.remove("CODEX_JS_REPL_NODE_MODULE_DIRS");
|
||||
turn.cwd = cwd_dir.path().to_path_buf();
|
||||
turn.cwd = cwd_dir.abs();
|
||||
turn.js_repl = Arc::new(JsReplHandle::with_node_path(
|
||||
turn.config.js_repl_node_path.clone(),
|
||||
Vec::new(),
|
||||
@@ -2648,7 +2650,7 @@ async fn js_repl_local_files_reject_static_bare_imports() -> anyhow::Result<()>
|
||||
turn.shell_environment_policy
|
||||
.r#set
|
||||
.remove("CODEX_JS_REPL_NODE_MODULE_DIRS");
|
||||
turn.cwd = cwd_dir.path().to_path_buf();
|
||||
turn.cwd = cwd_dir.abs();
|
||||
turn.js_repl = Arc::new(JsReplHandle::with_node_path(
|
||||
turn.config.js_repl_node_path.clone(),
|
||||
Vec::new(),
|
||||
@@ -2693,7 +2695,7 @@ async fn js_repl_rejects_unsupported_file_specifiers() -> anyhow::Result<()> {
|
||||
turn.shell_environment_policy
|
||||
.r#set
|
||||
.remove("CODEX_JS_REPL_NODE_MODULE_DIRS");
|
||||
turn.cwd = cwd_dir.path().to_path_buf();
|
||||
turn.cwd = cwd_dir.abs();
|
||||
turn.js_repl = Arc::new(JsReplHandle::with_node_path(
|
||||
turn.config.js_repl_node_path.clone(),
|
||||
Vec::new(),
|
||||
@@ -2795,7 +2797,7 @@ async fn js_repl_blocks_sensitive_builtin_imports_from_local_files() -> anyhow::
|
||||
turn.shell_environment_policy
|
||||
.r#set
|
||||
.remove("CODEX_JS_REPL_NODE_MODULE_DIRS");
|
||||
turn.cwd = cwd_dir.path().to_path_buf();
|
||||
turn.cwd = cwd_dir.abs();
|
||||
turn.js_repl = Arc::new(JsReplHandle::with_node_path(
|
||||
turn.config.js_repl_node_path.clone(),
|
||||
Vec::new(),
|
||||
@@ -2845,7 +2847,7 @@ async fn js_repl_local_files_do_not_escape_node_module_search_roots() -> anyhow:
|
||||
turn.shell_environment_policy
|
||||
.r#set
|
||||
.remove("CODEX_JS_REPL_NODE_MODULE_DIRS");
|
||||
turn.cwd = cwd_dir.clone();
|
||||
turn.cwd = cwd_dir.abs();
|
||||
turn.js_repl = Arc::new(JsReplHandle::with_node_path(
|
||||
turn.config.js_repl_node_path.clone(),
|
||||
Vec::new(),
|
||||
|
||||
@@ -379,7 +379,7 @@ impl NetworkApprovalService {
|
||||
approval_id,
|
||||
/*approval_id*/ None,
|
||||
prompt_command,
|
||||
turn_context.cwd.clone(),
|
||||
turn_context.cwd.to_path_buf(),
|
||||
Some(prompt_reason),
|
||||
Some(network_approval_context.clone()),
|
||||
/*proposed_execpolicy_amendment*/ None,
|
||||
|
||||
@@ -530,7 +530,7 @@ async fn dispatch_after_tool_use_hook(
|
||||
.hooks()
|
||||
.dispatch(HookPayload {
|
||||
session_id: session.conversation_id,
|
||||
cwd: turn.cwd.clone(),
|
||||
cwd: turn.cwd.to_path_buf(),
|
||||
client: turn.app_server_client_name.clone(),
|
||||
triggered_at: chrono::Utc::now(),
|
||||
hook_event: HookEvent::AfterToolUse {
|
||||
|
||||
@@ -159,7 +159,7 @@ pub(super) async fn try_run_zsh_fork(
|
||||
network: sandbox_network,
|
||||
windows_sandbox_level,
|
||||
arg0,
|
||||
sandbox_policy_cwd: ctx.turn.cwd.clone(),
|
||||
sandbox_policy_cwd: ctx.turn.cwd.to_path_buf(),
|
||||
macos_seatbelt_profile_extensions: ctx
|
||||
.turn
|
||||
.config
|
||||
@@ -263,7 +263,7 @@ pub(crate) async fn prepare_unified_exec_zsh_fork(
|
||||
network: exec_request.network.clone(),
|
||||
windows_sandbox_level: exec_request.windows_sandbox_level,
|
||||
arg0: exec_request.arg0.clone(),
|
||||
sandbox_policy_cwd: ctx.turn.cwd.clone(),
|
||||
sandbox_policy_cwd: ctx.turn.cwd.to_path_buf(),
|
||||
macos_seatbelt_profile_extensions: ctx
|
||||
.turn
|
||||
.config
|
||||
|
||||
@@ -160,7 +160,7 @@ impl UnifiedExecProcessManager {
|
||||
let cwd = request
|
||||
.workdir
|
||||
.clone()
|
||||
.unwrap_or_else(|| context.turn.cwd.clone());
|
||||
.unwrap_or_else(|| context.turn.cwd.to_path_buf());
|
||||
let process = self
|
||||
.open_session_with_sandbox(&request, cwd.clone(), context)
|
||||
.await;
|
||||
|
||||
@@ -14,6 +14,7 @@ use codex_core::config::ConfigBuilder;
|
||||
use codex_core::config::ConfigOverrides;
|
||||
use codex_utils_absolute_path::AbsolutePathBuf;
|
||||
use regex_lite::Regex;
|
||||
use std::path::Path;
|
||||
use std::path::PathBuf;
|
||||
|
||||
pub mod apps_test_server;
|
||||
@@ -104,6 +105,36 @@ pub fn test_absolute_path(unix_path: &str) -> AbsolutePathBuf {
|
||||
test_absolute_path_with_windows(unix_path, /*windows_path*/ None)
|
||||
}
|
||||
|
||||
pub trait PathExt {
|
||||
fn abs(&self) -> AbsolutePathBuf;
|
||||
}
|
||||
|
||||
impl PathExt for Path {
|
||||
fn abs(&self) -> AbsolutePathBuf {
|
||||
AbsolutePathBuf::try_from(self.to_path_buf()).expect("path should already be absolute")
|
||||
}
|
||||
}
|
||||
|
||||
pub trait PathBufExt {
|
||||
fn abs(&self) -> AbsolutePathBuf;
|
||||
}
|
||||
|
||||
impl PathBufExt for PathBuf {
|
||||
fn abs(&self) -> AbsolutePathBuf {
|
||||
self.as_path().abs()
|
||||
}
|
||||
}
|
||||
|
||||
pub trait TempDirExt {
|
||||
fn abs(&self) -> AbsolutePathBuf;
|
||||
}
|
||||
|
||||
impl TempDirExt for TempDir {
|
||||
fn abs(&self) -> AbsolutePathBuf {
|
||||
self.path().abs()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn test_tmp_path() -> AbsolutePathBuf {
|
||||
test_absolute_path_with_windows("/tmp", Some(r"C:\Users\codex\AppData\Local\Temp"))
|
||||
}
|
||||
|
||||
@@ -37,7 +37,10 @@ use serde_json::Value;
|
||||
use tempfile::TempDir;
|
||||
use wiremock::MockServer;
|
||||
|
||||
use crate::PathBufExt;
|
||||
use crate::PathExt;
|
||||
use crate::RemoteEnvConfig;
|
||||
use crate::TempDirExt;
|
||||
use crate::get_remote_test_env;
|
||||
use crate::load_default_config_for_test;
|
||||
use crate::responses::WebSocketTestServer;
|
||||
@@ -297,8 +300,7 @@ fn docker_command_capture_stdout<const N: usize>(args: [&str; N]) -> Result<Stri
|
||||
}
|
||||
|
||||
fn absolute_path(path: &Path) -> Result<AbsolutePathBuf> {
|
||||
AbsolutePathBuf::try_from(path.to_path_buf())
|
||||
.map_err(|err| anyhow!("invalid absolute path {}: {err}", path.display()))
|
||||
Ok(path.abs())
|
||||
}
|
||||
|
||||
/// A collection of different ways the model can output an apply_patch call
|
||||
@@ -393,7 +395,7 @@ impl TestCodexBuilder {
|
||||
let cwd = test_env.cwd.to_path_buf();
|
||||
self.config_mutators.push(Box::new(move |config| {
|
||||
config.experimental_exec_server_url = experimental_exec_server_url;
|
||||
config.cwd = cwd;
|
||||
config.cwd = cwd.abs();
|
||||
}));
|
||||
|
||||
let mut test = self.build(server).await?;
|
||||
@@ -556,7 +558,7 @@ impl TestCodexBuilder {
|
||||
};
|
||||
let cwd = Arc::new(TempDir::new()?);
|
||||
let mut config = load_default_config_for_test(home).await;
|
||||
config.cwd = cwd.path().to_path_buf();
|
||||
config.cwd = cwd.abs();
|
||||
config.model_provider = model_provider;
|
||||
for hook in self.pre_build_hooks.drain(..) {
|
||||
hook(home.path());
|
||||
@@ -716,7 +718,7 @@ impl TestCodex {
|
||||
text_elements: Vec::new(),
|
||||
}],
|
||||
final_output_json_schema: None,
|
||||
cwd: self.config.cwd.clone(),
|
||||
cwd: self.config.cwd.to_path_buf(),
|
||||
approval_policy,
|
||||
approvals_reviewer: None,
|
||||
sandbox_policy,
|
||||
|
||||
@@ -42,6 +42,7 @@ use codex_protocol::protocol::SessionMeta;
|
||||
use codex_protocol::protocol::SessionMetaLine;
|
||||
use codex_protocol::protocol::SessionSource;
|
||||
use codex_protocol::user_input::UserInput;
|
||||
use core_test_support::PathBufExt;
|
||||
use core_test_support::apps_test_server::AppsTestServer;
|
||||
use core_test_support::load_default_config_for_test;
|
||||
use core_test_support::responses::ev_completed;
|
||||
@@ -1105,7 +1106,7 @@ async fn skills_append_to_developer_message() {
|
||||
.with_home(codex_home.clone())
|
||||
.with_auth(CodexAuth::from_api_key("Test API Key"))
|
||||
.with_config(move |config| {
|
||||
config.cwd = codex_home_path;
|
||||
config.cwd = codex_home_path.abs();
|
||||
});
|
||||
let codex = builder
|
||||
.build(&server)
|
||||
@@ -1308,7 +1309,7 @@ async fn user_turn_collaboration_mode_overrides_model_and_effort() -> anyhow::Re
|
||||
text: "hello".into(),
|
||||
text_elements: Vec::new(),
|
||||
}],
|
||||
cwd: config.cwd.clone(),
|
||||
cwd: config.cwd.to_path_buf(),
|
||||
approval_policy: config.permissions.approval_policy.value(),
|
||||
approvals_reviewer: None,
|
||||
sandbox_policy: config.permissions.sandbox_policy.get().clone(),
|
||||
@@ -1426,7 +1427,7 @@ async fn user_turn_explicit_reasoning_summary_overrides_model_catalog_default()
|
||||
text: "hello".into(),
|
||||
text_elements: Vec::new(),
|
||||
}],
|
||||
cwd: config.cwd.clone(),
|
||||
cwd: config.cwd.to_path_buf(),
|
||||
approval_policy: config.permissions.approval_policy.value(),
|
||||
approvals_reviewer: None,
|
||||
sandbox_policy: config.permissions.sandbox_policy.get().clone(),
|
||||
|
||||
@@ -176,7 +176,7 @@ async fn collaboration_instructions_added_on_user_turn() -> Result<()> {
|
||||
text: "hello".into(),
|
||||
text_elements: Vec::new(),
|
||||
}],
|
||||
cwd: test.config.cwd.clone(),
|
||||
cwd: test.config.cwd.to_path_buf(),
|
||||
approval_policy: test.config.permissions.approval_policy.value(),
|
||||
approvals_reviewer: None,
|
||||
sandbox_policy: test.config.permissions.sandbox_policy.get().clone(),
|
||||
@@ -292,7 +292,7 @@ async fn user_turn_overrides_collaboration_instructions_after_override() -> Resu
|
||||
text: "hello".into(),
|
||||
text_elements: Vec::new(),
|
||||
}],
|
||||
cwd: test.config.cwd.clone(),
|
||||
cwd: test.config.cwd.to_path_buf(),
|
||||
approval_policy: test.config.permissions.approval_policy.value(),
|
||||
approvals_reviewer: None,
|
||||
sandbox_policy: test.config.permissions.sandbox_policy.get().clone(),
|
||||
|
||||
@@ -567,11 +567,11 @@ async fn snapshot_rollback_followup_turn_trims_context_updates() -> Result<()> {
|
||||
|
||||
user_turn(&conversation, TURN_ONE_USER).await;
|
||||
|
||||
let override_cwd = config.cwd.join(PRETURN_CONTEXT_DIFF_CWD);
|
||||
let override_cwd = config.cwd.join(PRETURN_CONTEXT_DIFF_CWD)?;
|
||||
std::fs::create_dir_all(&override_cwd)?;
|
||||
conversation
|
||||
.submit(Op::OverrideTurnContext {
|
||||
cwd: Some(override_cwd),
|
||||
cwd: Some(override_cwd.to_path_buf()),
|
||||
approval_policy: None,
|
||||
approvals_reviewer: None,
|
||||
sandbox_policy: None,
|
||||
|
||||
@@ -23,7 +23,14 @@ async fn hierarchical_agents_appends_to_project_doc_in_user_instructions() {
|
||||
.features
|
||||
.enable(Feature::ChildAgentsMd)
|
||||
.expect("test config should allow feature update");
|
||||
std::fs::write(config.cwd.join("AGENTS.md"), "be nice").expect("write AGENTS.md");
|
||||
std::fs::write(
|
||||
config
|
||||
.cwd
|
||||
.join("AGENTS.md")
|
||||
.expect("absolute AGENTS.md path"),
|
||||
"be nice",
|
||||
)
|
||||
.expect("write AGENTS.md");
|
||||
});
|
||||
let test = builder.build(&server).await.expect("build test codex");
|
||||
|
||||
|
||||
@@ -16,7 +16,7 @@ use codex_protocol::protocol::EventMsg;
|
||||
use codex_protocol::protocol::Op;
|
||||
use codex_protocol::protocol::SandboxPolicy;
|
||||
use codex_protocol::user_input::UserInput;
|
||||
use codex_utils_absolute_path::AbsolutePathBuf;
|
||||
use core_test_support::TempDirExt;
|
||||
use core_test_support::responses::ev_completed;
|
||||
use core_test_support::responses::ev_response_created;
|
||||
use core_test_support::responses::mount_sse_once;
|
||||
@@ -689,7 +689,7 @@ async fn per_turn_overrides_keep_cached_prefix_and_key_constant() -> anyhow::Res
|
||||
let new_cwd = TempDir::new().unwrap();
|
||||
let writable = TempDir::new().unwrap();
|
||||
let new_policy = SandboxPolicy::WorkspaceWrite {
|
||||
writable_roots: vec![AbsolutePathBuf::try_from(writable.path()).unwrap()],
|
||||
writable_roots: vec![writable.abs()],
|
||||
read_only_access: Default::default(),
|
||||
network_access: true,
|
||||
exclude_tmpdir_env_var: true,
|
||||
@@ -814,7 +814,7 @@ async fn send_user_turn_with_no_changes_does_not_send_environment_context() -> a
|
||||
text: "hello 1".into(),
|
||||
text_elements: Vec::new(),
|
||||
}],
|
||||
cwd: default_cwd.clone(),
|
||||
cwd: default_cwd.to_path_buf(),
|
||||
approval_policy: default_approval_policy,
|
||||
approvals_reviewer: None,
|
||||
sandbox_policy: default_sandbox_policy.clone(),
|
||||
@@ -835,7 +835,7 @@ async fn send_user_turn_with_no_changes_does_not_send_environment_context() -> a
|
||||
text: "hello 2".into(),
|
||||
text_elements: Vec::new(),
|
||||
}],
|
||||
cwd: default_cwd.clone(),
|
||||
cwd: default_cwd.to_path_buf(),
|
||||
approval_policy: default_approval_policy,
|
||||
approvals_reviewer: None,
|
||||
sandbox_policy: default_sandbox_policy.clone(),
|
||||
@@ -940,7 +940,7 @@ async fn send_user_turn_with_changes_sends_environment_context() -> anyhow::Resu
|
||||
text: "hello 1".into(),
|
||||
text_elements: Vec::new(),
|
||||
}],
|
||||
cwd: default_cwd.clone(),
|
||||
cwd: default_cwd.to_path_buf(),
|
||||
approval_policy: default_approval_policy,
|
||||
approvals_reviewer: None,
|
||||
sandbox_policy: default_sandbox_policy.clone(),
|
||||
@@ -961,7 +961,7 @@ async fn send_user_turn_with_changes_sends_environment_context() -> anyhow::Resu
|
||||
text: "hello 2".into(),
|
||||
text_elements: Vec::new(),
|
||||
}],
|
||||
cwd: default_cwd.clone(),
|
||||
cwd: default_cwd.to_path_buf(),
|
||||
approval_policy: AskForApproval::Never,
|
||||
approvals_reviewer: None,
|
||||
sandbox_policy: SandboxPolicy::DangerFullAccess,
|
||||
|
||||
@@ -28,7 +28,7 @@ fn resume_history(
|
||||
let turn_ctx = TurnContextItem {
|
||||
turn_id: Some(turn_id.clone()),
|
||||
trace_id: None,
|
||||
cwd: config.cwd.clone(),
|
||||
cwd: config.cwd.to_path_buf(),
|
||||
current_date: None,
|
||||
timezone: None,
|
||||
approval_policy: config.permissions.approval_policy.value(),
|
||||
|
||||
@@ -17,6 +17,7 @@ use codex_protocol::protocol::ReviewTarget;
|
||||
use codex_protocol::protocol::RolloutItem;
|
||||
use codex_protocol::protocol::RolloutLine;
|
||||
use codex_protocol::user_input::UserInput;
|
||||
use core_test_support::PathBufExt;
|
||||
use core_test_support::load_sse_fixture_with_id_from_str;
|
||||
use core_test_support::responses::ResponseMock;
|
||||
use core_test_support::responses::mount_sse_sequence;
|
||||
@@ -817,7 +818,7 @@ async fn review_uses_overridden_cwd_for_base_branch_merge_base() {
|
||||
let codex_home = Arc::new(TempDir::new().unwrap());
|
||||
let initial_cwd_path = initial_cwd.path().to_path_buf();
|
||||
let codex = new_conversation_for_server(&server, codex_home.clone(), move |config| {
|
||||
config.cwd = initial_cwd_path;
|
||||
config.cwd = initial_cwd_path.abs();
|
||||
})
|
||||
.await;
|
||||
|
||||
|
||||
@@ -10,6 +10,7 @@ use codex_protocol::protocol::Op;
|
||||
use codex_protocol::protocol::SandboxPolicy;
|
||||
use codex_protocol::protocol::TurnAbortReason;
|
||||
use codex_protocol::user_input::UserInput;
|
||||
use core_test_support::PathBufExt;
|
||||
use core_test_support::assert_regex_match;
|
||||
use core_test_support::responses;
|
||||
use core_test_support::responses::ev_assistant_message;
|
||||
@@ -46,7 +47,7 @@ async fn user_shell_cmd_ls_and_cat_in_temp_dir() {
|
||||
let server = start_mock_server().await;
|
||||
let cwd_path = cwd.path().to_path_buf();
|
||||
let mut builder = test_codex().with_config(move |config| {
|
||||
config.cwd = cwd_path;
|
||||
config.cwd = cwd_path.abs();
|
||||
});
|
||||
let codex = builder
|
||||
.build(&server)
|
||||
|
||||
@@ -41,7 +41,6 @@ use image::load_from_memory;
|
||||
use pretty_assertions::assert_eq;
|
||||
use serde_json::Value;
|
||||
use std::io::Cursor;
|
||||
use std::path::Path;
|
||||
use std::path::PathBuf;
|
||||
use tokio::time::Duration;
|
||||
use wiremock::BodyPrintLimit;
|
||||
@@ -78,11 +77,6 @@ fn find_image_message(body: &Value) -> Option<&Value> {
|
||||
image_messages(body).into_iter().next()
|
||||
}
|
||||
|
||||
fn absolute_path(path: &Path) -> anyhow::Result<codex_utils_absolute_path::AbsolutePathBuf> {
|
||||
codex_utils_absolute_path::AbsolutePathBuf::try_from(path.to_path_buf())
|
||||
.map_err(|err| anyhow::anyhow!("invalid absolute path {}: {err}", path.display()))
|
||||
}
|
||||
|
||||
fn png_bytes(width: u32, height: u32, rgba: [u8; 4]) -> anyhow::Result<Vec<u8>> {
|
||||
let image = ImageBuffer::from_pixel(width, height, Rgba(rgba));
|
||||
let mut cursor = Cursor::new(Vec::new());
|
||||
@@ -91,14 +85,11 @@ fn png_bytes(width: u32, height: u32, rgba: [u8; 4]) -> anyhow::Result<Vec<u8>>
|
||||
}
|
||||
|
||||
async fn create_workspace_directory(test: &TestCodex, rel_path: &str) -> anyhow::Result<PathBuf> {
|
||||
let abs_path = test.config.cwd.join(rel_path);
|
||||
let abs_path = test.config.cwd.join(rel_path)?;
|
||||
test.fs()
|
||||
.create_directory(
|
||||
&absolute_path(&abs_path)?,
|
||||
CreateDirectoryOptions { recursive: true },
|
||||
)
|
||||
.create_directory(&abs_path, CreateDirectoryOptions { recursive: true })
|
||||
.await?;
|
||||
Ok(abs_path)
|
||||
Ok(abs_path.into_path_buf())
|
||||
}
|
||||
|
||||
async fn write_workspace_file(
|
||||
@@ -106,19 +97,14 @@ async fn write_workspace_file(
|
||||
rel_path: &str,
|
||||
contents: Vec<u8>,
|
||||
) -> anyhow::Result<PathBuf> {
|
||||
let abs_path = test.config.cwd.join(rel_path);
|
||||
let abs_path = test.config.cwd.join(rel_path)?;
|
||||
if let Some(parent) = abs_path.parent() {
|
||||
test.fs()
|
||||
.create_directory(
|
||||
&absolute_path(parent)?,
|
||||
CreateDirectoryOptions { recursive: true },
|
||||
)
|
||||
.create_directory(&parent, CreateDirectoryOptions { recursive: true })
|
||||
.await?;
|
||||
}
|
||||
test.fs()
|
||||
.write_file(&absolute_path(&abs_path)?, contents)
|
||||
.await?;
|
||||
Ok(abs_path)
|
||||
test.fs().write_file(&abs_path, contents).await?;
|
||||
Ok(abs_path.into_path_buf())
|
||||
}
|
||||
|
||||
async fn write_workspace_png(
|
||||
@@ -168,7 +154,7 @@ async fn user_turn_with_local_image_attaches_image() -> anyhow::Result<()> {
|
||||
path: abs_path.clone(),
|
||||
}],
|
||||
final_output_json_schema: None,
|
||||
cwd: config.cwd.clone(),
|
||||
cwd: config.cwd.to_path_buf(),
|
||||
approval_policy: AskForApproval::Never,
|
||||
approvals_reviewer: None,
|
||||
sandbox_policy: SandboxPolicy::DangerFullAccess,
|
||||
@@ -240,7 +226,7 @@ async fn view_image_tool_attaches_local_image() -> anyhow::Result<()> {
|
||||
let cwd = config.cwd.clone();
|
||||
|
||||
let rel_path = "assets/example.png";
|
||||
let abs_path = cwd.join(rel_path);
|
||||
let abs_path = cwd.join(rel_path)?;
|
||||
let original_width = 2304;
|
||||
let original_height = 864;
|
||||
write_workspace_png(
|
||||
@@ -277,7 +263,7 @@ async fn view_image_tool_attaches_local_image() -> anyhow::Result<()> {
|
||||
text_elements: Vec::new(),
|
||||
}],
|
||||
final_output_json_schema: None,
|
||||
cwd: cwd.clone(),
|
||||
cwd: cwd.to_path_buf(),
|
||||
approval_policy: AskForApproval::Never,
|
||||
approvals_reviewer: None,
|
||||
sandbox_policy: SandboxPolicy::DangerFullAccess,
|
||||
@@ -312,7 +298,7 @@ async fn view_image_tool_attaches_local_image() -> anyhow::Result<()> {
|
||||
_ => unreachable!("stored event must be ViewImageToolCall"),
|
||||
};
|
||||
assert_eq!(tool_event.call_id, call_id);
|
||||
assert_eq!(tool_event.path, abs_path);
|
||||
assert_eq!(tool_event.path, abs_path.to_path_buf());
|
||||
|
||||
let req = mock.single_request();
|
||||
let body = req.body_json();
|
||||
@@ -418,7 +404,7 @@ async fn view_image_tool_can_preserve_original_resolution_when_requested_on_gpt5
|
||||
text_elements: Vec::new(),
|
||||
}],
|
||||
final_output_json_schema: None,
|
||||
cwd: config.cwd.clone(),
|
||||
cwd: config.cwd.to_path_buf(),
|
||||
approval_policy: AskForApproval::Never,
|
||||
approvals_reviewer: None,
|
||||
sandbox_policy: SandboxPolicy::DangerFullAccess,
|
||||
@@ -517,7 +503,7 @@ async fn view_image_tool_errors_clearly_for_unsupported_detail_values() -> anyho
|
||||
text_elements: Vec::new(),
|
||||
}],
|
||||
final_output_json_schema: None,
|
||||
cwd: config.cwd.clone(),
|
||||
cwd: config.cwd.to_path_buf(),
|
||||
approval_policy: AskForApproval::Never,
|
||||
approvals_reviewer: None,
|
||||
sandbox_policy: SandboxPolicy::DangerFullAccess,
|
||||
@@ -609,7 +595,7 @@ async fn view_image_tool_treats_null_detail_as_omitted() -> anyhow::Result<()> {
|
||||
text_elements: Vec::new(),
|
||||
}],
|
||||
final_output_json_schema: None,
|
||||
cwd: config.cwd.clone(),
|
||||
cwd: config.cwd.to_path_buf(),
|
||||
approval_policy: AskForApproval::Never,
|
||||
approvals_reviewer: None,
|
||||
sandbox_policy: SandboxPolicy::DangerFullAccess,
|
||||
@@ -709,7 +695,7 @@ async fn view_image_tool_resizes_when_model_lacks_original_detail_support() -> a
|
||||
text_elements: Vec::new(),
|
||||
}],
|
||||
final_output_json_schema: None,
|
||||
cwd: config.cwd.clone(),
|
||||
cwd: config.cwd.to_path_buf(),
|
||||
approval_policy: AskForApproval::Never,
|
||||
approvals_reviewer: None,
|
||||
sandbox_policy: SandboxPolicy::DangerFullAccess,
|
||||
@@ -820,7 +806,7 @@ async fn view_image_tool_does_not_force_original_resolution_with_capability_feat
|
||||
text_elements: Vec::new(),
|
||||
}],
|
||||
final_output_json_schema: None,
|
||||
cwd: config.cwd.clone(),
|
||||
cwd: config.cwd.to_path_buf(),
|
||||
approval_policy: AskForApproval::Never,
|
||||
approvals_reviewer: None,
|
||||
sandbox_policy: SandboxPolicy::DangerFullAccess,
|
||||
@@ -1135,7 +1121,7 @@ async fn view_image_tool_errors_when_path_is_directory() -> anyhow::Result<()> {
|
||||
text_elements: Vec::new(),
|
||||
}],
|
||||
final_output_json_schema: None,
|
||||
cwd: config.cwd.clone(),
|
||||
cwd: config.cwd.to_path_buf(),
|
||||
approval_policy: AskForApproval::Never,
|
||||
approvals_reviewer: None,
|
||||
sandbox_policy: SandboxPolicy::DangerFullAccess,
|
||||
@@ -1211,7 +1197,7 @@ async fn view_image_tool_errors_for_non_image_files() -> anyhow::Result<()> {
|
||||
text_elements: Vec::new(),
|
||||
}],
|
||||
final_output_json_schema: None,
|
||||
cwd: config.cwd.clone(),
|
||||
cwd: config.cwd.to_path_buf(),
|
||||
approval_policy: AskForApproval::Never,
|
||||
approvals_reviewer: None,
|
||||
sandbox_policy: SandboxPolicy::DangerFullAccess,
|
||||
@@ -1265,7 +1251,7 @@ async fn view_image_tool_errors_when_file_missing() -> anyhow::Result<()> {
|
||||
} = &test;
|
||||
|
||||
let rel_path = "missing/example.png";
|
||||
let abs_path = config.cwd.join(rel_path);
|
||||
let abs_path = config.cwd.join(rel_path)?;
|
||||
|
||||
let call_id = "view-image-missing";
|
||||
let arguments = serde_json::json!({ "path": rel_path }).to_string();
|
||||
@@ -1292,7 +1278,7 @@ async fn view_image_tool_errors_when_file_missing() -> anyhow::Result<()> {
|
||||
text_elements: Vec::new(),
|
||||
}],
|
||||
final_output_json_schema: None,
|
||||
cwd: config.cwd.clone(),
|
||||
cwd: config.cwd.to_path_buf(),
|
||||
approval_policy: AskForApproval::Never,
|
||||
approvals_reviewer: None,
|
||||
sandbox_policy: SandboxPolicy::DangerFullAccess,
|
||||
@@ -1415,7 +1401,7 @@ async fn view_image_tool_returns_unsupported_message_for_text_only_model() -> an
|
||||
text_elements: Vec::new(),
|
||||
}],
|
||||
final_output_json_schema: None,
|
||||
cwd: config.cwd.clone(),
|
||||
cwd: config.cwd.to_path_buf(),
|
||||
approval_policy: AskForApproval::Never,
|
||||
approvals_reviewer: None,
|
||||
sandbox_policy: SandboxPolicy::DangerFullAccess,
|
||||
@@ -1490,7 +1476,7 @@ async fn replaces_invalid_local_image_after_bad_request() -> anyhow::Result<()>
|
||||
path: abs_path.clone(),
|
||||
}],
|
||||
final_output_json_schema: None,
|
||||
cwd: config.cwd.clone(),
|
||||
cwd: config.cwd.to_path_buf(),
|
||||
approval_policy: AskForApproval::Never,
|
||||
approvals_reviewer: None,
|
||||
sandbox_policy: SandboxPolicy::DangerFullAccess,
|
||||
|
||||
+47
-23
@@ -34,6 +34,8 @@ use crate::pager_overlay::Overlay;
|
||||
use crate::render::highlight::highlight_bash_to_lines;
|
||||
use crate::render::renderable::Renderable;
|
||||
use crate::resume_picker::SessionSelection;
|
||||
#[cfg(test)]
|
||||
use crate::test_support::PathBufExt;
|
||||
use crate::tui;
|
||||
use crate::tui::TuiEvent;
|
||||
use crate::update_action::UpdateAction;
|
||||
@@ -1029,7 +1031,7 @@ impl App {
|
||||
|
||||
async fn refresh_in_memory_config_from_disk(&mut self) -> Result<()> {
|
||||
let mut config = self
|
||||
.rebuild_config_for_cwd(self.chat_widget.config_ref().cwd.clone())
|
||||
.rebuild_config_for_cwd(self.chat_widget.config_ref().cwd.to_path_buf())
|
||||
.await?;
|
||||
self.apply_runtime_policy_overrides(&mut config);
|
||||
self.config = config;
|
||||
@@ -1528,7 +1530,7 @@ impl App {
|
||||
self.chat_widget.current_model(),
|
||||
self.chat_widget.current_service_tier(),
|
||||
),
|
||||
self.config.cwd.clone(),
|
||||
self.config.cwd.to_path_buf(),
|
||||
version,
|
||||
)
|
||||
.display_lines(width)
|
||||
@@ -1781,7 +1783,7 @@ impl App {
|
||||
cwd: self
|
||||
.thread_cwd(thread_id)
|
||||
.await
|
||||
.unwrap_or_else(|| self.config.cwd.clone()),
|
||||
.unwrap_or_else(|| self.config.cwd.to_path_buf()),
|
||||
changes: ev.changes.clone(),
|
||||
},
|
||||
)),
|
||||
@@ -2545,7 +2547,7 @@ impl App {
|
||||
chat_widget
|
||||
.maybe_prompt_windows_sandbox_enable(should_prompt_windows_sandbox_nux_at_startup);
|
||||
|
||||
let file_search = FileSearchManager::new(config.cwd.clone(), app_event_tx.clone());
|
||||
let file_search = FileSearchManager::new(config.cwd.to_path_buf(), app_event_tx.clone());
|
||||
#[cfg(not(debug_assertions))]
|
||||
let upgrade_version = crate::updates::get_upgrade_version(&config);
|
||||
|
||||
@@ -2612,7 +2614,13 @@ impl App {
|
||||
let tx = app.app_event_tx.clone();
|
||||
let logs_base_dir = app.config.codex_home.clone();
|
||||
let sandbox_policy = app.config.permissions.sandbox_policy.get().clone();
|
||||
Self::spawn_world_writable_scan(cwd, env_map, logs_base_dir, sandbox_policy, tx);
|
||||
Self::spawn_world_writable_scan(
|
||||
cwd.to_path_buf(),
|
||||
env_map,
|
||||
logs_base_dir,
|
||||
sandbox_policy,
|
||||
tx,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2815,7 +2823,7 @@ impl App {
|
||||
.await?
|
||||
{
|
||||
SessionSelection::Resume(target_session) => {
|
||||
let current_cwd = self.config.cwd.clone();
|
||||
let current_cwd = self.config.cwd.to_path_buf();
|
||||
let resume_cwd = match crate::resolve_cwd_for_resume_or_fork(
|
||||
tui,
|
||||
&self.config,
|
||||
@@ -2865,7 +2873,8 @@ impl App {
|
||||
self.shutdown_current_thread().await;
|
||||
self.config = resume_config;
|
||||
tui.set_notification_method(self.config.tui_notification_method);
|
||||
self.file_search.update_search_dir(self.config.cwd.clone());
|
||||
self.file_search
|
||||
.update_search_dir(self.config.cwd.to_path_buf());
|
||||
let init = self.chatwidget_init_for_forked_or_resumed_thread(
|
||||
tui,
|
||||
self.config.clone(),
|
||||
@@ -3192,7 +3201,8 @@ impl App {
|
||||
plugin_display_name,
|
||||
result,
|
||||
);
|
||||
if install_succeeded && self.chat_widget.config_ref().cwd == cwd {
|
||||
if install_succeeded && self.chat_widget.config_ref().cwd.as_path() == cwd.as_path()
|
||||
{
|
||||
self.fetch_plugins_list(cwd.clone());
|
||||
if should_refresh_plugin_detail {
|
||||
self.fetch_plugin_detail(
|
||||
@@ -3644,7 +3654,9 @@ impl App {
|
||||
plugin_display_name,
|
||||
result,
|
||||
);
|
||||
if uninstall_succeeded && self.chat_widget.config_ref().cwd == cwd {
|
||||
if uninstall_succeeded
|
||||
&& self.chat_widget.config_ref().cwd.as_path() == cwd.as_path()
|
||||
{
|
||||
self.fetch_plugins_list(cwd);
|
||||
}
|
||||
}
|
||||
@@ -3831,7 +3843,7 @@ impl App {
|
||||
let logs_base_dir = self.config.codex_home.clone();
|
||||
let sandbox_policy = self.config.permissions.sandbox_policy.get().clone();
|
||||
Self::spawn_world_writable_scan(
|
||||
cwd,
|
||||
cwd.to_path_buf(),
|
||||
env_map,
|
||||
logs_base_dir,
|
||||
sandbox_policy,
|
||||
@@ -4981,7 +4993,7 @@ mod tests {
|
||||
approval_policy: AskForApproval::Never,
|
||||
approvals_reviewer: ApprovalsReviewer::User,
|
||||
sandbox_policy: SandboxPolicy::new_read_only_policy(),
|
||||
cwd: PathBuf::from("/tmp/project"),
|
||||
cwd: PathBuf::from("/tmp/project").abs().to_path_buf(),
|
||||
reasoning_effort: None,
|
||||
history_log_id: 0,
|
||||
history_entry_count: 0,
|
||||
@@ -6121,7 +6133,7 @@ mod tests {
|
||||
let (mut app, mut app_event_rx, mut op_rx) = make_test_app_with_channels().await;
|
||||
let codex_home = tempdir()?;
|
||||
app.config.codex_home = codex_home.path().to_path_buf();
|
||||
let config_toml_path = AbsolutePathBuf::try_from(codex_home.path().join("config.toml"))?;
|
||||
let config_toml_path = codex_home.path().join("config.toml").abs();
|
||||
let config_toml = "approvals_reviewer = \"guardian_subagent\"\napproval_policy = \"on-request\"\nsandbox_mode = \"workspace-write\"\n\n[features]\nguardian_approval = true\n";
|
||||
std::fs::write(config_toml_path.as_path(), config_toml)?;
|
||||
let user_config = toml::from_str::<TomlValue>(config_toml)?;
|
||||
@@ -6213,7 +6225,7 @@ mod tests {
|
||||
let codex_home = tempdir()?;
|
||||
app.config.codex_home = codex_home.path().to_path_buf();
|
||||
let guardian_approvals = guardian_approvals_mode();
|
||||
let config_toml_path = AbsolutePathBuf::try_from(codex_home.path().join("config.toml"))?;
|
||||
let config_toml_path = codex_home.path().join("config.toml").abs();
|
||||
let config_toml = "approvals_reviewer = \"user\"\n";
|
||||
std::fs::write(config_toml_path.as_path(), config_toml)?;
|
||||
let user_config = toml::from_str::<TomlValue>(config_toml)?;
|
||||
@@ -6280,7 +6292,7 @@ mod tests {
|
||||
let (mut app, mut app_event_rx, mut op_rx) = make_test_app_with_channels().await;
|
||||
let codex_home = tempdir()?;
|
||||
app.config.codex_home = codex_home.path().to_path_buf();
|
||||
let config_toml_path = AbsolutePathBuf::try_from(codex_home.path().join("config.toml"))?;
|
||||
let config_toml_path = codex_home.path().join("config.toml").abs();
|
||||
let config_toml = "approvals_reviewer = \"user\"\napproval_policy = \"on-request\"\nsandbox_mode = \"workspace-write\"\n\n[features]\nguardian_approval = true\n";
|
||||
std::fs::write(config_toml_path.as_path(), config_toml)?;
|
||||
let user_config = toml::from_str::<TomlValue>(config_toml)?;
|
||||
@@ -6341,7 +6353,7 @@ mod tests {
|
||||
app.config.codex_home = codex_home.path().to_path_buf();
|
||||
let guardian_approvals = guardian_approvals_mode();
|
||||
app.active_profile = Some("guardian".to_string());
|
||||
let config_toml_path = AbsolutePathBuf::try_from(codex_home.path().join("config.toml"))?;
|
||||
let config_toml_path = codex_home.path().join("config.toml").abs();
|
||||
let config_toml = "profile = \"guardian\"\napprovals_reviewer = \"user\"\n";
|
||||
std::fs::write(config_toml_path.as_path(), config_toml)?;
|
||||
let user_config = toml::from_str::<TomlValue>(config_toml)?;
|
||||
@@ -6411,7 +6423,7 @@ mod tests {
|
||||
let codex_home = tempdir()?;
|
||||
app.config.codex_home = codex_home.path().to_path_buf();
|
||||
app.active_profile = Some("guardian".to_string());
|
||||
let config_toml_path = AbsolutePathBuf::try_from(codex_home.path().join("config.toml"))?;
|
||||
let config_toml_path = codex_home.path().join("config.toml").abs();
|
||||
let config_toml = r#"
|
||||
profile = "guardian"
|
||||
approvals_reviewer = "user"
|
||||
@@ -6499,7 +6511,7 @@ guardian_approval = true
|
||||
let codex_home = tempdir()?;
|
||||
app.config.codex_home = codex_home.path().to_path_buf();
|
||||
app.active_profile = Some("guardian".to_string());
|
||||
let config_toml_path = AbsolutePathBuf::try_from(codex_home.path().join("config.toml"))?;
|
||||
let config_toml_path = codex_home.path().join("config.toml").abs();
|
||||
let config_toml = "profile = \"guardian\"\napprovals_reviewer = \"guardian_subagent\"\n\n[features]\nguardian_approval = true\n";
|
||||
std::fs::write(config_toml_path.as_path(), config_toml)?;
|
||||
let user_config = toml::from_str::<TomlValue>(config_toml)?;
|
||||
@@ -6833,7 +6845,7 @@ guardian_approval = true
|
||||
|
||||
async fn render_clear_ui_header_after_long_transcript_for_snapshot() -> String {
|
||||
let mut app = make_test_app().await;
|
||||
app.config.cwd = PathBuf::from("/tmp/project");
|
||||
app.config.cwd = PathBuf::from("/tmp/project").abs();
|
||||
app.chat_widget.set_model("gpt-test");
|
||||
app.chat_widget
|
||||
.set_reasoning_effort(Some(ReasoningEffortConfig::High));
|
||||
@@ -6944,21 +6956,33 @@ guardian_approval = true
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
#[cfg_attr(
|
||||
target_os = "windows",
|
||||
ignore = "snapshot path rendering differs on Windows"
|
||||
)]
|
||||
async fn clear_ui_after_long_transcript_snapshots_fresh_header_only() {
|
||||
let rendered = render_clear_ui_header_after_long_transcript_for_snapshot().await;
|
||||
assert_snapshot!("clear_ui_after_long_transcript_fresh_header_only", rendered);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
#[cfg_attr(
|
||||
target_os = "windows",
|
||||
ignore = "snapshot path rendering differs on Windows"
|
||||
)]
|
||||
async fn ctrl_l_clear_ui_after_long_transcript_reuses_clear_header_snapshot() {
|
||||
let rendered = render_clear_ui_header_after_long_transcript_for_snapshot().await;
|
||||
assert_snapshot!("clear_ui_after_long_transcript_fresh_header_only", rendered);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
#[cfg_attr(
|
||||
target_os = "windows",
|
||||
ignore = "snapshot path rendering differs on Windows"
|
||||
)]
|
||||
async fn clear_ui_header_shows_fast_status_only_for_gpt54() {
|
||||
let mut app = make_test_app().await;
|
||||
app.config.cwd = PathBuf::from("/tmp/project");
|
||||
app.config.cwd = PathBuf::from("/tmp/project").abs();
|
||||
app.chat_widget.set_model("gpt-5.4");
|
||||
app.chat_widget
|
||||
.set_reasoning_effort(Some(ReasoningEffortConfig::XHigh));
|
||||
@@ -6993,7 +7017,7 @@ guardian_approval = true
|
||||
let auth_manager = codex_core::test_support::auth_manager_from_auth(
|
||||
CodexAuth::from_api_key("Test API Key"),
|
||||
);
|
||||
let file_search = FileSearchManager::new(config.cwd.clone(), app_event_tx.clone());
|
||||
let file_search = FileSearchManager::new(config.cwd.to_path_buf(), app_event_tx.clone());
|
||||
let model = codex_core::test_support::get_model_offline(config.model.as_deref());
|
||||
let session_telemetry = test_session_telemetry(&config, model.as_str());
|
||||
|
||||
@@ -7056,7 +7080,7 @@ guardian_approval = true
|
||||
let auth_manager = codex_core::test_support::auth_manager_from_auth(
|
||||
CodexAuth::from_api_key("Test API Key"),
|
||||
);
|
||||
let file_search = FileSearchManager::new(config.cwd.clone(), app_event_tx.clone());
|
||||
let file_search = FileSearchManager::new(config.cwd.to_path_buf(), app_event_tx.clone());
|
||||
let model = codex_core::test_support::get_model_offline(config.model.as_deref());
|
||||
let session_telemetry = test_session_telemetry(&config, model.as_str());
|
||||
|
||||
@@ -7555,7 +7579,7 @@ guardian_approval = true
|
||||
}),
|
||||
});
|
||||
|
||||
assert_eq!(app.chat_widget.config_ref().cwd, next_cwd);
|
||||
assert_eq!(app.chat_widget.config_ref().cwd.to_path_buf(), next_cwd);
|
||||
assert_eq!(app.config.cwd, original_cwd);
|
||||
|
||||
app.refresh_in_memory_config_from_disk().await?;
|
||||
@@ -7575,7 +7599,7 @@ guardian_approval = true
|
||||
let current_cwd = current_config.cwd.clone();
|
||||
|
||||
let resume_config = app
|
||||
.rebuild_config_for_resume_or_fallback(¤t_cwd, current_cwd.clone())
|
||||
.rebuild_config_for_resume_or_fallback(¤t_cwd, current_cwd.to_path_buf())
|
||||
.await?;
|
||||
|
||||
assert_eq!(resume_config, current_config);
|
||||
|
||||
@@ -163,6 +163,7 @@ use codex_terminal_detection::Multiplexer;
|
||||
use codex_terminal_detection::TerminalInfo;
|
||||
use codex_terminal_detection::TerminalName;
|
||||
use codex_terminal_detection::terminal_info;
|
||||
use codex_utils_absolute_path::AbsolutePathBuf;
|
||||
use codex_utils_sleep_inhibitor::SleepInhibitor;
|
||||
use crossterm::event::KeyCode;
|
||||
use crossterm::event::KeyEvent;
|
||||
@@ -1416,7 +1417,12 @@ impl ChatWidget {
|
||||
self.forked_from = event.forked_from_id;
|
||||
self.current_rollout_path = event.rollout_path.clone();
|
||||
self.current_cwd = Some(event.cwd.clone());
|
||||
self.config.cwd = event.cwd.clone();
|
||||
match AbsolutePathBuf::try_from(event.cwd.clone()) {
|
||||
Ok(cwd) => self.config.cwd = cwd,
|
||||
Err(err) => {
|
||||
tracing::warn!(path = %event.cwd.display(), %err, "session cwd should be absolute");
|
||||
}
|
||||
}
|
||||
if let Err(err) = self
|
||||
.config
|
||||
.permissions
|
||||
@@ -3475,13 +3481,13 @@ impl ChatWidget {
|
||||
id: ev.call_id,
|
||||
reason: ev.reason,
|
||||
changes: ev.changes.clone(),
|
||||
cwd: self.config.cwd.clone(),
|
||||
cwd: self.config.cwd.to_path_buf(),
|
||||
};
|
||||
self.bottom_pane
|
||||
.push_approval_request(request, &self.config.features);
|
||||
self.request_redraw();
|
||||
self.notify(Notification::EditApprovalRequested {
|
||||
cwd: self.config.cwd.clone(),
|
||||
cwd: self.config.cwd.to_path_buf(),
|
||||
changes: ev.changes.keys().cloned().collect(),
|
||||
});
|
||||
}
|
||||
@@ -3711,7 +3717,7 @@ impl ChatWidget {
|
||||
|
||||
let active_cell = Some(Self::placeholder_session_header_cell(&config));
|
||||
|
||||
let current_cwd = Some(config.cwd.clone());
|
||||
let current_cwd = Some(config.cwd.to_path_buf());
|
||||
let queued_message_edit_binding = queued_message_edit_binding_for_terminal(terminal_info());
|
||||
let mut widget = Self {
|
||||
app_event_tx: app_event_tx.clone(),
|
||||
@@ -3914,7 +3920,7 @@ impl ChatWidget {
|
||||
};
|
||||
|
||||
let active_cell = Some(Self::placeholder_session_header_cell(&config));
|
||||
let current_cwd = Some(config.cwd.clone());
|
||||
let current_cwd = Some(config.cwd.to_path_buf());
|
||||
|
||||
let queued_message_edit_binding = queued_message_edit_binding_for_terminal(terminal_info());
|
||||
let mut widget = Self {
|
||||
@@ -4537,7 +4543,15 @@ impl ChatWidget {
|
||||
self.app_event_tx.send(AppEvent::ForkCurrentSession);
|
||||
}
|
||||
SlashCommand::Init => {
|
||||
let init_target = self.config.cwd.join(DEFAULT_PROJECT_DOC_FILENAME);
|
||||
let init_target = match self.config.cwd.join(DEFAULT_PROJECT_DOC_FILENAME) {
|
||||
Ok(path) => path,
|
||||
Err(err) => {
|
||||
self.add_error_message(format!(
|
||||
"Failed to prepare {DEFAULT_PROJECT_DOC_FILENAME}: {err}",
|
||||
));
|
||||
return;
|
||||
}
|
||||
};
|
||||
if init_target.exists() {
|
||||
let message = format!(
|
||||
"{DEFAULT_PROJECT_DOC_FILENAME} already exists here. Skipping /init to avoid overwriting it."
|
||||
@@ -5268,7 +5282,7 @@ impl ChatWidget {
|
||||
let service_tier = self.config.service_tier.map(Some);
|
||||
let op = Op::UserTurn {
|
||||
items,
|
||||
cwd: self.config.cwd.clone(),
|
||||
cwd: self.config.cwd.to_path_buf(),
|
||||
approval_policy: self.config.permissions.approval_policy.value(),
|
||||
approvals_reviewer: None,
|
||||
sandbox_policy: self.config.permissions.sandbox_policy.get().clone(),
|
||||
@@ -8409,7 +8423,7 @@ impl ChatWidget {
|
||||
placeholder_style,
|
||||
/*reasoning_effort*/ None,
|
||||
/*show_fast_status*/ false,
|
||||
config.cwd.clone(),
|
||||
config.cwd.to_path_buf(),
|
||||
CODEX_CLI_VERSION,
|
||||
))
|
||||
}
|
||||
@@ -9158,7 +9172,7 @@ impl ChatWidget {
|
||||
name: "Review against a base branch".to_string(),
|
||||
description: Some("(PR Style)".into()),
|
||||
actions: vec![Box::new({
|
||||
let cwd = self.config.cwd.clone();
|
||||
let cwd = self.config.cwd.to_path_buf();
|
||||
move |tx| {
|
||||
tx.send(AppEvent::OpenReviewBranchPicker(cwd.clone()));
|
||||
}
|
||||
@@ -9185,7 +9199,7 @@ impl ChatWidget {
|
||||
items.push(SelectionItem {
|
||||
name: "Review a commit".to_string(),
|
||||
actions: vec![Box::new({
|
||||
let cwd = self.config.cwd.clone();
|
||||
let cwd = self.config.cwd.to_path_buf();
|
||||
move |tx| {
|
||||
tx.send(AppEvent::OpenReviewCommitPicker(cwd.clone()));
|
||||
}
|
||||
|
||||
@@ -159,11 +159,11 @@ impl ChatWidget {
|
||||
cwd: PathBuf,
|
||||
result: Result<PluginListResponse, String>,
|
||||
) {
|
||||
if self.plugins_fetch_state.in_flight_cwd.as_ref() == Some(&cwd) {
|
||||
if self.plugins_fetch_state.in_flight_cwd.as_deref() == Some(cwd.as_path()) {
|
||||
self.plugins_fetch_state.in_flight_cwd = None;
|
||||
}
|
||||
|
||||
if self.config.cwd != cwd {
|
||||
if self.config.cwd.as_path() != cwd.as_path() {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -191,13 +191,13 @@ impl ChatWidget {
|
||||
}
|
||||
|
||||
fn prefetch_plugins(&mut self) {
|
||||
let cwd = self.config.cwd.clone();
|
||||
if self.plugins_fetch_state.in_flight_cwd.as_ref() == Some(&cwd) {
|
||||
let cwd = self.config.cwd.to_path_buf();
|
||||
if self.plugins_fetch_state.in_flight_cwd.as_deref() == Some(cwd.as_path()) {
|
||||
return;
|
||||
}
|
||||
|
||||
self.plugins_fetch_state.in_flight_cwd = Some(cwd.clone());
|
||||
if self.plugins_fetch_state.cache_cwd.as_ref() != Some(&cwd) {
|
||||
if self.plugins_fetch_state.cache_cwd.as_deref() != Some(cwd.as_path()) {
|
||||
self.plugins_cache = PluginsCacheState::Loading;
|
||||
}
|
||||
|
||||
@@ -205,7 +205,7 @@ impl ChatWidget {
|
||||
}
|
||||
|
||||
fn plugins_cache_for_current_cwd(&self) -> PluginsCacheState {
|
||||
if self.plugins_fetch_state.cache_cwd.as_ref() == Some(&self.config.cwd) {
|
||||
if self.plugins_fetch_state.cache_cwd.as_deref() == Some(self.config.cwd.as_path()) {
|
||||
self.plugins_cache.clone()
|
||||
} else {
|
||||
PluginsCacheState::Uninitialized
|
||||
@@ -253,7 +253,7 @@ impl ChatWidget {
|
||||
cwd: PathBuf,
|
||||
result: Result<PluginReadResponse, String>,
|
||||
) {
|
||||
if self.config.cwd != cwd {
|
||||
if self.config.cwd.as_path() != cwd.as_path() {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -288,7 +288,7 @@ impl ChatWidget {
|
||||
plugin_display_name: String,
|
||||
result: Result<PluginInstallResponse, String>,
|
||||
) -> bool {
|
||||
if self.config.cwd != cwd {
|
||||
if self.config.cwd.as_path() != cwd.as_path() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -346,7 +346,7 @@ impl ChatWidget {
|
||||
plugin_display_name: String,
|
||||
result: Result<PluginUninstallResponse, String>,
|
||||
) {
|
||||
if self.config.cwd != cwd {
|
||||
if self.config.cwd.as_path() != cwd.as_path() {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -676,7 +676,7 @@ impl ChatWidget {
|
||||
..Default::default()
|
||||
}];
|
||||
if let Some(plugins_response) = plugins_response.cloned() {
|
||||
let cwd = self.config.cwd.clone();
|
||||
let cwd = self.config.cwd.to_path_buf();
|
||||
items.push(SelectionItem {
|
||||
name: "Back to plugins".to_string(),
|
||||
description: Some("Return to the plugin list.".to_string()),
|
||||
@@ -770,7 +770,7 @@ impl ChatWidget {
|
||||
"{display_name} {} {} {}",
|
||||
plugin.id, plugin.name, marketplace_label
|
||||
);
|
||||
let cwd = self.config.cwd.clone();
|
||||
let cwd = self.config.cwd.to_path_buf();
|
||||
let plugin_display_name = display_name.clone();
|
||||
let marketplace_path = marketplace.path.clone();
|
||||
let plugin_name = plugin.name.clone();
|
||||
@@ -861,7 +861,7 @@ impl ChatWidget {
|
||||
header.push(Line::from(description.dim()));
|
||||
}
|
||||
|
||||
let cwd = self.config.cwd.clone();
|
||||
let cwd = self.config.cwd.to_path_buf();
|
||||
let plugins_response = plugins_response.clone();
|
||||
let mut items = vec![SelectionItem {
|
||||
name: "Back to plugins".to_string(),
|
||||
@@ -877,7 +877,7 @@ impl ChatWidget {
|
||||
}];
|
||||
|
||||
if plugin.summary.installed {
|
||||
let uninstall_cwd = self.config.cwd.clone();
|
||||
let uninstall_cwd = self.config.cwd.to_path_buf();
|
||||
let plugin_id = plugin.summary.id.clone();
|
||||
let plugin_display_name = display_name;
|
||||
items.push(SelectionItem {
|
||||
@@ -906,7 +906,7 @@ impl ChatWidget {
|
||||
..Default::default()
|
||||
});
|
||||
} else {
|
||||
let install_cwd = self.config.cwd.clone();
|
||||
let install_cwd = self.config.cwd.to_path_buf();
|
||||
let marketplace_path = plugin.marketplace_path.clone();
|
||||
let plugin_name = plugin.summary.name.clone();
|
||||
let plugin_display_name = display_name;
|
||||
|
||||
@@ -326,7 +326,9 @@ impl ChatWidget {
|
||||
}
|
||||
|
||||
fn status_line_cwd(&self) -> &Path {
|
||||
self.current_cwd.as_ref().unwrap_or(&self.config.cwd)
|
||||
self.current_cwd
|
||||
.as_deref()
|
||||
.unwrap_or(self.config.cwd.as_path())
|
||||
}
|
||||
|
||||
/// Resolves the project root associated with `cwd`.
|
||||
|
||||
@@ -16,6 +16,8 @@ use crate::bottom_pane::MentionBinding;
|
||||
use crate::chatwidget::realtime::RealtimeConversationPhase;
|
||||
use crate::history_cell::UserHistoryCell;
|
||||
use crate::test_backend::VT100Backend;
|
||||
use crate::test_support::PathBufExt;
|
||||
use crate::test_support::test_path_display;
|
||||
use crate::tui::FrameRequester;
|
||||
use assert_matches::assert_matches;
|
||||
use codex_app_server_protocol::AppSummary;
|
||||
@@ -434,10 +436,10 @@ async fn session_configured_syncs_widget_config_permissions_and_cwd() {
|
||||
.sandbox_policy
|
||||
.set(SandboxPolicy::new_workspace_write_policy())
|
||||
.expect("set sandbox policy");
|
||||
chat.config.cwd = PathBuf::from("/home/user/main");
|
||||
chat.config.cwd = PathBuf::from("/home/user/main").abs();
|
||||
|
||||
let expected_sandbox = SandboxPolicy::new_read_only_policy();
|
||||
let expected_cwd = PathBuf::from("/home/user/sub-agent");
|
||||
let expected_cwd = PathBuf::from("/home/user/sub-agent").abs();
|
||||
let configured = codex_protocol::protocol::SessionConfiguredEvent {
|
||||
session_id: ThreadId::new(),
|
||||
forked_from_id: None,
|
||||
@@ -448,7 +450,7 @@ async fn session_configured_syncs_widget_config_permissions_and_cwd() {
|
||||
approval_policy: AskForApproval::Never,
|
||||
approvals_reviewer: ApprovalsReviewer::User,
|
||||
sandbox_policy: expected_sandbox.clone(),
|
||||
cwd: expected_cwd.clone(),
|
||||
cwd: expected_cwd.to_path_buf(),
|
||||
reasoning_effort: Some(ReasoningEffortConfig::default()),
|
||||
history_log_id: 0,
|
||||
history_entry_count: 0,
|
||||
@@ -5801,7 +5803,7 @@ async fn slash_init_skips_when_project_doc_exists() {
|
||||
let tempdir = tempdir().unwrap();
|
||||
let existing_path = tempdir.path().join(DEFAULT_PROJECT_DOC_FILENAME);
|
||||
std::fs::write(&existing_path, "existing instructions").unwrap();
|
||||
chat.config.cwd = tempdir.path().to_path_buf();
|
||||
chat.config.cwd = tempdir.path().to_path_buf().abs();
|
||||
|
||||
chat.dispatch_command(SlashCommand::Init);
|
||||
|
||||
@@ -6822,13 +6824,17 @@ async fn custom_prompt_enter_empty_does_not_send() {
|
||||
#[tokio::test]
|
||||
async fn view_image_tool_call_adds_history_cell() {
|
||||
let (mut chat, mut rx, _op_rx) = make_chatwidget_manual(None).await;
|
||||
let image_path = chat.config.cwd.join("example.png");
|
||||
let image_path = chat
|
||||
.config
|
||||
.cwd
|
||||
.join("example.png")
|
||||
.expect("absolute image path");
|
||||
|
||||
chat.handle_codex_event(Event {
|
||||
id: "sub-image".into(),
|
||||
msg: EventMsg::ViewImageToolCall(ViewImageToolCallEvent {
|
||||
call_id: "call-image".into(),
|
||||
path: image_path,
|
||||
path: image_path.to_path_buf(),
|
||||
}),
|
||||
});
|
||||
|
||||
@@ -7118,12 +7124,10 @@ fn strip_osc8_for_snapshot(text: &str) -> String {
|
||||
}
|
||||
|
||||
fn plugins_test_absolute_path(path: &str) -> AbsolutePathBuf {
|
||||
AbsolutePathBuf::try_from(
|
||||
std::env::temp_dir()
|
||||
.join("codex-plugin-menu-tests")
|
||||
.join(path),
|
||||
)
|
||||
.expect("expected absolute test path")
|
||||
std::env::temp_dir()
|
||||
.join("codex-plugin-menu-tests")
|
||||
.join(path)
|
||||
.abs()
|
||||
}
|
||||
|
||||
fn plugins_test_interface(
|
||||
@@ -7205,7 +7209,7 @@ fn plugins_test_response(marketplaces: Vec<PluginMarketplaceEntry>) -> PluginLis
|
||||
|
||||
fn render_loaded_plugins_popup(chat: &mut ChatWidget, response: PluginListResponse) -> String {
|
||||
let cwd = chat.config.cwd.clone();
|
||||
chat.on_plugins_loaded(cwd, Ok(response));
|
||||
chat.on_plugins_loaded(cwd.to_path_buf(), Ok(response));
|
||||
chat.add_plugins_output();
|
||||
render_bottom_popup(chat, 100)
|
||||
}
|
||||
@@ -7356,10 +7360,10 @@ async fn plugin_detail_popup_snapshot_shows_install_actions_and_capability_summa
|
||||
summary.clone(),
|
||||
])]);
|
||||
let cwd = chat.config.cwd.clone();
|
||||
chat.on_plugins_loaded(cwd.clone(), Ok(response));
|
||||
chat.on_plugins_loaded(cwd.to_path_buf(), Ok(response));
|
||||
chat.add_plugins_output();
|
||||
chat.on_plugin_detail_loaded(
|
||||
cwd,
|
||||
cwd.to_path_buf(),
|
||||
Ok(PluginReadResponse {
|
||||
plugin: plugins_test_detail(
|
||||
summary,
|
||||
@@ -7396,10 +7400,10 @@ async fn plugin_detail_popup_hides_disclosure_for_installed_plugins() {
|
||||
summary.clone(),
|
||||
])]);
|
||||
let cwd = chat.config.cwd.clone();
|
||||
chat.on_plugins_loaded(cwd.clone(), Ok(response));
|
||||
chat.on_plugins_loaded(cwd.to_path_buf(), Ok(response));
|
||||
chat.add_plugins_output();
|
||||
chat.on_plugin_detail_loaded(
|
||||
cwd,
|
||||
cwd.to_path_buf(),
|
||||
Ok(PluginReadResponse {
|
||||
plugin: plugins_test_detail(
|
||||
summary,
|
||||
@@ -7486,7 +7490,7 @@ async fn plugins_popup_refresh_replaces_selection_with_first_row() {
|
||||
),
|
||||
])]);
|
||||
let cwd = chat.config.cwd.clone();
|
||||
chat.on_plugins_loaded(cwd, Ok(refreshed));
|
||||
chat.on_plugins_loaded(cwd.to_path_buf(), Ok(refreshed));
|
||||
|
||||
let after = render_bottom_popup(&chat, 100);
|
||||
assert!(
|
||||
@@ -7555,7 +7559,7 @@ async fn plugins_popup_refreshes_installed_counts_after_install() {
|
||||
),
|
||||
])]);
|
||||
let cwd = chat.config.cwd.clone();
|
||||
chat.on_plugins_loaded(cwd, Ok(refreshed));
|
||||
chat.on_plugins_loaded(cwd.to_path_buf(), Ok(refreshed));
|
||||
|
||||
let after = render_bottom_popup(&chat, 100);
|
||||
assert!(
|
||||
@@ -8271,8 +8275,7 @@ async fn apps_initial_load_applies_enabled_state_from_config() {
|
||||
chat.bottom_pane.set_connectors_enabled(true);
|
||||
|
||||
let temp = tempdir().expect("tempdir");
|
||||
let config_toml_path =
|
||||
AbsolutePathBuf::try_from(temp.path().join("config.toml")).expect("absolute config path");
|
||||
let config_toml_path = temp.path().join("config.toml").abs();
|
||||
let user_config = toml::from_str::<TomlValue>(
|
||||
"[apps.connector_1]\nenabled = false\ndisabled_reason = \"user\"\n",
|
||||
)
|
||||
@@ -8336,8 +8339,7 @@ async fn apps_initial_load_applies_enabled_state_from_requirements_with_user_ove
|
||||
..Default::default()
|
||||
};
|
||||
let temp = tempdir().expect("tempdir");
|
||||
let config_toml_path =
|
||||
AbsolutePathBuf::try_from(temp.path().join("config.toml")).expect("absolute config path");
|
||||
let config_toml_path = temp.path().join("config.toml").abs();
|
||||
chat.config.config_layer_stack =
|
||||
ConfigLayerStack::new(Vec::new(), ConfigRequirements::default(), requirements)
|
||||
.expect("requirements stack")
|
||||
@@ -8912,7 +8914,7 @@ async fn preset_matching_accepts_workspace_write_with_extra_roots() {
|
||||
.find(|p| p.id == "auto")
|
||||
.expect("auto preset exists");
|
||||
let current_sandbox = SandboxPolicy::WorkspaceWrite {
|
||||
writable_roots: vec![AbsolutePathBuf::try_from("C:\\extra").unwrap()],
|
||||
writable_roots: vec![PathBuf::from("C:\\extra").abs()],
|
||||
read_only_access: Default::default(),
|
||||
network_access: false,
|
||||
exclude_tmpdir_env_var: false,
|
||||
@@ -9716,8 +9718,7 @@ async fn permissions_selection_marks_guardian_approvals_current_with_custom_work
|
||||
.features
|
||||
.set_enabled(Feature::GuardianApproval, true);
|
||||
|
||||
let extra_root = AbsolutePathBuf::try_from("/tmp/guardian-approvals-extra")
|
||||
.expect("absolute extra writable root");
|
||||
let extra_root = PathBuf::from("/tmp/guardian-approvals-extra").abs();
|
||||
|
||||
chat.handle_codex_event(Event {
|
||||
id: "session-configured-custom-workspace".to_string(),
|
||||
@@ -11627,7 +11628,7 @@ async fn default_terminal_title_refreshes_when_spinner_state_changes() {
|
||||
let cwd = chat
|
||||
.current_cwd
|
||||
.clone()
|
||||
.unwrap_or_else(|| chat.config.cwd.clone());
|
||||
.unwrap_or_else(|| chat.config.cwd.to_path_buf());
|
||||
let project = get_git_repo_root(&cwd)
|
||||
.map(|root| {
|
||||
root.file_name()
|
||||
@@ -11866,7 +11867,7 @@ async fn status_line_fast_mode_footer_snapshot() {
|
||||
#[tokio::test]
|
||||
async fn status_line_model_with_reasoning_includes_fast_for_gpt54_only() {
|
||||
let (mut chat, _rx, _op_rx) = make_chatwidget_manual(Some("gpt-5.4")).await;
|
||||
chat.config.cwd = PathBuf::from("/tmp/project");
|
||||
chat.config.cwd = PathBuf::from("/tmp/project").abs();
|
||||
chat.config.tui_status_line = Some(vec![
|
||||
"model-with-reasoning".to_string(),
|
||||
"context-remaining".to_string(),
|
||||
@@ -11876,10 +11877,11 @@ async fn status_line_model_with_reasoning_includes_fast_for_gpt54_only() {
|
||||
chat.set_service_tier(Some(ServiceTier::Fast));
|
||||
set_chatgpt_auth(&mut chat);
|
||||
chat.refresh_status_surfaces();
|
||||
let test_cwd = test_path_display("/tmp/project");
|
||||
|
||||
assert_eq!(
|
||||
status_line_text(&chat),
|
||||
Some("gpt-5.4 xhigh fast · 100% left · /tmp/project".to_string())
|
||||
Some(format!("gpt-5.4 xhigh fast · 100% left · {test_cwd}"))
|
||||
);
|
||||
|
||||
chat.set_model("gpt-5.3-codex");
|
||||
@@ -11887,18 +11889,22 @@ async fn status_line_model_with_reasoning_includes_fast_for_gpt54_only() {
|
||||
|
||||
assert_eq!(
|
||||
status_line_text(&chat),
|
||||
Some("gpt-5.3-codex xhigh · 100% left · /tmp/project".to_string())
|
||||
Some(format!("gpt-5.3-codex xhigh · 100% left · {test_cwd}"))
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
#[cfg_attr(
|
||||
target_os = "windows",
|
||||
ignore = "snapshot path rendering differs on Windows"
|
||||
)]
|
||||
async fn status_line_model_with_reasoning_fast_footer_snapshot() {
|
||||
use ratatui::Terminal;
|
||||
use ratatui::backend::TestBackend;
|
||||
|
||||
let (mut chat, _rx, _op_rx) = make_chatwidget_manual(Some("gpt-5.4")).await;
|
||||
chat.show_welcome_banner = false;
|
||||
chat.config.cwd = PathBuf::from("/tmp/project");
|
||||
chat.config.cwd = PathBuf::from("/tmp/project").abs();
|
||||
chat.config.tui_status_line = Some(vec![
|
||||
"model-with-reasoning".to_string(),
|
||||
"context-remaining".to_string(),
|
||||
|
||||
@@ -27,6 +27,8 @@ use crate::render::line_utils::push_owned_lines;
|
||||
use crate::render::renderable::Renderable;
|
||||
use crate::style::proposed_plan_style;
|
||||
use crate::style::user_message_style;
|
||||
#[cfg(test)]
|
||||
use crate::test_support::PathBufExt;
|
||||
use crate::text_formatting::format_and_truncate_tool_result;
|
||||
use crate::text_formatting::truncate_text;
|
||||
use crate::tooltips;
|
||||
@@ -1137,7 +1139,7 @@ pub(crate) fn new_session_info(
|
||||
model.clone(),
|
||||
reasoning_effort,
|
||||
show_fast_status,
|
||||
config.cwd.clone(),
|
||||
config.cwd.to_path_buf(),
|
||||
CODEX_CLI_VERSION,
|
||||
);
|
||||
let mut parts: Vec<Box<dyn HistoryCell>> = vec![Box::new(header)];
|
||||
@@ -2654,7 +2656,7 @@ mod tests {
|
||||
approval_policy: AskForApproval::Never,
|
||||
approvals_reviewer: codex_protocol::config_types::ApprovalsReviewer::User,
|
||||
sandbox_policy: SandboxPolicy::new_read_only_policy(),
|
||||
cwd: PathBuf::from("/tmp/project"),
|
||||
cwd: PathBuf::from("/tmp/project").abs().to_path_buf(),
|
||||
reasoning_effort: None,
|
||||
history_log_id: 0,
|
||||
history_entry_count: 0,
|
||||
@@ -2768,9 +2770,13 @@ mod tests {
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
#[cfg_attr(
|
||||
target_os = "windows",
|
||||
ignore = "snapshot path rendering differs on Windows"
|
||||
)]
|
||||
async fn session_info_availability_nux_tooltip_snapshot() {
|
||||
let mut config = test_config().await;
|
||||
config.cwd = PathBuf::from("/tmp/project");
|
||||
config.cwd = PathBuf::from("/tmp/project").abs();
|
||||
let cell = new_session_info(
|
||||
&config,
|
||||
"gpt-5",
|
||||
|
||||
@@ -251,6 +251,8 @@ mod wrapping;
|
||||
|
||||
#[cfg(test)]
|
||||
pub mod test_backend;
|
||||
#[cfg(test)]
|
||||
pub(crate) mod test_support;
|
||||
|
||||
use crate::onboarding::onboarding_screen::OnboardingScreenArgs;
|
||||
use crate::onboarding::onboarding_screen::run_onboarding_app;
|
||||
|
||||
@@ -82,7 +82,7 @@ impl OnboardingScreen {
|
||||
auth_manager,
|
||||
config,
|
||||
} = args;
|
||||
let cwd = config.cwd.clone();
|
||||
let cwd = config.cwd.to_path_buf();
|
||||
let forced_chatgpt_workspace_id = config.forced_chatgpt_workspace_id.clone();
|
||||
let forced_login_method = config.forced_login_method;
|
||||
let codex_home = config.codex_home.clone();
|
||||
|
||||
@@ -256,7 +256,7 @@ impl StatusHistoryCell {
|
||||
Self {
|
||||
model_name,
|
||||
model_details,
|
||||
directory: config.cwd.clone(),
|
||||
directory: config.cwd.to_path_buf(),
|
||||
permissions,
|
||||
agents_summary,
|
||||
collaboration_mode: collaboration_mode.map(ToString::to_string),
|
||||
|
||||
@@ -46,7 +46,7 @@ pub(crate) fn compose_agents_summary(config: &Config) -> String {
|
||||
.map(|name| name.to_string_lossy().to_string())
|
||||
.unwrap_or_else(|| "<unknown>".to_string());
|
||||
let display = if let Some(parent) = p.parent() {
|
||||
if parent == config.cwd {
|
||||
if parent == config.cwd.as_path() {
|
||||
file_name.clone()
|
||||
} else {
|
||||
let mut cur = config.cwd.as_path();
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
use super::new_status_output;
|
||||
use super::rate_limit_snapshot_display;
|
||||
use crate::history_cell::HistoryCell;
|
||||
use crate::test_support::PathBufExt;
|
||||
use chrono::Duration as ChronoDuration;
|
||||
use chrono::TimeZone;
|
||||
use chrono::Utc;
|
||||
@@ -109,7 +110,7 @@ async fn status_snapshot_includes_reasoning_details() {
|
||||
})
|
||||
.expect("set sandbox policy");
|
||||
|
||||
config.cwd = PathBuf::from("/workspace/tests");
|
||||
config.cwd = PathBuf::from("/workspace/tests").abs();
|
||||
|
||||
let auth_manager = test_auth_manager(&config);
|
||||
let usage = TokenUsage {
|
||||
@@ -193,7 +194,7 @@ async fn status_permissions_non_default_workspace_write_is_custom() {
|
||||
exclude_slash_tmp: false,
|
||||
})
|
||||
.expect("set sandbox policy");
|
||||
config.cwd = PathBuf::from("/workspace/tests");
|
||||
config.cwd = PathBuf::from("/workspace/tests").abs();
|
||||
|
||||
let auth_manager = test_auth_manager(&config);
|
||||
let usage = TokenUsage::default();
|
||||
@@ -242,7 +243,7 @@ async fn status_snapshot_includes_forked_from() {
|
||||
let mut config = test_config(&temp_home).await;
|
||||
config.model = Some("gpt-5.1-codex-max".to_string());
|
||||
config.model_provider_id = "openai".to_string();
|
||||
config.cwd = PathBuf::from("/workspace/tests");
|
||||
config.cwd = PathBuf::from("/workspace/tests").abs();
|
||||
|
||||
let auth_manager = test_auth_manager(&config);
|
||||
let usage = TokenUsage {
|
||||
@@ -296,7 +297,7 @@ async fn status_snapshot_includes_monthly_limit() {
|
||||
let mut config = test_config(&temp_home).await;
|
||||
config.model = Some("gpt-5.1-codex-max".to_string());
|
||||
config.model_provider_id = "openai".to_string();
|
||||
config.cwd = PathBuf::from("/workspace/tests");
|
||||
config.cwd = PathBuf::from("/workspace/tests").abs();
|
||||
|
||||
let auth_manager = test_auth_manager(&config);
|
||||
let usage = TokenUsage {
|
||||
@@ -549,7 +550,7 @@ async fn status_card_token_usage_excludes_cached_tokens() {
|
||||
let temp_home = TempDir::new().expect("temp home");
|
||||
let mut config = test_config(&temp_home).await;
|
||||
config.model = Some("gpt-5.1-codex-max".to_string());
|
||||
config.cwd = PathBuf::from("/workspace/tests");
|
||||
config.cwd = PathBuf::from("/workspace/tests").abs();
|
||||
|
||||
let auth_manager = test_auth_manager(&config);
|
||||
let usage = TokenUsage {
|
||||
@@ -597,7 +598,7 @@ async fn status_snapshot_truncates_in_narrow_terminal() {
|
||||
config.model = Some("gpt-5.1-codex-max".to_string());
|
||||
config.model_provider_id = "openai".to_string();
|
||||
config.model_reasoning_summary = Some(ReasoningSummary::Detailed);
|
||||
config.cwd = PathBuf::from("/workspace/tests");
|
||||
config.cwd = PathBuf::from("/workspace/tests").abs();
|
||||
|
||||
let auth_manager = test_auth_manager(&config);
|
||||
let usage = TokenUsage {
|
||||
@@ -660,7 +661,7 @@ async fn status_snapshot_shows_missing_limits_message() {
|
||||
let temp_home = TempDir::new().expect("temp home");
|
||||
let mut config = test_config(&temp_home).await;
|
||||
config.model = Some("gpt-5.1-codex-max".to_string());
|
||||
config.cwd = PathBuf::from("/workspace/tests");
|
||||
config.cwd = PathBuf::from("/workspace/tests").abs();
|
||||
|
||||
let auth_manager = test_auth_manager(&config);
|
||||
let usage = TokenUsage {
|
||||
@@ -708,7 +709,7 @@ async fn status_snapshot_includes_credits_and_limits() {
|
||||
let temp_home = TempDir::new().expect("temp home");
|
||||
let mut config = test_config(&temp_home).await;
|
||||
config.model = Some("gpt-5.1-codex".to_string());
|
||||
config.cwd = PathBuf::from("/workspace/tests");
|
||||
config.cwd = PathBuf::from("/workspace/tests").abs();
|
||||
|
||||
let auth_manager = test_auth_manager(&config);
|
||||
let usage = TokenUsage {
|
||||
@@ -777,7 +778,7 @@ async fn status_snapshot_shows_empty_limits_message() {
|
||||
let temp_home = TempDir::new().expect("temp home");
|
||||
let mut config = test_config(&temp_home).await;
|
||||
config.model = Some("gpt-5.1-codex-max".to_string());
|
||||
config.cwd = PathBuf::from("/workspace/tests");
|
||||
config.cwd = PathBuf::from("/workspace/tests").abs();
|
||||
|
||||
let auth_manager = test_auth_manager(&config);
|
||||
let usage = TokenUsage {
|
||||
@@ -834,7 +835,7 @@ async fn status_snapshot_shows_stale_limits_message() {
|
||||
let temp_home = TempDir::new().expect("temp home");
|
||||
let mut config = test_config(&temp_home).await;
|
||||
config.model = Some("gpt-5.1-codex-max".to_string());
|
||||
config.cwd = PathBuf::from("/workspace/tests");
|
||||
config.cwd = PathBuf::from("/workspace/tests").abs();
|
||||
|
||||
let auth_manager = test_auth_manager(&config);
|
||||
let usage = TokenUsage {
|
||||
@@ -900,7 +901,7 @@ async fn status_snapshot_cached_limits_hide_credits_without_flag() {
|
||||
let temp_home = TempDir::new().expect("temp home");
|
||||
let mut config = test_config(&temp_home).await;
|
||||
config.model = Some("gpt-5.1-codex".to_string());
|
||||
config.cwd = PathBuf::from("/workspace/tests");
|
||||
config.cwd = PathBuf::from("/workspace/tests").abs();
|
||||
|
||||
let auth_manager = test_auth_manager(&config);
|
||||
let usage = TokenUsage {
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
use codex_utils_absolute_path::AbsolutePathBuf;
|
||||
use std::path::Path;
|
||||
use std::path::PathBuf;
|
||||
|
||||
pub(crate) trait PathExt {
|
||||
fn abs(&self) -> AbsolutePathBuf;
|
||||
}
|
||||
|
||||
impl PathExt for Path {
|
||||
fn abs(&self) -> AbsolutePathBuf {
|
||||
AbsolutePathBuf::try_from(self.to_path_buf())
|
||||
.unwrap_or_else(|_| panic!("path should already be absolute"))
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) trait PathBufExt {
|
||||
fn abs(&self) -> AbsolutePathBuf;
|
||||
}
|
||||
|
||||
impl PathBufExt for PathBuf {
|
||||
fn abs(&self) -> AbsolutePathBuf {
|
||||
self.as_path().abs()
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn test_path_display(path: &str) -> String {
|
||||
Path::new(path).abs().display().to_string()
|
||||
}
|
||||
@@ -42,6 +42,8 @@ use crate::read_session_model;
|
||||
use crate::render::highlight::highlight_bash_to_lines;
|
||||
use crate::render::renderable::Renderable;
|
||||
use crate::resume_picker::SessionSelection;
|
||||
#[cfg(test)]
|
||||
use crate::test_support::PathBufExt;
|
||||
use crate::tui;
|
||||
use crate::tui::TuiEvent;
|
||||
use crate::update_action::UpdateAction;
|
||||
@@ -1039,7 +1041,7 @@ impl App {
|
||||
|
||||
async fn refresh_in_memory_config_from_disk(&mut self) -> Result<()> {
|
||||
let mut config = self
|
||||
.rebuild_config_for_cwd(self.chat_widget.config_ref().cwd.clone())
|
||||
.rebuild_config_for_cwd(self.chat_widget.config_ref().cwd.to_path_buf())
|
||||
.await?;
|
||||
self.apply_runtime_policy_overrides(&mut config);
|
||||
self.config = config;
|
||||
@@ -1406,7 +1408,7 @@ impl App {
|
||||
self.chat_widget.current_model(),
|
||||
self.chat_widget.current_service_tier(),
|
||||
),
|
||||
self.config.cwd.clone(),
|
||||
self.config.cwd.to_path_buf(),
|
||||
version,
|
||||
)
|
||||
.display_lines(width)
|
||||
@@ -1702,7 +1704,7 @@ impl App {
|
||||
cwd: self
|
||||
.thread_cwd(thread_id)
|
||||
.await
|
||||
.unwrap_or_else(|| self.config.cwd.clone()),
|
||||
.unwrap_or_else(|| self.config.cwd.to_path_buf()),
|
||||
changes: HashMap::new(),
|
||||
}),
|
||||
),
|
||||
@@ -3077,7 +3079,7 @@ impl App {
|
||||
chat_widget
|
||||
.maybe_prompt_windows_sandbox_enable(should_prompt_windows_sandbox_nux_at_startup);
|
||||
|
||||
let file_search = FileSearchManager::new(config.cwd.clone(), app_event_tx.clone());
|
||||
let file_search = FileSearchManager::new(config.cwd.to_path_buf(), app_event_tx.clone());
|
||||
#[cfg(not(debug_assertions))]
|
||||
let upgrade_version = crate::updates::get_upgrade_version(&config);
|
||||
|
||||
@@ -3144,7 +3146,13 @@ impl App {
|
||||
let tx = app.app_event_tx.clone();
|
||||
let logs_base_dir = app.config.codex_home.clone();
|
||||
let sandbox_policy = app.config.permissions.sandbox_policy.get().clone();
|
||||
Self::spawn_world_writable_scan(cwd, env_map, logs_base_dir, sandbox_policy, tx);
|
||||
Self::spawn_world_writable_scan(
|
||||
cwd.to_path_buf(),
|
||||
env_map,
|
||||
logs_base_dir,
|
||||
sandbox_policy,
|
||||
tx,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3369,7 +3377,7 @@ impl App {
|
||||
.await?
|
||||
{
|
||||
SessionSelection::Resume(target_session) => {
|
||||
let current_cwd = self.config.cwd.clone();
|
||||
let current_cwd = self.config.cwd.to_path_buf();
|
||||
let resume_cwd = if self.remote_app_server_url.is_some() {
|
||||
current_cwd.clone()
|
||||
} else {
|
||||
@@ -3417,7 +3425,8 @@ impl App {
|
||||
self.shutdown_current_thread(app_server).await;
|
||||
self.config = resume_config;
|
||||
tui.set_notification_method(self.config.tui_notification_method);
|
||||
self.file_search.update_search_dir(self.config.cwd.clone());
|
||||
self.file_search
|
||||
.update_search_dir(self.config.cwd.to_path_buf());
|
||||
match self
|
||||
.replace_chat_widget_with_app_server_thread(tui, resumed)
|
||||
.await
|
||||
@@ -3715,7 +3724,8 @@ impl App {
|
||||
plugin_display_name,
|
||||
result,
|
||||
);
|
||||
if install_succeeded && self.chat_widget.config_ref().cwd == cwd {
|
||||
if install_succeeded && self.chat_widget.config_ref().cwd.as_path() == cwd.as_path()
|
||||
{
|
||||
self.fetch_plugins_list(app_server, cwd.clone());
|
||||
if should_refresh_plugin_detail {
|
||||
self.fetch_plugin_detail(
|
||||
@@ -4190,7 +4200,9 @@ impl App {
|
||||
plugin_display_name,
|
||||
result,
|
||||
);
|
||||
if uninstall_succeeded && self.chat_widget.config_ref().cwd == cwd {
|
||||
if uninstall_succeeded
|
||||
&& self.chat_widget.config_ref().cwd.as_path() == cwd.as_path()
|
||||
{
|
||||
self.fetch_plugins_list(app_server, cwd);
|
||||
}
|
||||
}
|
||||
@@ -4377,7 +4389,7 @@ impl App {
|
||||
let logs_base_dir = self.config.codex_home.clone();
|
||||
let sandbox_policy = self.config.permissions.sandbox_policy.get().clone();
|
||||
Self::spawn_world_writable_scan(
|
||||
cwd,
|
||||
cwd.to_path_buf(),
|
||||
env_map,
|
||||
logs_base_dir,
|
||||
sandbox_policy,
|
||||
@@ -6693,7 +6705,7 @@ mod tests {
|
||||
let (mut app, mut app_event_rx, mut op_rx) = make_test_app_with_channels().await;
|
||||
let codex_home = tempdir()?;
|
||||
app.config.codex_home = codex_home.path().to_path_buf();
|
||||
let config_toml_path = AbsolutePathBuf::try_from(codex_home.path().join("config.toml"))?;
|
||||
let config_toml_path = codex_home.path().join("config.toml").abs();
|
||||
let config_toml = "approvals_reviewer = \"guardian_subagent\"\napproval_policy = \"on-request\"\nsandbox_mode = \"workspace-write\"\n\n[features]\nguardian_approval = true\n";
|
||||
std::fs::write(config_toml_path.as_path(), config_toml)?;
|
||||
let user_config = toml::from_str::<TomlValue>(config_toml)?;
|
||||
@@ -6785,7 +6797,7 @@ mod tests {
|
||||
let codex_home = tempdir()?;
|
||||
app.config.codex_home = codex_home.path().to_path_buf();
|
||||
let guardian_approvals = guardian_approvals_mode();
|
||||
let config_toml_path = AbsolutePathBuf::try_from(codex_home.path().join("config.toml"))?;
|
||||
let config_toml_path = codex_home.path().join("config.toml").abs();
|
||||
let config_toml = "approvals_reviewer = \"user\"\n";
|
||||
std::fs::write(config_toml_path.as_path(), config_toml)?;
|
||||
let user_config = toml::from_str::<TomlValue>(config_toml)?;
|
||||
@@ -6852,7 +6864,7 @@ mod tests {
|
||||
let (mut app, mut app_event_rx, mut op_rx) = make_test_app_with_channels().await;
|
||||
let codex_home = tempdir()?;
|
||||
app.config.codex_home = codex_home.path().to_path_buf();
|
||||
let config_toml_path = AbsolutePathBuf::try_from(codex_home.path().join("config.toml"))?;
|
||||
let config_toml_path = codex_home.path().join("config.toml").abs();
|
||||
let config_toml = "approvals_reviewer = \"user\"\napproval_policy = \"on-request\"\nsandbox_mode = \"workspace-write\"\n\n[features]\nguardian_approval = true\n";
|
||||
std::fs::write(config_toml_path.as_path(), config_toml)?;
|
||||
let user_config = toml::from_str::<TomlValue>(config_toml)?;
|
||||
@@ -6913,7 +6925,7 @@ mod tests {
|
||||
app.config.codex_home = codex_home.path().to_path_buf();
|
||||
let guardian_approvals = guardian_approvals_mode();
|
||||
app.active_profile = Some("guardian".to_string());
|
||||
let config_toml_path = AbsolutePathBuf::try_from(codex_home.path().join("config.toml"))?;
|
||||
let config_toml_path = codex_home.path().join("config.toml").abs();
|
||||
let config_toml = "profile = \"guardian\"\napprovals_reviewer = \"user\"\n";
|
||||
std::fs::write(config_toml_path.as_path(), config_toml)?;
|
||||
let user_config = toml::from_str::<TomlValue>(config_toml)?;
|
||||
@@ -6983,7 +6995,7 @@ mod tests {
|
||||
let codex_home = tempdir()?;
|
||||
app.config.codex_home = codex_home.path().to_path_buf();
|
||||
app.active_profile = Some("guardian".to_string());
|
||||
let config_toml_path = AbsolutePathBuf::try_from(codex_home.path().join("config.toml"))?;
|
||||
let config_toml_path = codex_home.path().join("config.toml").abs();
|
||||
let config_toml = r#"
|
||||
profile = "guardian"
|
||||
approvals_reviewer = "user"
|
||||
@@ -7071,7 +7083,7 @@ guardian_approval = true
|
||||
let codex_home = tempdir()?;
|
||||
app.config.codex_home = codex_home.path().to_path_buf();
|
||||
app.active_profile = Some("guardian".to_string());
|
||||
let config_toml_path = AbsolutePathBuf::try_from(codex_home.path().join("config.toml"))?;
|
||||
let config_toml_path = codex_home.path().join("config.toml").abs();
|
||||
let config_toml = "profile = \"guardian\"\napprovals_reviewer = \"guardian_subagent\"\n\n[features]\nguardian_approval = true\n";
|
||||
std::fs::write(config_toml_path.as_path(), config_toml)?;
|
||||
let user_config = toml::from_str::<TomlValue>(config_toml)?;
|
||||
@@ -7652,7 +7664,7 @@ guardian_approval = true
|
||||
|
||||
async fn render_clear_ui_header_after_long_transcript_for_snapshot() -> String {
|
||||
let mut app = make_test_app().await;
|
||||
app.config.cwd = PathBuf::from("/tmp/project");
|
||||
app.config.cwd = PathBuf::from("/tmp/project").abs();
|
||||
app.chat_widget.set_model("gpt-test");
|
||||
app.chat_widget
|
||||
.set_reasoning_effort(Some(ReasoningEffortConfig::High));
|
||||
@@ -7705,7 +7717,7 @@ guardian_approval = true
|
||||
approval_policy: AskForApproval::Never,
|
||||
approvals_reviewer: ApprovalsReviewer::User,
|
||||
sandbox_policy: SandboxPolicy::new_read_only_policy(),
|
||||
cwd: PathBuf::from("/tmp/project"),
|
||||
cwd: PathBuf::from("/tmp/project").abs().to_path_buf(),
|
||||
reasoning_effort: Some(ReasoningEffortConfig::High),
|
||||
history_log_id: 0,
|
||||
history_entry_count: 0,
|
||||
@@ -7763,21 +7775,33 @@ guardian_approval = true
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
#[cfg_attr(
|
||||
target_os = "windows",
|
||||
ignore = "snapshot path rendering differs on Windows"
|
||||
)]
|
||||
async fn clear_ui_after_long_transcript_snapshots_fresh_header_only() {
|
||||
let rendered = render_clear_ui_header_after_long_transcript_for_snapshot().await;
|
||||
assert_snapshot!("clear_ui_after_long_transcript_fresh_header_only", rendered);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
#[cfg_attr(
|
||||
target_os = "windows",
|
||||
ignore = "snapshot path rendering differs on Windows"
|
||||
)]
|
||||
async fn ctrl_l_clear_ui_after_long_transcript_reuses_clear_header_snapshot() {
|
||||
let rendered = render_clear_ui_header_after_long_transcript_for_snapshot().await;
|
||||
assert_snapshot!("clear_ui_after_long_transcript_fresh_header_only", rendered);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
#[cfg_attr(
|
||||
target_os = "windows",
|
||||
ignore = "snapshot path rendering differs on Windows"
|
||||
)]
|
||||
async fn clear_ui_header_shows_fast_status_only_for_gpt54() {
|
||||
let mut app = make_test_app().await;
|
||||
app.config.cwd = PathBuf::from("/tmp/project");
|
||||
app.config.cwd = PathBuf::from("/tmp/project").abs();
|
||||
app.chat_widget.set_model("gpt-5.4");
|
||||
app.chat_widget
|
||||
.set_reasoning_effort(Some(ReasoningEffortConfig::XHigh));
|
||||
@@ -7803,7 +7827,7 @@ guardian_approval = true
|
||||
async fn make_test_app() -> App {
|
||||
let (chat_widget, app_event_tx, _rx, _op_rx) = make_chatwidget_manual_with_sender().await;
|
||||
let config = chat_widget.config_ref().clone();
|
||||
let file_search = FileSearchManager::new(config.cwd.clone(), app_event_tx.clone());
|
||||
let file_search = FileSearchManager::new(config.cwd.to_path_buf(), app_event_tx.clone());
|
||||
let model = codex_core::test_support::get_model_offline(config.model.as_deref());
|
||||
let session_telemetry = test_session_telemetry(&config, model.as_str());
|
||||
|
||||
@@ -7853,7 +7877,7 @@ guardian_approval = true
|
||||
) {
|
||||
let (chat_widget, app_event_tx, rx, op_rx) = make_chatwidget_manual_with_sender().await;
|
||||
let config = chat_widget.config_ref().clone();
|
||||
let file_search = FileSearchManager::new(config.cwd.clone(), app_event_tx.clone());
|
||||
let file_search = FileSearchManager::new(config.cwd.to_path_buf(), app_event_tx.clone());
|
||||
let model = codex_core::test_support::get_model_offline(config.model.as_deref());
|
||||
let session_telemetry = test_session_telemetry(&config, model.as_str());
|
||||
|
||||
@@ -8598,7 +8622,7 @@ guardian_approval = true
|
||||
}),
|
||||
});
|
||||
|
||||
assert_eq!(app.chat_widget.config_ref().cwd, next_cwd);
|
||||
assert_eq!(app.chat_widget.config_ref().cwd.to_path_buf(), next_cwd);
|
||||
assert_eq!(app.config.cwd, original_cwd);
|
||||
|
||||
app.refresh_in_memory_config_from_disk().await?;
|
||||
@@ -8618,7 +8642,7 @@ guardian_approval = true
|
||||
let current_cwd = current_config.cwd.clone();
|
||||
|
||||
let resume_config = app
|
||||
.rebuild_config_for_resume_or_fallback(¤t_cwd, current_cwd.clone())
|
||||
.rebuild_config_for_resume_or_fallback(¤t_cwd, current_cwd.to_path_buf())
|
||||
.await?;
|
||||
|
||||
assert_eq!(resume_config, current_config);
|
||||
|
||||
@@ -207,6 +207,7 @@ use codex_terminal_detection::Multiplexer;
|
||||
use codex_terminal_detection::TerminalInfo;
|
||||
use codex_terminal_detection::TerminalName;
|
||||
use codex_terminal_detection::terminal_info;
|
||||
use codex_utils_absolute_path::AbsolutePathBuf;
|
||||
use codex_utils_sleep_inhibitor::SleepInhibitor;
|
||||
use crossterm::event::KeyCode;
|
||||
use crossterm::event::KeyEvent;
|
||||
@@ -1778,7 +1779,12 @@ impl ChatWidget {
|
||||
self.forked_from = event.forked_from_id;
|
||||
self.current_rollout_path = event.rollout_path.clone();
|
||||
self.current_cwd = Some(event.cwd.clone());
|
||||
self.config.cwd = event.cwd.clone();
|
||||
match AbsolutePathBuf::try_from(event.cwd.clone()) {
|
||||
Ok(cwd) => self.config.cwd = cwd,
|
||||
Err(err) => {
|
||||
tracing::warn!(path = %event.cwd.display(), %err, "session cwd should be absolute");
|
||||
}
|
||||
}
|
||||
if let Err(err) = self
|
||||
.config
|
||||
.permissions
|
||||
@@ -4026,13 +4032,13 @@ impl ChatWidget {
|
||||
id: ev.call_id,
|
||||
reason: ev.reason,
|
||||
changes: ev.changes.clone(),
|
||||
cwd: self.config.cwd.clone(),
|
||||
cwd: self.config.cwd.to_path_buf(),
|
||||
};
|
||||
self.bottom_pane
|
||||
.push_approval_request(request, &self.config.features);
|
||||
self.request_redraw();
|
||||
self.notify(Notification::EditApprovalRequested {
|
||||
cwd: self.config.cwd.clone(),
|
||||
cwd: self.config.cwd.to_path_buf(),
|
||||
changes: ev.changes.keys().cloned().collect(),
|
||||
});
|
||||
}
|
||||
@@ -4274,7 +4280,7 @@ impl ChatWidget {
|
||||
|
||||
let active_cell = Some(Self::placeholder_session_header_cell(&config));
|
||||
|
||||
let current_cwd = Some(config.cwd.clone());
|
||||
let current_cwd = Some(config.cwd.to_path_buf());
|
||||
let queued_message_edit_binding = queued_message_edit_binding_for_terminal(terminal_info());
|
||||
let mut widget = Self {
|
||||
app_event_tx: app_event_tx.clone(),
|
||||
@@ -4693,7 +4699,15 @@ impl ChatWidget {
|
||||
self.app_event_tx.send(AppEvent::ForkCurrentSession);
|
||||
}
|
||||
SlashCommand::Init => {
|
||||
let init_target = self.config.cwd.join(DEFAULT_PROJECT_DOC_FILENAME);
|
||||
let init_target = match self.config.cwd.join(DEFAULT_PROJECT_DOC_FILENAME) {
|
||||
Ok(path) => path,
|
||||
Err(err) => {
|
||||
self.add_error_message(format!(
|
||||
"Failed to prepare {DEFAULT_PROJECT_DOC_FILENAME}: {err}",
|
||||
));
|
||||
return;
|
||||
}
|
||||
};
|
||||
if init_target.exists() {
|
||||
let message = format!(
|
||||
"{DEFAULT_PROJECT_DOC_FILENAME} already exists here. Skipping /init to avoid overwriting it."
|
||||
@@ -5415,7 +5429,7 @@ impl ChatWidget {
|
||||
let service_tier = self.config.service_tier.map(Some);
|
||||
let op = AppCommand::user_turn(
|
||||
items,
|
||||
self.config.cwd.clone(),
|
||||
self.config.cwd.to_path_buf(),
|
||||
self.config.permissions.approval_policy.value(),
|
||||
self.config.permissions.sandbox_policy.get().clone(),
|
||||
effective_mode.model().to_string(),
|
||||
@@ -7022,7 +7036,9 @@ impl ChatWidget {
|
||||
}
|
||||
|
||||
fn status_line_cwd(&self) -> &Path {
|
||||
self.current_cwd.as_ref().unwrap_or(&self.config.cwd)
|
||||
self.current_cwd
|
||||
.as_deref()
|
||||
.unwrap_or(self.config.cwd.as_path())
|
||||
}
|
||||
|
||||
fn status_line_project_root(&self) -> Option<PathBuf> {
|
||||
@@ -9617,7 +9633,7 @@ impl ChatWidget {
|
||||
placeholder_style,
|
||||
/*reasoning_effort*/ None,
|
||||
/*show_fast_status*/ false,
|
||||
config.cwd.clone(),
|
||||
config.cwd.to_path_buf(),
|
||||
CODEX_CLI_VERSION,
|
||||
))
|
||||
}
|
||||
@@ -10331,7 +10347,7 @@ impl ChatWidget {
|
||||
name: "Review against a base branch".to_string(),
|
||||
description: Some("(PR Style)".into()),
|
||||
actions: vec![Box::new({
|
||||
let cwd = self.config.cwd.clone();
|
||||
let cwd = self.config.cwd.to_path_buf();
|
||||
move |tx| {
|
||||
tx.send(AppEvent::OpenReviewBranchPicker(cwd.clone()));
|
||||
}
|
||||
@@ -10356,7 +10372,7 @@ impl ChatWidget {
|
||||
items.push(SelectionItem {
|
||||
name: "Review a commit".to_string(),
|
||||
actions: vec![Box::new({
|
||||
let cwd = self.config.cwd.clone();
|
||||
let cwd = self.config.cwd.to_path_buf();
|
||||
move |tx| {
|
||||
tx.send(AppEvent::OpenReviewCommitPicker(cwd.clone()));
|
||||
}
|
||||
|
||||
@@ -159,11 +159,11 @@ impl ChatWidget {
|
||||
cwd: PathBuf,
|
||||
result: Result<PluginListResponse, String>,
|
||||
) {
|
||||
if self.plugins_fetch_state.in_flight_cwd.as_ref() == Some(&cwd) {
|
||||
if self.plugins_fetch_state.in_flight_cwd.as_deref() == Some(cwd.as_path()) {
|
||||
self.plugins_fetch_state.in_flight_cwd = None;
|
||||
}
|
||||
|
||||
if self.config.cwd != cwd {
|
||||
if self.config.cwd.as_path() != cwd.as_path() {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -191,13 +191,13 @@ impl ChatWidget {
|
||||
}
|
||||
|
||||
fn prefetch_plugins(&mut self) {
|
||||
let cwd = self.config.cwd.clone();
|
||||
if self.plugins_fetch_state.in_flight_cwd.as_ref() == Some(&cwd) {
|
||||
let cwd = self.config.cwd.to_path_buf();
|
||||
if self.plugins_fetch_state.in_flight_cwd.as_deref() == Some(cwd.as_path()) {
|
||||
return;
|
||||
}
|
||||
|
||||
self.plugins_fetch_state.in_flight_cwd = Some(cwd.clone());
|
||||
if self.plugins_fetch_state.cache_cwd.as_ref() != Some(&cwd) {
|
||||
if self.plugins_fetch_state.cache_cwd.as_deref() != Some(cwd.as_path()) {
|
||||
self.plugins_cache = PluginsCacheState::Loading;
|
||||
}
|
||||
|
||||
@@ -205,7 +205,7 @@ impl ChatWidget {
|
||||
}
|
||||
|
||||
fn plugins_cache_for_current_cwd(&self) -> PluginsCacheState {
|
||||
if self.plugins_fetch_state.cache_cwd.as_ref() == Some(&self.config.cwd) {
|
||||
if self.plugins_fetch_state.cache_cwd.as_deref() == Some(self.config.cwd.as_path()) {
|
||||
self.plugins_cache.clone()
|
||||
} else {
|
||||
PluginsCacheState::Uninitialized
|
||||
@@ -253,7 +253,7 @@ impl ChatWidget {
|
||||
cwd: PathBuf,
|
||||
result: Result<PluginReadResponse, String>,
|
||||
) {
|
||||
if self.config.cwd != cwd {
|
||||
if self.config.cwd.as_path() != cwd.as_path() {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -288,7 +288,7 @@ impl ChatWidget {
|
||||
plugin_display_name: String,
|
||||
result: Result<PluginInstallResponse, String>,
|
||||
) -> bool {
|
||||
if self.config.cwd != cwd {
|
||||
if self.config.cwd.as_path() != cwd.as_path() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -346,7 +346,7 @@ impl ChatWidget {
|
||||
plugin_display_name: String,
|
||||
result: Result<PluginUninstallResponse, String>,
|
||||
) {
|
||||
if self.config.cwd != cwd {
|
||||
if self.config.cwd.as_path() != cwd.as_path() {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -676,7 +676,7 @@ impl ChatWidget {
|
||||
..Default::default()
|
||||
}];
|
||||
if let Some(plugins_response) = plugins_response.cloned() {
|
||||
let cwd = self.config.cwd.clone();
|
||||
let cwd = self.config.cwd.to_path_buf();
|
||||
items.push(SelectionItem {
|
||||
name: "Back to plugins".to_string(),
|
||||
description: Some("Return to the plugin list.".to_string()),
|
||||
@@ -770,7 +770,7 @@ impl ChatWidget {
|
||||
"{display_name} {} {} {}",
|
||||
plugin.id, plugin.name, marketplace_label
|
||||
);
|
||||
let cwd = self.config.cwd.clone();
|
||||
let cwd = self.config.cwd.to_path_buf();
|
||||
let plugin_display_name = display_name.clone();
|
||||
let marketplace_path = marketplace.path.clone();
|
||||
let plugin_name = plugin.name.clone();
|
||||
@@ -861,7 +861,7 @@ impl ChatWidget {
|
||||
header.push(Line::from(description.dim()));
|
||||
}
|
||||
|
||||
let cwd = self.config.cwd.clone();
|
||||
let cwd = self.config.cwd.to_path_buf();
|
||||
let plugins_response = plugins_response.clone();
|
||||
let mut items = vec![SelectionItem {
|
||||
name: "Back to plugins".to_string(),
|
||||
@@ -877,7 +877,7 @@ impl ChatWidget {
|
||||
}];
|
||||
|
||||
if plugin.summary.installed {
|
||||
let uninstall_cwd = self.config.cwd.clone();
|
||||
let uninstall_cwd = self.config.cwd.to_path_buf();
|
||||
let plugin_id = plugin.summary.id.clone();
|
||||
let plugin_display_name = display_name;
|
||||
items.push(SelectionItem {
|
||||
@@ -906,7 +906,7 @@ impl ChatWidget {
|
||||
..Default::default()
|
||||
});
|
||||
} else {
|
||||
let install_cwd = self.config.cwd.clone();
|
||||
let install_cwd = self.config.cwd.to_path_buf();
|
||||
let marketplace_path = plugin.marketplace_path.clone();
|
||||
let plugin_name = plugin.summary.name.clone();
|
||||
let plugin_display_name = display_name;
|
||||
|
||||
@@ -17,6 +17,8 @@ use crate::chatwidget::realtime::RealtimeConversationPhase;
|
||||
use crate::history_cell::UserHistoryCell;
|
||||
use crate::model_catalog::ModelCatalog;
|
||||
use crate::test_backend::VT100Backend;
|
||||
use crate::test_support::PathBufExt;
|
||||
use crate::test_support::test_path_display;
|
||||
use crate::tui::FrameRequester;
|
||||
use assert_matches::assert_matches;
|
||||
use codex_app_server_protocol::AppSummary;
|
||||
@@ -458,10 +460,10 @@ async fn session_configured_syncs_widget_config_permissions_and_cwd() {
|
||||
.sandbox_policy
|
||||
.set(SandboxPolicy::new_workspace_write_policy())
|
||||
.expect("set sandbox policy");
|
||||
chat.config.cwd = PathBuf::from("/home/user/main");
|
||||
chat.config.cwd = PathBuf::from("/home/user/main").abs();
|
||||
|
||||
let expected_sandbox = SandboxPolicy::new_read_only_policy();
|
||||
let expected_cwd = PathBuf::from("/home/user/sub-agent");
|
||||
let expected_cwd = PathBuf::from("/home/user/sub-agent").abs();
|
||||
let configured = codex_protocol::protocol::SessionConfiguredEvent {
|
||||
session_id: ThreadId::new(),
|
||||
forked_from_id: None,
|
||||
@@ -472,7 +474,7 @@ async fn session_configured_syncs_widget_config_permissions_and_cwd() {
|
||||
approval_policy: AskForApproval::Never,
|
||||
approvals_reviewer: ApprovalsReviewer::User,
|
||||
sandbox_policy: expected_sandbox.clone(),
|
||||
cwd: expected_cwd.clone(),
|
||||
cwd: expected_cwd.to_path_buf(),
|
||||
reasoning_effort: Some(ReasoningEffortConfig::default()),
|
||||
history_log_id: 0,
|
||||
history_entry_count: 0,
|
||||
@@ -6426,7 +6428,7 @@ async fn slash_init_skips_when_project_doc_exists() {
|
||||
let tempdir = tempdir().unwrap();
|
||||
let existing_path = tempdir.path().join(DEFAULT_PROJECT_DOC_FILENAME);
|
||||
std::fs::write(&existing_path, "existing instructions").unwrap();
|
||||
chat.config.cwd = tempdir.path().to_path_buf();
|
||||
chat.config.cwd = tempdir.path().to_path_buf().abs();
|
||||
|
||||
chat.dispatch_command(SlashCommand::Init);
|
||||
|
||||
@@ -7419,13 +7421,17 @@ async fn custom_prompt_enter_empty_does_not_send() {
|
||||
#[tokio::test]
|
||||
async fn view_image_tool_call_adds_history_cell() {
|
||||
let (mut chat, mut rx, _op_rx) = make_chatwidget_manual(None).await;
|
||||
let image_path = chat.config.cwd.join("example.png");
|
||||
let image_path = chat
|
||||
.config
|
||||
.cwd
|
||||
.join("example.png")
|
||||
.expect("absolute image path");
|
||||
|
||||
chat.handle_codex_event(Event {
|
||||
id: "sub-image".into(),
|
||||
msg: EventMsg::ViewImageToolCall(ViewImageToolCallEvent {
|
||||
call_id: "call-image".into(),
|
||||
path: image_path,
|
||||
path: image_path.to_path_buf(),
|
||||
}),
|
||||
});
|
||||
|
||||
@@ -7715,12 +7721,10 @@ fn strip_osc8_for_snapshot(text: &str) -> String {
|
||||
}
|
||||
|
||||
fn plugins_test_absolute_path(path: &str) -> AbsolutePathBuf {
|
||||
AbsolutePathBuf::try_from(
|
||||
std::env::temp_dir()
|
||||
.join("codex-plugin-menu-tests")
|
||||
.join(path),
|
||||
)
|
||||
.expect("expected absolute test path")
|
||||
std::env::temp_dir()
|
||||
.join("codex-plugin-menu-tests")
|
||||
.join(path)
|
||||
.abs()
|
||||
}
|
||||
|
||||
fn plugins_test_interface(
|
||||
@@ -7802,7 +7806,7 @@ fn plugins_test_response(marketplaces: Vec<PluginMarketplaceEntry>) -> PluginLis
|
||||
|
||||
fn render_loaded_plugins_popup(chat: &mut ChatWidget, response: PluginListResponse) -> String {
|
||||
let cwd = chat.config.cwd.clone();
|
||||
chat.on_plugins_loaded(cwd, Ok(response));
|
||||
chat.on_plugins_loaded(cwd.to_path_buf(), Ok(response));
|
||||
chat.add_plugins_output();
|
||||
render_bottom_popup(chat, 100)
|
||||
}
|
||||
@@ -7953,10 +7957,10 @@ async fn plugin_detail_popup_snapshot_shows_install_actions_and_capability_summa
|
||||
summary.clone(),
|
||||
])]);
|
||||
let cwd = chat.config.cwd.clone();
|
||||
chat.on_plugins_loaded(cwd.clone(), Ok(response));
|
||||
chat.on_plugins_loaded(cwd.to_path_buf(), Ok(response));
|
||||
chat.add_plugins_output();
|
||||
chat.on_plugin_detail_loaded(
|
||||
cwd,
|
||||
cwd.to_path_buf(),
|
||||
Ok(PluginReadResponse {
|
||||
plugin: plugins_test_detail(
|
||||
summary,
|
||||
@@ -7993,10 +7997,10 @@ async fn plugin_detail_popup_hides_disclosure_for_installed_plugins() {
|
||||
summary.clone(),
|
||||
])]);
|
||||
let cwd = chat.config.cwd.clone();
|
||||
chat.on_plugins_loaded(cwd.clone(), Ok(response));
|
||||
chat.on_plugins_loaded(cwd.to_path_buf(), Ok(response));
|
||||
chat.add_plugins_output();
|
||||
chat.on_plugin_detail_loaded(
|
||||
cwd,
|
||||
cwd.to_path_buf(),
|
||||
Ok(PluginReadResponse {
|
||||
plugin: plugins_test_detail(
|
||||
summary,
|
||||
@@ -8083,7 +8087,7 @@ async fn plugins_popup_refresh_replaces_selection_with_first_row() {
|
||||
),
|
||||
])]);
|
||||
let cwd = chat.config.cwd.clone();
|
||||
chat.on_plugins_loaded(cwd, Ok(refreshed));
|
||||
chat.on_plugins_loaded(cwd.to_path_buf(), Ok(refreshed));
|
||||
|
||||
let after = render_bottom_popup(&chat, 100);
|
||||
assert!(
|
||||
@@ -8152,7 +8156,7 @@ async fn plugins_popup_refreshes_installed_counts_after_install() {
|
||||
),
|
||||
])]);
|
||||
let cwd = chat.config.cwd.clone();
|
||||
chat.on_plugins_loaded(cwd, Ok(refreshed));
|
||||
chat.on_plugins_loaded(cwd.to_path_buf(), Ok(refreshed));
|
||||
|
||||
let after = render_bottom_popup(&chat, 100);
|
||||
assert!(
|
||||
@@ -8824,8 +8828,7 @@ async fn apps_initial_load_applies_enabled_state_from_config() {
|
||||
chat.bottom_pane.set_connectors_enabled(true);
|
||||
|
||||
let temp = tempdir().expect("tempdir");
|
||||
let config_toml_path =
|
||||
AbsolutePathBuf::try_from(temp.path().join("config.toml")).expect("absolute config path");
|
||||
let config_toml_path = temp.path().join("config.toml").abs();
|
||||
let user_config = toml::from_str::<TomlValue>(
|
||||
"[apps.connector_1]\nenabled = false\ndisabled_reason = \"user\"\n",
|
||||
)
|
||||
@@ -8889,8 +8892,7 @@ async fn apps_initial_load_applies_enabled_state_from_requirements_with_user_ove
|
||||
..Default::default()
|
||||
};
|
||||
let temp = tempdir().expect("tempdir");
|
||||
let config_toml_path =
|
||||
AbsolutePathBuf::try_from(temp.path().join("config.toml")).expect("absolute config path");
|
||||
let config_toml_path = temp.path().join("config.toml").abs();
|
||||
chat.config.config_layer_stack =
|
||||
ConfigLayerStack::new(Vec::new(), ConfigRequirements::default(), requirements)
|
||||
.expect("requirements stack")
|
||||
@@ -9465,7 +9467,7 @@ async fn preset_matching_accepts_workspace_write_with_extra_roots() {
|
||||
.find(|p| p.id == "auto")
|
||||
.expect("auto preset exists");
|
||||
let current_sandbox = SandboxPolicy::WorkspaceWrite {
|
||||
writable_roots: vec![AbsolutePathBuf::try_from("C:\\extra").unwrap()],
|
||||
writable_roots: vec![PathBuf::from("C:\\extra").abs()],
|
||||
read_only_access: Default::default(),
|
||||
network_access: false,
|
||||
exclude_tmpdir_env_var: false,
|
||||
@@ -10265,8 +10267,7 @@ async fn permissions_selection_marks_guardian_approvals_current_with_custom_work
|
||||
.features
|
||||
.set_enabled(Feature::GuardianApproval, true);
|
||||
|
||||
let extra_root = AbsolutePathBuf::try_from("/tmp/guardian-approvals-extra")
|
||||
.expect("absolute extra writable root");
|
||||
let extra_root = PathBuf::from("/tmp/guardian-approvals-extra").abs();
|
||||
|
||||
chat.handle_codex_event(Event {
|
||||
id: "session-configured-custom-workspace".to_string(),
|
||||
@@ -12279,7 +12280,7 @@ async fn status_line_fast_mode_footer_snapshot() {
|
||||
#[tokio::test]
|
||||
async fn status_line_model_with_reasoning_includes_fast_for_gpt54_only() {
|
||||
let (mut chat, _rx, _op_rx) = make_chatwidget_manual(Some("gpt-5.4")).await;
|
||||
chat.config.cwd = PathBuf::from("/tmp/project");
|
||||
chat.config.cwd = PathBuf::from("/tmp/project").abs();
|
||||
chat.config.tui_status_line = Some(vec![
|
||||
"model-with-reasoning".to_string(),
|
||||
"context-remaining".to_string(),
|
||||
@@ -12289,10 +12290,11 @@ async fn status_line_model_with_reasoning_includes_fast_for_gpt54_only() {
|
||||
chat.set_service_tier(Some(ServiceTier::Fast));
|
||||
set_chatgpt_auth(&mut chat);
|
||||
chat.refresh_status_line();
|
||||
let test_cwd = test_path_display("/tmp/project");
|
||||
|
||||
assert_eq!(
|
||||
status_line_text(&chat),
|
||||
Some("gpt-5.4 xhigh fast · 100% left · /tmp/project".to_string())
|
||||
Some(format!("gpt-5.4 xhigh fast · 100% left · {test_cwd}"))
|
||||
);
|
||||
|
||||
chat.set_model("gpt-5.3-codex");
|
||||
@@ -12300,18 +12302,22 @@ async fn status_line_model_with_reasoning_includes_fast_for_gpt54_only() {
|
||||
|
||||
assert_eq!(
|
||||
status_line_text(&chat),
|
||||
Some("gpt-5.3-codex xhigh · 100% left · /tmp/project".to_string())
|
||||
Some(format!("gpt-5.3-codex xhigh · 100% left · {test_cwd}"))
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
#[cfg_attr(
|
||||
target_os = "windows",
|
||||
ignore = "snapshot path rendering differs on Windows"
|
||||
)]
|
||||
async fn status_line_model_with_reasoning_fast_footer_snapshot() {
|
||||
use ratatui::Terminal;
|
||||
use ratatui::backend::TestBackend;
|
||||
|
||||
let (mut chat, _rx, _op_rx) = make_chatwidget_manual(Some("gpt-5.4")).await;
|
||||
chat.show_welcome_banner = false;
|
||||
chat.config.cwd = PathBuf::from("/tmp/project");
|
||||
chat.config.cwd = PathBuf::from("/tmp/project").abs();
|
||||
chat.config.tui_status_line = Some(vec![
|
||||
"model-with-reasoning".to_string(),
|
||||
"context-remaining".to_string(),
|
||||
|
||||
@@ -27,6 +27,8 @@ use crate::render::line_utils::push_owned_lines;
|
||||
use crate::render::renderable::Renderable;
|
||||
use crate::style::proposed_plan_style;
|
||||
use crate::style::user_message_style;
|
||||
#[cfg(test)]
|
||||
use crate::test_support::PathBufExt;
|
||||
use crate::text_formatting::format_and_truncate_tool_result;
|
||||
use crate::text_formatting::truncate_text;
|
||||
use crate::tooltips;
|
||||
@@ -1143,7 +1145,7 @@ pub(crate) fn new_session_info(
|
||||
model.clone(),
|
||||
reasoning_effort,
|
||||
show_fast_status,
|
||||
config.cwd.clone(),
|
||||
config.cwd.to_path_buf(),
|
||||
CODEX_CLI_VERSION,
|
||||
);
|
||||
let mut parts: Vec<Box<dyn HistoryCell>> = vec![Box::new(header)];
|
||||
@@ -2883,7 +2885,7 @@ mod tests {
|
||||
approval_policy: AskForApproval::Never,
|
||||
approvals_reviewer: codex_protocol::config_types::ApprovalsReviewer::User,
|
||||
sandbox_policy: SandboxPolicy::new_read_only_policy(),
|
||||
cwd: PathBuf::from("/tmp/project"),
|
||||
cwd: PathBuf::from("/tmp/project").abs().to_path_buf(),
|
||||
reasoning_effort: None,
|
||||
history_log_id: 0,
|
||||
history_entry_count: 0,
|
||||
@@ -2997,9 +2999,13 @@ mod tests {
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
#[cfg_attr(
|
||||
target_os = "windows",
|
||||
ignore = "snapshot path rendering differs on Windows"
|
||||
)]
|
||||
async fn session_info_availability_nux_tooltip_snapshot() {
|
||||
let mut config = test_config().await;
|
||||
config.cwd = PathBuf::from("/tmp/project");
|
||||
config.cwd = PathBuf::from("/tmp/project").abs();
|
||||
let cell = new_session_info(
|
||||
&config,
|
||||
"gpt-5",
|
||||
|
||||
@@ -248,6 +248,8 @@ mod wrapping;
|
||||
|
||||
#[cfg(test)]
|
||||
pub mod test_backend;
|
||||
#[cfg(test)]
|
||||
pub(crate) mod test_support;
|
||||
|
||||
use crate::onboarding::onboarding_screen::OnboardingScreenArgs;
|
||||
use crate::onboarding::onboarding_screen::run_onboarding_app;
|
||||
@@ -1221,7 +1223,7 @@ async fn run_ratatui_app(
|
||||
let fallback_cwd = match action_and_target_session_if_resume_or_fork {
|
||||
Some((action, target_session)) => {
|
||||
if remote_mode {
|
||||
Some(current_cwd.clone())
|
||||
Some(current_cwd.to_path_buf())
|
||||
} else {
|
||||
match resolve_cwd_for_resume_or_fork(
|
||||
&mut tui,
|
||||
|
||||
@@ -85,7 +85,7 @@ impl OnboardingScreen {
|
||||
app_server_request_handle,
|
||||
config,
|
||||
} = args;
|
||||
let cwd = config.cwd.clone();
|
||||
let cwd = config.cwd.to_path_buf();
|
||||
let forced_chatgpt_workspace_id = config.forced_chatgpt_workspace_id.clone();
|
||||
let forced_login_method = config.forced_login_method;
|
||||
let codex_home = config.codex_home.clone();
|
||||
|
||||
@@ -255,7 +255,7 @@ impl StatusHistoryCell {
|
||||
Self {
|
||||
model_name,
|
||||
model_details,
|
||||
directory: config.cwd.clone(),
|
||||
directory: config.cwd.to_path_buf(),
|
||||
permissions,
|
||||
agents_summary,
|
||||
collaboration_mode: collaboration_mode.map(ToString::to_string),
|
||||
|
||||
@@ -42,7 +42,7 @@ pub(crate) fn compose_agents_summary(config: &Config) -> String {
|
||||
.map(|name| name.to_string_lossy().to_string())
|
||||
.unwrap_or_else(|| "<unknown>".to_string());
|
||||
let display = if let Some(parent) = p.parent() {
|
||||
if parent == config.cwd {
|
||||
if parent == config.cwd.as_path() {
|
||||
file_name.clone()
|
||||
} else {
|
||||
let mut cur = config.cwd.as_path();
|
||||
|
||||
@@ -2,6 +2,7 @@ use super::new_status_output;
|
||||
use super::rate_limit_snapshot_display;
|
||||
use crate::history_cell::HistoryCell;
|
||||
use crate::status::StatusAccountDisplay;
|
||||
use crate::test_support::PathBufExt;
|
||||
use chrono::Duration as ChronoDuration;
|
||||
use chrono::TimeZone;
|
||||
use chrono::Utc;
|
||||
@@ -105,7 +106,7 @@ async fn status_snapshot_includes_reasoning_details() {
|
||||
})
|
||||
.expect("set sandbox policy");
|
||||
|
||||
config.cwd = PathBuf::from("/workspace/tests");
|
||||
config.cwd = PathBuf::from("/workspace/tests").abs();
|
||||
|
||||
let account_display = test_status_account_display();
|
||||
let usage = TokenUsage {
|
||||
@@ -189,7 +190,7 @@ async fn status_permissions_non_default_workspace_write_is_custom() {
|
||||
exclude_slash_tmp: false,
|
||||
})
|
||||
.expect("set sandbox policy");
|
||||
config.cwd = PathBuf::from("/workspace/tests");
|
||||
config.cwd = PathBuf::from("/workspace/tests").abs();
|
||||
|
||||
let account_display = test_status_account_display();
|
||||
let usage = TokenUsage::default();
|
||||
@@ -238,7 +239,7 @@ async fn status_snapshot_includes_forked_from() {
|
||||
let mut config = test_config(&temp_home).await;
|
||||
config.model = Some("gpt-5.1-codex-max".to_string());
|
||||
config.model_provider_id = "openai".to_string();
|
||||
config.cwd = PathBuf::from("/workspace/tests");
|
||||
config.cwd = PathBuf::from("/workspace/tests").abs();
|
||||
|
||||
let account_display = test_status_account_display();
|
||||
let usage = TokenUsage {
|
||||
@@ -292,7 +293,7 @@ async fn status_snapshot_includes_monthly_limit() {
|
||||
let mut config = test_config(&temp_home).await;
|
||||
config.model = Some("gpt-5.1-codex-max".to_string());
|
||||
config.model_provider_id = "openai".to_string();
|
||||
config.cwd = PathBuf::from("/workspace/tests");
|
||||
config.cwd = PathBuf::from("/workspace/tests").abs();
|
||||
|
||||
let account_display = test_status_account_display();
|
||||
let usage = TokenUsage {
|
||||
@@ -545,7 +546,7 @@ async fn status_card_token_usage_excludes_cached_tokens() {
|
||||
let temp_home = TempDir::new().expect("temp home");
|
||||
let mut config = test_config(&temp_home).await;
|
||||
config.model = Some("gpt-5.1-codex-max".to_string());
|
||||
config.cwd = PathBuf::from("/workspace/tests");
|
||||
config.cwd = PathBuf::from("/workspace/tests").abs();
|
||||
|
||||
let account_display = test_status_account_display();
|
||||
let usage = TokenUsage {
|
||||
@@ -593,7 +594,7 @@ async fn status_snapshot_truncates_in_narrow_terminal() {
|
||||
config.model = Some("gpt-5.1-codex-max".to_string());
|
||||
config.model_provider_id = "openai".to_string();
|
||||
config.model_reasoning_summary = Some(ReasoningSummary::Detailed);
|
||||
config.cwd = PathBuf::from("/workspace/tests");
|
||||
config.cwd = PathBuf::from("/workspace/tests").abs();
|
||||
|
||||
let account_display = test_status_account_display();
|
||||
let usage = TokenUsage {
|
||||
@@ -656,7 +657,7 @@ async fn status_snapshot_shows_missing_limits_message() {
|
||||
let temp_home = TempDir::new().expect("temp home");
|
||||
let mut config = test_config(&temp_home).await;
|
||||
config.model = Some("gpt-5.1-codex-max".to_string());
|
||||
config.cwd = PathBuf::from("/workspace/tests");
|
||||
config.cwd = PathBuf::from("/workspace/tests").abs();
|
||||
|
||||
let account_display = test_status_account_display();
|
||||
let usage = TokenUsage {
|
||||
@@ -704,7 +705,7 @@ async fn status_snapshot_includes_credits_and_limits() {
|
||||
let temp_home = TempDir::new().expect("temp home");
|
||||
let mut config = test_config(&temp_home).await;
|
||||
config.model = Some("gpt-5.1-codex".to_string());
|
||||
config.cwd = PathBuf::from("/workspace/tests");
|
||||
config.cwd = PathBuf::from("/workspace/tests").abs();
|
||||
|
||||
let account_display = test_status_account_display();
|
||||
let usage = TokenUsage {
|
||||
@@ -773,7 +774,7 @@ async fn status_snapshot_shows_empty_limits_message() {
|
||||
let temp_home = TempDir::new().expect("temp home");
|
||||
let mut config = test_config(&temp_home).await;
|
||||
config.model = Some("gpt-5.1-codex-max".to_string());
|
||||
config.cwd = PathBuf::from("/workspace/tests");
|
||||
config.cwd = PathBuf::from("/workspace/tests").abs();
|
||||
|
||||
let account_display = test_status_account_display();
|
||||
let usage = TokenUsage {
|
||||
@@ -830,7 +831,7 @@ async fn status_snapshot_shows_stale_limits_message() {
|
||||
let temp_home = TempDir::new().expect("temp home");
|
||||
let mut config = test_config(&temp_home).await;
|
||||
config.model = Some("gpt-5.1-codex-max".to_string());
|
||||
config.cwd = PathBuf::from("/workspace/tests");
|
||||
config.cwd = PathBuf::from("/workspace/tests").abs();
|
||||
|
||||
let account_display = test_status_account_display();
|
||||
let usage = TokenUsage {
|
||||
@@ -896,7 +897,7 @@ async fn status_snapshot_cached_limits_hide_credits_without_flag() {
|
||||
let temp_home = TempDir::new().expect("temp home");
|
||||
let mut config = test_config(&temp_home).await;
|
||||
config.model = Some("gpt-5.1-codex".to_string());
|
||||
config.cwd = PathBuf::from("/workspace/tests");
|
||||
config.cwd = PathBuf::from("/workspace/tests").abs();
|
||||
|
||||
let account_display = test_status_account_display();
|
||||
let usage = TokenUsage {
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
use codex_utils_absolute_path::AbsolutePathBuf;
|
||||
use std::path::Path;
|
||||
use std::path::PathBuf;
|
||||
|
||||
pub(crate) trait PathExt {
|
||||
fn abs(&self) -> AbsolutePathBuf;
|
||||
}
|
||||
|
||||
impl PathExt for Path {
|
||||
fn abs(&self) -> AbsolutePathBuf {
|
||||
AbsolutePathBuf::try_from(self.to_path_buf())
|
||||
.unwrap_or_else(|_| panic!("path should already be absolute"))
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) trait PathBufExt {
|
||||
fn abs(&self) -> AbsolutePathBuf;
|
||||
}
|
||||
|
||||
impl PathBufExt for PathBuf {
|
||||
fn abs(&self) -> AbsolutePathBuf {
|
||||
self.as_path().abs()
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn test_path_display(path: &str) -> String {
|
||||
Path::new(path).abs().display().to_string()
|
||||
}
|
||||
@@ -63,6 +63,12 @@ impl AbsolutePathBuf {
|
||||
Self::from_absolute_path(current_dir)
|
||||
}
|
||||
|
||||
/// Construct an absolute path from `path`, resolving relative paths against
|
||||
/// the process current working directory.
|
||||
pub fn relative_to_current_dir<P: AsRef<Path>>(path: P) -> std::io::Result<Self> {
|
||||
Self::resolve_path_against_base(path, std::env::current_dir()?)
|
||||
}
|
||||
|
||||
pub fn join<P: AsRef<Path>>(&self, path: P) -> std::io::Result<Self> {
|
||||
Self::resolve_path_against_base(path, &self.0)
|
||||
}
|
||||
@@ -104,6 +110,14 @@ impl AsRef<Path> for AbsolutePathBuf {
|
||||
}
|
||||
}
|
||||
|
||||
impl std::ops::Deref for AbsolutePathBuf {
|
||||
type Target = Path;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl From<AbsolutePathBuf> for PathBuf {
|
||||
fn from(path: AbsolutePathBuf) -> Self {
|
||||
path.into_path_buf()
|
||||
@@ -216,6 +230,17 @@ mod tests {
|
||||
assert_eq!(abs_path_buf.as_path(), base_dir.join("file.txt").as_path());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn relative_to_current_dir_resolves_relative_path() -> std::io::Result<()> {
|
||||
let current_dir = std::env::current_dir()?;
|
||||
let abs_path_buf = AbsolutePathBuf::relative_to_current_dir("file.txt")?;
|
||||
assert_eq!(
|
||||
abs_path_buf.as_path(),
|
||||
current_dir.join("file.txt").as_path()
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn guard_used_in_deserialization() {
|
||||
let temp_dir = tempdir().expect("base dir");
|
||||
|
||||
Reference in New Issue
Block a user