mirror of
https://github.com/openai/codex.git
synced 2026-11-14 21:40:39 +00:00
Show the primary executor's workspace in TUI status surfaces (#61405)
## Why The primary executor's working directory can differ from the app-server's. Displaying the app-server directory can misidentify the workspace, and abbreviating a remote path using the TUI's home directory can be misleading. ## What changed - Carry the primary environment's working directory through thread initialization, restoration, and settings updates. - Use it for the status line and terminal title's current-directory item, and expose it as `thread.workspace.cwd` in the statusline state. - Preserve the known environment directory when settings omit environments; fall back to the app-server directory when the environment list is empty. - Apply home-directory abbreviation only to server-local workspaces on local connections. ## Testing Add regression coverage for remote executor paths, omitted and empty environment settings, local-only directory abbreviation, and workspace state for inactive threads. Update the resumed-thread snapshot to display the executor directory. GitOrigin-RevId: 9f3bcad186e07f942ea00d1f4233cf09f022571f
This commit is contained in:
@@ -1944,6 +1944,7 @@ enabled = false
|
||||
.handle_thread_session(crate::session_state::ThreadSessionState {
|
||||
read_only_history: false,
|
||||
thread_settings_confirmed: true,
|
||||
primary_environment_cwd: None,
|
||||
daybreak_enabled: false,
|
||||
windows_sandbox_host: crate::app::WindowsSandboxHost::Local,
|
||||
thread_id: ThreadId::new(),
|
||||
|
||||
@@ -37,6 +37,7 @@ pub(in crate::app) fn attach_thread(app: &mut App, thread_id: ThreadId) {
|
||||
app.chat_widget.handle_thread_session(ThreadSessionState {
|
||||
read_only_history: false,
|
||||
thread_settings_confirmed: true,
|
||||
primary_environment_cwd: None,
|
||||
daybreak_enabled: false,
|
||||
windows_sandbox_host: crate::app::WindowsSandboxHost::Local,
|
||||
thread_id,
|
||||
|
||||
@@ -7518,7 +7518,7 @@ async fn inactive_thread_started_notification_initializes_replay_session() -> Re
|
||||
originator: None,
|
||||
environments: Some(vec![codex_app_server_protocol::ThreadEnvironment {
|
||||
environment_id: "remote".to_string(),
|
||||
cwd: test_path_buf("/tmp/agent").abs().into(),
|
||||
cwd: codex_utils_path_uri::LegacyAppPathString::from_string("/executor/agent"),
|
||||
runtime_workspace_roots: Vec::new(),
|
||||
}]),
|
||||
id: agent_thread_id.to_string(),
|
||||
@@ -7596,6 +7596,7 @@ async fn inactive_thread_started_notification_initializes_replay_session() -> Re
|
||||
app.chat_widget.handle_thread_session(session);
|
||||
let state = serde_json::to_value(app.chat_widget.statusline_state())?;
|
||||
let thread = &state["thread"];
|
||||
assert_eq!(thread["workspace"]["cwd"], "/executor/agent");
|
||||
assert_eq!(thread["model"]["id"], "gpt-agent");
|
||||
assert_eq!(thread["model"]["reasoning_level"], "low");
|
||||
assert_eq!(thread["model"]["access_program"], "daybreak_blue");
|
||||
@@ -8871,6 +8872,7 @@ async fn render_clear_ui_header_after_long_transcript_for_snapshot() -> String {
|
||||
let session = ThreadSessionState {
|
||||
read_only_history: false,
|
||||
thread_settings_confirmed: true,
|
||||
primary_environment_cwd: None,
|
||||
daybreak_enabled: false,
|
||||
windows_sandbox_host: crate::app::WindowsSandboxHost::Local,
|
||||
thread_id: ThreadId::new(),
|
||||
@@ -9586,6 +9588,7 @@ pub(super) fn test_thread_session(thread_id: ThreadId, cwd: PathBuf) -> ThreadSe
|
||||
ThreadSessionState {
|
||||
read_only_history: false,
|
||||
thread_settings_confirmed: true,
|
||||
primary_environment_cwd: None,
|
||||
daybreak_enabled: false,
|
||||
windows_sandbox_host: crate::app::WindowsSandboxHost::Local,
|
||||
thread_id,
|
||||
@@ -10904,6 +10907,7 @@ async fn backtrack_selection_preserves_selected_prompt_and_requests_branch() {
|
||||
let session = ThreadSessionState {
|
||||
read_only_history: false,
|
||||
thread_settings_confirmed: true,
|
||||
primary_environment_cwd: None,
|
||||
daybreak_enabled: false,
|
||||
windows_sandbox_host: crate::app::WindowsSandboxHost::Local,
|
||||
thread_id: ThreadId::new(),
|
||||
@@ -10979,6 +10983,7 @@ async fn backtrack_selection_preserves_selected_prompt_and_requests_branch() {
|
||||
.handle_thread_session(crate::session_state::ThreadSessionState {
|
||||
read_only_history: false,
|
||||
thread_settings_confirmed: true,
|
||||
primary_environment_cwd: None,
|
||||
daybreak_enabled: false,
|
||||
windows_sandbox_host: crate::app::WindowsSandboxHost::Local,
|
||||
thread_id: base_id,
|
||||
@@ -12377,6 +12382,7 @@ async fn new_session_requests_shutdown_for_previous_conversation() {
|
||||
let event = crate::session_state::ThreadSessionState {
|
||||
read_only_history: false,
|
||||
thread_settings_confirmed: true,
|
||||
primary_environment_cwd: None,
|
||||
daybreak_enabled: false,
|
||||
windows_sandbox_host: crate::app::WindowsSandboxHost::Local,
|
||||
thread_id,
|
||||
@@ -13158,6 +13164,7 @@ async fn clear_only_ui_reset_preserves_chat_session_state() {
|
||||
.handle_thread_session(crate::session_state::ThreadSessionState {
|
||||
read_only_history: false,
|
||||
thread_settings_confirmed: true,
|
||||
primary_environment_cwd: None,
|
||||
daybreak_enabled: false,
|
||||
windows_sandbox_host: crate::app::WindowsSandboxHost::Local,
|
||||
thread_id,
|
||||
|
||||
@@ -746,6 +746,7 @@ mod tests {
|
||||
ThreadSessionState {
|
||||
read_only_history: false,
|
||||
thread_settings_confirmed: true,
|
||||
primary_environment_cwd: None,
|
||||
daybreak_enabled: false,
|
||||
windows_sandbox_host: crate::app::WindowsSandboxHost::Local,
|
||||
thread_id,
|
||||
|
||||
@@ -1432,6 +1432,13 @@ impl App {
|
||||
let mut session = self.primary_session_configured.clone()?;
|
||||
session.read_only_history = false;
|
||||
session.thread_settings_confirmed = false;
|
||||
session.primary_environment_cwd = crate::session_state::primary_environment_cwd(
|
||||
notification
|
||||
.thread
|
||||
.environments
|
||||
.as_deref()
|
||||
.unwrap_or_default(),
|
||||
);
|
||||
session.thread_id = thread_id;
|
||||
// The primary session supplies defaults. The new thread supplies its own fork parent.
|
||||
session.set_fork_parent(notification.thread.forked_from_id.as_deref());
|
||||
|
||||
@@ -83,6 +83,7 @@ impl App {
|
||||
// `thread/read` does not include all thread settings, so do not carry
|
||||
// thread-scoped state from the currently active session.
|
||||
session.thread_settings_confirmed = false;
|
||||
session.primary_environment_cwd = None;
|
||||
session.collaboration_mode = None;
|
||||
session.daybreak_enabled = false;
|
||||
}
|
||||
@@ -91,6 +92,7 @@ impl App {
|
||||
ThreadSessionState {
|
||||
read_only_history: true,
|
||||
thread_settings_confirmed: false,
|
||||
primary_environment_cwd: None,
|
||||
daybreak_enabled: false,
|
||||
windows_sandbox_host: crate::app::WindowsSandboxHost::Local,
|
||||
thread_id,
|
||||
@@ -116,6 +118,10 @@ impl App {
|
||||
rollout_path: thread.path.clone(),
|
||||
}
|
||||
};
|
||||
if let Some(environments) = &thread.environments {
|
||||
session.primary_environment_cwd =
|
||||
crate::session_state::primary_environment_cwd(environments);
|
||||
}
|
||||
session.windows_sandbox_host =
|
||||
crate::windows_sandbox::host_from_environments(thread.environments.as_deref());
|
||||
session.thread_id = thread_id;
|
||||
@@ -180,6 +186,7 @@ mod tests {
|
||||
ThreadSessionState {
|
||||
read_only_history: false,
|
||||
thread_settings_confirmed: true,
|
||||
primary_environment_cwd: None,
|
||||
daybreak_enabled: false,
|
||||
windows_sandbox_host: crate::app::WindowsSandboxHost::Local,
|
||||
thread_id,
|
||||
|
||||
@@ -290,6 +290,10 @@ pub(super) fn apply_thread_settings_to_session(
|
||||
settings.cwd.as_path(),
|
||||
);
|
||||
session.active_permission_profile = settings.active_permission_profile.clone().map(Into::into);
|
||||
if let Some(environments) = &settings.environments {
|
||||
session.primary_environment_cwd =
|
||||
crate::session_state::primary_environment_cwd(environments);
|
||||
}
|
||||
session.set_cwd_retargeting_implicit_runtime_workspace_root(settings.cwd.clone());
|
||||
let mut collaboration_mode = settings.collaboration_mode.clone();
|
||||
collaboration_mode
|
||||
|
||||
@@ -2973,7 +2973,7 @@ async fn thread_session_state_from_thread_start_response(
|
||||
);
|
||||
let mut session = thread_session_state_from_thread_response(
|
||||
&response.thread.id,
|
||||
crate::windows_sandbox::host_from_environments(response.thread.environments.as_deref()),
|
||||
response.thread.environments.as_deref(),
|
||||
response.thread.forked_from_id.clone(),
|
||||
response.thread.name.clone(),
|
||||
response.thread.path.clone(),
|
||||
@@ -3011,7 +3011,7 @@ async fn thread_session_state_from_thread_resume_response(
|
||||
);
|
||||
let mut session = thread_session_state_from_thread_response(
|
||||
&response.thread.id,
|
||||
crate::windows_sandbox::host_from_environments(response.thread.environments.as_deref()),
|
||||
response.thread.environments.as_deref(),
|
||||
response.thread.forked_from_id.clone(),
|
||||
response.thread.name.clone(),
|
||||
response.thread.path.clone(),
|
||||
@@ -3048,7 +3048,7 @@ async fn thread_session_state_from_thread_fork_response(
|
||||
);
|
||||
let mut session = thread_session_state_from_thread_response(
|
||||
&response.thread.id,
|
||||
crate::windows_sandbox::host_from_environments(response.thread.environments.as_deref()),
|
||||
response.thread.environments.as_deref(),
|
||||
response.thread.forked_from_id.clone(),
|
||||
response.thread.name.clone(),
|
||||
response.thread.path.clone(),
|
||||
@@ -3101,7 +3101,7 @@ fn display_permission_profile_from_thread_response(
|
||||
)]
|
||||
async fn thread_session_state_from_thread_response(
|
||||
thread_id: &str,
|
||||
windows_sandbox_host: crate::app::WindowsSandboxHost,
|
||||
environments: Option<&[codex_app_server_protocol::ThreadEnvironment]>,
|
||||
forked_from_id: Option<String>,
|
||||
thread_name: Option<String>,
|
||||
rollout_path: Option<PathBuf>,
|
||||
@@ -3134,7 +3134,10 @@ async fn thread_session_state_from_thread_response(
|
||||
read_only_history: false,
|
||||
thread_settings_confirmed: true,
|
||||
daybreak_enabled: false,
|
||||
windows_sandbox_host,
|
||||
windows_sandbox_host: crate::windows_sandbox::host_from_environments(environments),
|
||||
primary_environment_cwd: crate::session_state::primary_environment_cwd(
|
||||
environments.unwrap_or_default(),
|
||||
),
|
||||
thread_id,
|
||||
forked_from_id,
|
||||
fork_parent_title: None,
|
||||
@@ -5046,7 +5049,11 @@ mod tests {
|
||||
disabled_plugin_ids: Vec::new(),
|
||||
thread: codex_app_server_protocol::Thread {
|
||||
originator: None,
|
||||
environments: None,
|
||||
environments: Some(vec![codex_app_server_protocol::ThreadEnvironment {
|
||||
environment_id: "remote".into(),
|
||||
cwd: LegacyAppPathString::from_string("/executor/work"),
|
||||
runtime_workspace_roots: Vec::new(),
|
||||
}]),
|
||||
id: thread_id.to_string(),
|
||||
extra: None,
|
||||
session_id: ThreadId::new().to_string(),
|
||||
@@ -5345,7 +5352,7 @@ mod tests {
|
||||
|
||||
let session = thread_session_state_from_thread_response(
|
||||
&thread_id.to_string(),
|
||||
crate::app::WindowsSandboxHost::Local,
|
||||
/*environments*/ None,
|
||||
/*forked_from_id*/ None,
|
||||
Some("restore".to_string()),
|
||||
/*rollout_path*/ None,
|
||||
@@ -5381,7 +5388,7 @@ mod tests {
|
||||
|
||||
let session = thread_session_state_from_thread_response(
|
||||
&thread_id.to_string(),
|
||||
crate::app::WindowsSandboxHost::Local,
|
||||
/*environments*/ None,
|
||||
Some(forked_from_id.to_string()),
|
||||
Some("restore".to_string()),
|
||||
/*rollout_path*/ None,
|
||||
|
||||
@@ -78,7 +78,7 @@ impl AppServerSession {
|
||||
}
|
||||
let mut session = thread_session_state_from_thread_response(
|
||||
&thread.id,
|
||||
crate::windows_sandbox::host_from_environments(thread.environments.as_deref()),
|
||||
thread.environments.as_deref(),
|
||||
thread.forked_from_id.clone(),
|
||||
thread.name.clone(),
|
||||
thread.path.clone(),
|
||||
|
||||
@@ -744,6 +744,7 @@ pub(crate) struct ChatWidget {
|
||||
current_rollout_path: Option<PathBuf>,
|
||||
// Current working directory (if known)
|
||||
current_cwd: Option<PathBuf>,
|
||||
primary_environment_cwd: Option<crate::session_state::EnvironmentCwd>,
|
||||
// App-server-backed command runner for status-line workspace metadata lookups.
|
||||
workspace_command_runner: Option<WorkspaceCommandRunner>,
|
||||
// Instruction source files loaded for the current session, supplied by app-server.
|
||||
|
||||
@@ -276,6 +276,7 @@ impl ChatWidget {
|
||||
feedback,
|
||||
current_rollout_path: None,
|
||||
current_cwd,
|
||||
primary_environment_cwd: None,
|
||||
workspace_command_runner,
|
||||
instruction_source_paths: Vec::new(),
|
||||
session_network_proxy: None,
|
||||
|
||||
@@ -142,6 +142,8 @@ impl ChatWidget {
|
||||
self.update_collaboration_mode_indicator();
|
||||
self.forked_from = session.forked_from_id;
|
||||
self.current_rollout_path = session.rollout_path.clone();
|
||||
self.primary_environment_cwd
|
||||
.clone_from(&session.primary_environment_cwd);
|
||||
self.current_cwd = Some(session.cwd.to_path_buf());
|
||||
self.config.cwd = session.cwd.clone();
|
||||
self.config.model_provider_id = session.model_provider_id.clone();
|
||||
|
||||
@@ -529,6 +529,10 @@ impl ChatWidget {
|
||||
fn apply_thread_settings(&mut self, mut settings: ThreadSettings) {
|
||||
self.invalidate_permission_discovery();
|
||||
let cwd_changed = self.config.cwd != settings.cwd;
|
||||
if let Some(environments) = &settings.environments {
|
||||
self.primary_environment_cwd =
|
||||
crate::session_state::primary_environment_cwd(environments);
|
||||
}
|
||||
self.apply_thread_settings_cwd(settings.cwd.clone());
|
||||
self.config.model_provider_id = settings.model_provider.clone();
|
||||
self.set_daybreak_enabled(self.daybreak_enabled);
|
||||
|
||||
@@ -509,6 +509,31 @@ impl ChatWidget {
|
||||
.unwrap_or(self.config.cwd.as_path())
|
||||
}
|
||||
|
||||
pub(super) fn status_line_workspace_cwd(&self) -> &Path {
|
||||
self.primary_environment_cwd
|
||||
.as_ref()
|
||||
.map(|cwd| Path::new(cwd.path.as_str()))
|
||||
.unwrap_or_else(|| self.status_line_cwd())
|
||||
}
|
||||
|
||||
fn workspace_directory_display(&self) -> String {
|
||||
let cwd = self.status_line_workspace_cwd();
|
||||
// Only a server-local path on a local connection shares the TUI's home.
|
||||
if self
|
||||
.remote_connection
|
||||
.as_ref()
|
||||
.is_none_or(|connection| connection.is_local_daemon)
|
||||
&& self
|
||||
.primary_environment_cwd
|
||||
.as_ref()
|
||||
.is_none_or(|cwd| cwd.is_server_local)
|
||||
{
|
||||
format_directory_display(cwd, /*max_width*/ None)
|
||||
} else {
|
||||
cwd.display().to_string()
|
||||
}
|
||||
}
|
||||
|
||||
/// Resolves the project root associated with `cwd`.
|
||||
///
|
||||
/// Git repository root wins when available. Otherwise we fall back to the
|
||||
@@ -735,12 +760,7 @@ impl ChatWidget {
|
||||
StatusLineItem::Provider => self
|
||||
.thread_id
|
||||
.map(|_| format!("provider: {}", self.config.model_provider_id)),
|
||||
StatusLineItem::CurrentDir => {
|
||||
Some(format_directory_display(
|
||||
self.status_line_cwd(),
|
||||
/*max_width*/ None,
|
||||
))
|
||||
}
|
||||
StatusLineItem::CurrentDir => Some(self.workspace_directory_display()),
|
||||
StatusLineItem::ProjectRoot => self.status_line_project_root_name(),
|
||||
StatusLineItem::Hostname => os_host_name(),
|
||||
StatusLineItem::GitBranch => self.status_line_branch.clone(),
|
||||
@@ -909,7 +929,7 @@ impl ChatWidget {
|
||||
TerminalTitleItem::AppName => Some("codex".to_string()),
|
||||
TerminalTitleItem::Project => self.terminal_title_project_name(),
|
||||
TerminalTitleItem::CurrentDir => Some(Self::truncate_terminal_title_part(
|
||||
format_directory_display(self.status_line_cwd(), /*max_width*/ None),
|
||||
self.workspace_directory_display(),
|
||||
/*max_chars*/ 32,
|
||||
)),
|
||||
TerminalTitleItem::Spinner => self.terminal_title_spinner_text_at(now),
|
||||
|
||||
@@ -43,12 +43,18 @@ struct StatuslineStateThread {
|
||||
name: Option<String>,
|
||||
agent: StatuslineStateAgent,
|
||||
model: StatuslineStateModel,
|
||||
workspace: StatuslineStateWorkspace,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
approval: Option<StatuslineStateApproval>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
permissions: Option<StatuslineStatePermissions>,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
struct StatuslineStateWorkspace {
|
||||
cwd: codex_utils_path_uri::LegacyAppPathString,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
struct StatuslineStateAgent {
|
||||
state: StatuslineStateRunState,
|
||||
@@ -242,6 +248,11 @@ impl ChatWidget {
|
||||
name,
|
||||
agent,
|
||||
model,
|
||||
workspace: StatuslineStateWorkspace {
|
||||
cwd: codex_utils_path_uri::LegacyAppPathString::from_path(
|
||||
self.status_line_workspace_cwd(),
|
||||
),
|
||||
},
|
||||
approval,
|
||||
permissions,
|
||||
},
|
||||
|
||||
@@ -51,6 +51,7 @@ pub(super) fn configured_thread_session(
|
||||
crate::session_state::ThreadSessionState {
|
||||
read_only_history: false,
|
||||
thread_settings_confirmed: true,
|
||||
primary_environment_cwd: None,
|
||||
daybreak_enabled: false,
|
||||
windows_sandbox_host: crate::app::WindowsSandboxHost::Local,
|
||||
thread_id,
|
||||
@@ -686,6 +687,74 @@ async fn invalid_url_elicitation_is_declined() {
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn statusline_workspace_follows_primary_executor_and_legacy_settings() {
|
||||
let (mut chat, _events, _ops) = make_chatwidget_manual(Some("gpt-5.2")).await;
|
||||
let id = ThreadId::new();
|
||||
chat.handle_thread_session(configured_thread_session(id));
|
||||
let mut update = thread_settings_for_test("gpt-5.2", id);
|
||||
let host = update.thread_settings.cwd.to_string_lossy().into_owned();
|
||||
let remote = r"C:\executor\checkout";
|
||||
let primary = codex_app_server_protocol::ThreadEnvironment {
|
||||
environment_id: "remote".into(),
|
||||
cwd: codex_utils_path_uri::LegacyAppPathString::from_string(remote),
|
||||
runtime_workspace_roots: Vec::new(),
|
||||
};
|
||||
for (environments, expected) in [
|
||||
(Some(vec![primary]), remote),
|
||||
(None, remote),
|
||||
(Some(vec![]), host.as_str()),
|
||||
] {
|
||||
update.thread_settings.environments = environments;
|
||||
chat.handle_server_notification(
|
||||
ServerNotification::ThreadSettingsUpdated(update.clone()),
|
||||
/*replay_kind*/ None,
|
||||
);
|
||||
let state = serde_json::to_value(chat.statusline_state()).unwrap();
|
||||
assert_eq!(state["thread"]["workspace"]["cwd"], expected);
|
||||
}
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn statusline_and_title_abbreviate_only_the_local_workspace() {
|
||||
let (mut chat, _events, _ops) = make_chatwidget_manual(Some("gpt-5.2")).await;
|
||||
let id = ThreadId::new();
|
||||
chat.handle_thread_session(configured_thread_session(id));
|
||||
let mut update = thread_settings_for_test("gpt-5.2", id);
|
||||
let path = dirs::home_dir()
|
||||
.unwrap()
|
||||
.join("a-checkout-with-a-long-directory-name");
|
||||
let raw = path.display().to_string();
|
||||
let local = format_directory_display(&path, /*max_width*/ None);
|
||||
for (environment_id, expected) in [
|
||||
("remote", raw.as_str()),
|
||||
(codex_exec_server::LOCAL_ENVIRONMENT_ID, local.as_str()),
|
||||
] {
|
||||
update.thread_settings.environments =
|
||||
Some(vec![codex_app_server_protocol::ThreadEnvironment {
|
||||
environment_id: environment_id.into(),
|
||||
cwd: codex_utils_path_uri::LegacyAppPathString::from_path(&path),
|
||||
runtime_workspace_roots: Vec::new(),
|
||||
}]);
|
||||
chat.handle_server_notification(
|
||||
ServerNotification::ThreadSettingsUpdated(update.clone()),
|
||||
/*replay_kind*/ None,
|
||||
);
|
||||
assert_eq!(
|
||||
chat.status_line_value_for_item(StatusLineItem::CurrentDir)
|
||||
.as_deref(),
|
||||
Some(expected)
|
||||
);
|
||||
assert_eq!(
|
||||
chat.terminal_title_value_for_item(TerminalTitleItem::CurrentDir, Instant::now()),
|
||||
Some(ChatWidget::truncate_terminal_title_part(
|
||||
expected.into(),
|
||||
/*max_chars*/ 32
|
||||
))
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn thread_settings_updated_updates_visible_state_without_transcript() {
|
||||
let (mut chat, mut rx, _op_rx) = make_chatwidget_manual(Some("gpt-5.2")).await;
|
||||
|
||||
@@ -631,6 +631,7 @@ async fn submission_preserves_text_elements_and_local_images() {
|
||||
let configured = crate::session_state::ThreadSessionState {
|
||||
read_only_history: false,
|
||||
thread_settings_confirmed: true,
|
||||
primary_environment_cwd: None,
|
||||
daybreak_enabled: false,
|
||||
windows_sandbox_host: crate::app::WindowsSandboxHost::Local,
|
||||
thread_id,
|
||||
@@ -743,6 +744,7 @@ async fn submission_includes_configured_active_permission_profile() {
|
||||
let configured = crate::session_state::ThreadSessionState {
|
||||
read_only_history: false,
|
||||
thread_settings_confirmed: true,
|
||||
primary_environment_cwd: None,
|
||||
daybreak_enabled: false,
|
||||
windows_sandbox_host: crate::app::WindowsSandboxHost::Local,
|
||||
thread_id,
|
||||
@@ -801,6 +803,7 @@ async fn submission_omits_active_permission_profile_for_legacy_snapshot() {
|
||||
let configured = crate::session_state::ThreadSessionState {
|
||||
read_only_history: false,
|
||||
thread_settings_confirmed: true,
|
||||
primary_environment_cwd: None,
|
||||
daybreak_enabled: false,
|
||||
windows_sandbox_host: crate::app::WindowsSandboxHost::Local,
|
||||
thread_id,
|
||||
@@ -849,6 +852,7 @@ async fn submission_with_remote_and_local_images_keeps_local_placeholder_numberi
|
||||
let configured = crate::session_state::ThreadSessionState {
|
||||
read_only_history: false,
|
||||
thread_settings_confirmed: true,
|
||||
primary_environment_cwd: None,
|
||||
daybreak_enabled: false,
|
||||
windows_sandbox_host: crate::app::WindowsSandboxHost::Local,
|
||||
thread_id,
|
||||
@@ -952,6 +956,7 @@ async fn enter_with_only_remote_images_submits_user_turn() {
|
||||
let configured = crate::session_state::ThreadSessionState {
|
||||
read_only_history: false,
|
||||
thread_settings_confirmed: true,
|
||||
primary_environment_cwd: None,
|
||||
daybreak_enabled: false,
|
||||
windows_sandbox_host: crate::app::WindowsSandboxHost::Local,
|
||||
thread_id,
|
||||
@@ -1024,6 +1029,7 @@ async fn shift_enter_with_only_remote_images_does_not_submit_user_turn() {
|
||||
let configured = crate::session_state::ThreadSessionState {
|
||||
read_only_history: false,
|
||||
thread_settings_confirmed: true,
|
||||
primary_environment_cwd: None,
|
||||
daybreak_enabled: false,
|
||||
windows_sandbox_host: crate::app::WindowsSandboxHost::Local,
|
||||
thread_id,
|
||||
@@ -1068,6 +1074,7 @@ async fn enter_with_only_remote_images_does_not_submit_when_modal_is_active() {
|
||||
let configured = crate::session_state::ThreadSessionState {
|
||||
read_only_history: false,
|
||||
thread_settings_confirmed: true,
|
||||
primary_environment_cwd: None,
|
||||
daybreak_enabled: false,
|
||||
windows_sandbox_host: crate::app::WindowsSandboxHost::Local,
|
||||
thread_id,
|
||||
@@ -1112,6 +1119,7 @@ async fn enter_with_only_remote_images_does_not_submit_when_input_disabled() {
|
||||
let configured = crate::session_state::ThreadSessionState {
|
||||
read_only_history: false,
|
||||
thread_settings_confirmed: true,
|
||||
primary_environment_cwd: None,
|
||||
daybreak_enabled: false,
|
||||
windows_sandbox_host: crate::app::WindowsSandboxHost::Local,
|
||||
thread_id,
|
||||
@@ -1159,6 +1167,7 @@ async fn submission_prefers_selected_duplicate_skill_path() {
|
||||
let configured = crate::session_state::ThreadSessionState {
|
||||
read_only_history: false,
|
||||
thread_settings_confirmed: true,
|
||||
primary_environment_cwd: None,
|
||||
daybreak_enabled: false,
|
||||
windows_sandbox_host: crate::app::WindowsSandboxHost::Local,
|
||||
thread_id,
|
||||
|
||||
@@ -1273,6 +1273,7 @@ async fn bang_shell_enter_while_task_running_submits_run_user_shell_command() {
|
||||
let configured = crate::session_state::ThreadSessionState {
|
||||
read_only_history: false,
|
||||
thread_settings_confirmed: true,
|
||||
primary_environment_cwd: None,
|
||||
daybreak_enabled: false,
|
||||
windows_sandbox_host: crate::app::WindowsSandboxHost::Local,
|
||||
thread_id,
|
||||
|
||||
@@ -22,6 +22,7 @@ async fn resumed_initial_messages_render_history() {
|
||||
let mut configured = crate::session_state::ThreadSessionState {
|
||||
read_only_history: false,
|
||||
thread_settings_confirmed: true,
|
||||
primary_environment_cwd: None,
|
||||
daybreak_enabled: true,
|
||||
windows_sandbox_host: crate::app::WindowsSandboxHost::Local,
|
||||
thread_id,
|
||||
@@ -186,6 +187,7 @@ async fn restored_conversation_ultra_remains_selected_after_switching_to_plan()
|
||||
chat.handle_thread_session(crate::session_state::ThreadSessionState {
|
||||
read_only_history: false,
|
||||
thread_settings_confirmed: true,
|
||||
primary_environment_cwd: None,
|
||||
daybreak_enabled: false,
|
||||
windows_sandbox_host: crate::app::WindowsSandboxHost::Local,
|
||||
thread_id: ThreadId::new(),
|
||||
@@ -511,6 +513,7 @@ async fn replayed_user_message_preserves_text_elements_and_local_images() {
|
||||
let configured = crate::session_state::ThreadSessionState {
|
||||
read_only_history: false,
|
||||
thread_settings_confirmed: true,
|
||||
primary_environment_cwd: None,
|
||||
daybreak_enabled: false,
|
||||
windows_sandbox_host: crate::app::WindowsSandboxHost::Local,
|
||||
thread_id,
|
||||
@@ -586,6 +589,7 @@ async fn replayed_user_message_preserves_remote_image_urls() {
|
||||
let configured = crate::session_state::ThreadSessionState {
|
||||
read_only_history: false,
|
||||
thread_settings_confirmed: true,
|
||||
primary_environment_cwd: None,
|
||||
daybreak_enabled: false,
|
||||
windows_sandbox_host: crate::app::WindowsSandboxHost::Local,
|
||||
thread_id,
|
||||
@@ -691,6 +695,7 @@ async fn session_configured_syncs_widget_config_permissions_and_cwd() {
|
||||
let configured = crate::session_state::ThreadSessionState {
|
||||
read_only_history: false,
|
||||
thread_settings_confirmed: true,
|
||||
primary_environment_cwd: None,
|
||||
daybreak_enabled: false,
|
||||
windows_sandbox_host: crate::app::WindowsSandboxHost::Local,
|
||||
thread_id: ThreadId::new(),
|
||||
@@ -772,6 +777,7 @@ async fn session_configured_preserves_profile_workspace_roots() {
|
||||
let configured = crate::session_state::ThreadSessionState {
|
||||
read_only_history: false,
|
||||
thread_settings_confirmed: true,
|
||||
primary_environment_cwd: None,
|
||||
daybreak_enabled: false,
|
||||
windows_sandbox_host: crate::app::WindowsSandboxHost::Local,
|
||||
thread_id: ThreadId::new(),
|
||||
@@ -819,6 +825,7 @@ async fn session_configured_external_sandbox_keeps_external_runtime_policy() {
|
||||
let configured = crate::session_state::ThreadSessionState {
|
||||
read_only_history: false,
|
||||
thread_settings_confirmed: true,
|
||||
primary_environment_cwd: None,
|
||||
daybreak_enabled: false,
|
||||
windows_sandbox_host: crate::app::WindowsSandboxHost::Local,
|
||||
thread_id: ThreadId::new(),
|
||||
@@ -861,6 +868,7 @@ async fn replayed_user_message_with_only_remote_images_renders_history_cell() {
|
||||
let configured = crate::session_state::ThreadSessionState {
|
||||
read_only_history: false,
|
||||
thread_settings_confirmed: true,
|
||||
primary_environment_cwd: None,
|
||||
daybreak_enabled: false,
|
||||
windows_sandbox_host: crate::app::WindowsSandboxHost::Local,
|
||||
thread_id,
|
||||
@@ -924,6 +932,7 @@ async fn replayed_user_message_with_only_local_images_renders_history_cell() {
|
||||
let configured = crate::session_state::ThreadSessionState {
|
||||
read_only_history: false,
|
||||
thread_settings_confirmed: true,
|
||||
primary_environment_cwd: None,
|
||||
daybreak_enabled: false,
|
||||
windows_sandbox_host: crate::app::WindowsSandboxHost::Local,
|
||||
thread_id,
|
||||
@@ -1284,6 +1293,7 @@ async fn replayed_reasoning_item_preserves_summary_parts_and_hides_raw_reasoning
|
||||
chat.handle_thread_session(crate::session_state::ThreadSessionState {
|
||||
read_only_history: false,
|
||||
thread_settings_confirmed: true,
|
||||
primary_environment_cwd: None,
|
||||
daybreak_enabled: false,
|
||||
windows_sandbox_host: crate::app::WindowsSandboxHost::Local,
|
||||
thread_id: ThreadId::new(),
|
||||
@@ -1338,6 +1348,7 @@ async fn replayed_reasoning_item_shows_raw_reasoning_when_enabled() {
|
||||
chat.handle_thread_session(crate::session_state::ThreadSessionState {
|
||||
read_only_history: false,
|
||||
thread_settings_confirmed: true,
|
||||
primary_environment_cwd: None,
|
||||
daybreak_enabled: false,
|
||||
windows_sandbox_host: crate::app::WindowsSandboxHost::Local,
|
||||
thread_id: ThreadId::new(),
|
||||
|
||||
@@ -1164,6 +1164,7 @@ async fn required_windows_sandbox_setup_defers_configured_initial_prompt() {
|
||||
chat.handle_thread_session(crate::session_state::ThreadSessionState {
|
||||
read_only_history: false,
|
||||
thread_settings_confirmed: true,
|
||||
primary_environment_cwd: None,
|
||||
daybreak_enabled: false,
|
||||
windows_sandbox_host: crate::app::WindowsSandboxHost::Local,
|
||||
thread_id: ThreadId::new(),
|
||||
@@ -1616,6 +1617,7 @@ async fn permissions_selection_marks_auto_review_current_after_session_configure
|
||||
chat.handle_thread_session(crate::session_state::ThreadSessionState {
|
||||
read_only_history: false,
|
||||
thread_settings_confirmed: true,
|
||||
primary_environment_cwd: None,
|
||||
daybreak_enabled: false,
|
||||
windows_sandbox_host: crate::app::WindowsSandboxHost::Local,
|
||||
thread_id: ThreadId::new(),
|
||||
@@ -1672,6 +1674,7 @@ async fn permissions_selection_marks_auto_review_current_with_custom_workspace_w
|
||||
chat.handle_thread_session(crate::session_state::ThreadSessionState {
|
||||
read_only_history: false,
|
||||
thread_settings_confirmed: true,
|
||||
primary_environment_cwd: None,
|
||||
daybreak_enabled: false,
|
||||
windows_sandbox_host: crate::app::WindowsSandboxHost::Local,
|
||||
thread_id: ThreadId::new(),
|
||||
|
||||
@@ -18,6 +18,7 @@ fn plan_test_session(thread_id: ThreadId) -> crate::session_state::ThreadSession
|
||||
crate::session_state::ThreadSessionState {
|
||||
read_only_history: false,
|
||||
thread_settings_confirmed: true,
|
||||
primary_environment_cwd: None,
|
||||
daybreak_enabled: false,
|
||||
windows_sandbox_host: crate::app::WindowsSandboxHost::Local,
|
||||
thread_id,
|
||||
@@ -1233,6 +1234,7 @@ async fn submit_user_message_emits_structured_plugin_mentions_from_bindings() {
|
||||
let configured = crate::session_state::ThreadSessionState {
|
||||
read_only_history: false,
|
||||
thread_settings_confirmed: true,
|
||||
primary_environment_cwd: None,
|
||||
daybreak_enabled: false,
|
||||
windows_sandbox_host: crate::app::WindowsSandboxHost::Local,
|
||||
thread_id,
|
||||
|
||||
@@ -4721,6 +4721,7 @@ async fn session_configured_clears_goal_status_footer() {
|
||||
chat.handle_thread_session(crate::session_state::ThreadSessionState {
|
||||
read_only_history: false,
|
||||
thread_settings_confirmed: true,
|
||||
primary_environment_cwd: None,
|
||||
daybreak_enabled: false,
|
||||
windows_sandbox_host: crate::app::WindowsSandboxHost::Local,
|
||||
thread_id: ThreadId::new(),
|
||||
|
||||
@@ -13,6 +13,7 @@ async fn statusline_state_projects_the_displayed_settings() {
|
||||
let mut session = configured_thread_session(id);
|
||||
session.model = "gpt-5.5".into();
|
||||
session.thread_name = Some("Saved carts".into());
|
||||
let expected_cwd = session.cwd.to_path_buf();
|
||||
chat.handle_thread_session(session);
|
||||
chat.local_settings.tui.status_line = Some(vec!["status".into()]);
|
||||
let mut model = get_available_model(&chat, "gpt-5.5");
|
||||
@@ -32,6 +33,7 @@ async fn statusline_state_projects_the_displayed_settings() {
|
||||
"app": {"cli_version": CODEX_CLI_VERSION, "display_mode": "standard"},
|
||||
"config": {"statusline_items": ["run-state"]},
|
||||
"thread": {
|
||||
"workspace": {"cwd": expected_cwd},
|
||||
"id": id, "name": "Saved carts", "agent": {"state": "ready"},
|
||||
"model": {"id": "gpt-5.5", "display_name": "Test model", "provider": "openai",
|
||||
"reasoning_level": "none", "service_tier": "acme-priority", "service_tier_display_name": "Turbo"},
|
||||
|
||||
@@ -626,6 +626,7 @@ fn session_configured_event(model: &str) -> ThreadSessionState {
|
||||
ThreadSessionState {
|
||||
read_only_history: false,
|
||||
thread_settings_confirmed: true,
|
||||
primary_environment_cwd: None,
|
||||
daybreak_enabled: false,
|
||||
windows_sandbox_host: crate::app::WindowsSandboxHost::Local,
|
||||
thread_id: ThreadId::new(),
|
||||
|
||||
@@ -6,11 +6,13 @@
|
||||
use std::path::PathBuf;
|
||||
|
||||
use codex_app_server_protocol::AskForApproval;
|
||||
use codex_app_server_protocol::ThreadEnvironment;
|
||||
use codex_protocol::ThreadId;
|
||||
use codex_protocol::config_types::CollaborationMode;
|
||||
use codex_protocol::models::ActivePermissionProfile;
|
||||
use codex_protocol::models::PermissionProfile;
|
||||
use codex_utils_absolute_path::AbsolutePathBuf;
|
||||
use codex_utils_path_uri::LegacyAppPathString;
|
||||
use codex_utils_path_uri::PathUri;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
@@ -52,6 +54,8 @@ pub(crate) struct ThreadSessionState {
|
||||
/// when the server knows it.
|
||||
pub(crate) active_permission_profile: Option<ActivePermissionProfile>,
|
||||
pub(crate) cwd: AbsolutePathBuf,
|
||||
/// Native cwd of the primary executor, which can differ from the app-server cwd.
|
||||
pub(crate) primary_environment_cwd: Option<EnvironmentCwd>,
|
||||
pub(crate) runtime_workspace_roots: Vec<AbsolutePathBuf>,
|
||||
pub(crate) instruction_source_paths: Vec<PathUri>,
|
||||
pub(crate) reasoning_effort: Option<codex_protocol::openai_models::ReasoningEffort>,
|
||||
@@ -61,6 +65,21 @@ pub(crate) struct ThreadSessionState {
|
||||
pub(crate) rollout_path: Option<PathBuf>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub(crate) struct EnvironmentCwd {
|
||||
pub(crate) path: LegacyAppPathString,
|
||||
pub(crate) is_server_local: bool,
|
||||
}
|
||||
|
||||
pub(crate) fn primary_environment_cwd(
|
||||
environments: &[ThreadEnvironment],
|
||||
) -> Option<EnvironmentCwd> {
|
||||
environments.first().map(|primary| EnvironmentCwd {
|
||||
path: primary.cwd.clone(),
|
||||
is_server_local: primary.environment_id == codex_exec_server::LOCAL_ENVIRONMENT_ID,
|
||||
})
|
||||
}
|
||||
|
||||
impl ThreadSessionState {
|
||||
pub(crate) fn set_fork_parent(&mut self, parent: Option<&str>) {
|
||||
let parent = parent.and_then(|id| ThreadId::from_string(id).ok());
|
||||
|
||||
+1
-1
@@ -4,5 +4,5 @@ expression: "crate::chatwidget::tests::helpers::render_bottom_popup(&chat, 40)"
|
||||
---
|
||||
› Ask Codex to do anything
|
||||
|
||||
GPT-5.5 default · /tmp/project
|
||||
GPT-5.5 default · /executor/work
|
||||
Plan mode (⇧tab to cycle)
|
||||
|
||||
Reference in New Issue
Block a user