mirror of
https://github.com/openai/codex.git
synced 2026-11-13 14:46:14 +00:00
Add working-directory commands to the TUI (#38894)
## What changed - Add `/cd [path]` for changing an idle local session's working directory while preserving conversation history. Relative paths resolve from the current directory, and omitting the path selects `~`. - Reload project configuration, instructions, permissions, keybindings, file search, and hooks for the destination before replacing the active session. - Reject unsafe transitions, including active or queued work, background terminals, remote environments, untrusted destinations, and incompatible permission profiles. - Add `/pwd` and its `/cwd` alias to display the current working directory, and discard asynchronous results left over from the previous directory. ## Testing - Cover command parsing, aliases, default paths, session eligibility, transition failures, history preservation, destination policy loading, hooks, and stale events. GitOrigin-RevId: 1a987c029f157a087ba02c0f26e022260409132b
This commit is contained in:
1 parent
375996d3f5
commit
ed32158e90
18 files changed
+776
-24
No files matched your search
@@ -10,6 +10,7 @@ use crate::app_server_session::ForkGoalContinuation;
|
||||
use crate::config_update::format_config_error;
|
||||
use crate::external_agent_config_migration::flow::ExternalAgentConfigMigrationFlowOutcome;
|
||||
use crate::pager_overlay::TranscriptHistoryState;
|
||||
use crate::session_resume::cwds_differ;
|
||||
#[cfg(target_os = "windows")]
|
||||
use codex_config::types::WindowsSandboxModeToml;
|
||||
|
||||
@@ -23,6 +24,9 @@ impl App {
|
||||
event: AppEvent,
|
||||
) -> Result<AppRunControl> {
|
||||
match event {
|
||||
AppEvent::SkillsListLoaded { ref cwd, .. }
|
||||
| AppEvent::PluginMentionsLoaded { ref cwd, .. }
|
||||
if cwds_differ(cwd, self.config.cwd.as_path()) => {}
|
||||
AppEvent::NewSession { name } => {
|
||||
self.start_fresh_session_with_summary_hint(
|
||||
tui, app_server, /*session_start_source*/ None,
|
||||
@@ -30,6 +34,44 @@ impl App {
|
||||
)
|
||||
.await;
|
||||
}
|
||||
AppEvent::ChangeWorkingDirectory {
|
||||
thread_id,
|
||||
requested_cwd,
|
||||
} => {
|
||||
if self.primary_thread_id != Some(thread_id)
|
||||
|| !self.chat_widget.can_change_working_directory(thread_id)
|
||||
{
|
||||
self.chat_widget.add_error_message(
|
||||
"Changing directories requires an idle primary session without queued input."
|
||||
.to_string(),
|
||||
);
|
||||
} else if crate::uses_remote_workspace_or_environment(
|
||||
&self.app_server_target,
|
||||
self.environment_manager.as_ref(),
|
||||
) {
|
||||
self.chat_widget.add_error_message(
|
||||
"Changing directories is not supported for remote workspaces or remote execution environments."
|
||||
.to_string(),
|
||||
);
|
||||
} else {
|
||||
let cwd = AbsolutePathBuf::resolve_path_against_base(
|
||||
requested_cwd.as_path(),
|
||||
self.chat_widget.config_ref().cwd.as_path(),
|
||||
);
|
||||
match std::fs::metadata(cwd.as_path()) {
|
||||
Ok(metadata) if metadata.is_dir() => {
|
||||
self.change_working_directory(tui, app_server, cwd).await;
|
||||
}
|
||||
Ok(_) => self
|
||||
.chat_widget
|
||||
.add_error_message(format!("Not a directory: {}", cwd.display())),
|
||||
Err(error) => self.chat_widget.add_error_message(format!(
|
||||
"Cannot access directory {}: {error}",
|
||||
cwd.display()
|
||||
)),
|
||||
}
|
||||
}
|
||||
}
|
||||
AppEvent::StartupThreadStarted { result } => {
|
||||
self.handle_startup_thread_started(app_server, result)
|
||||
.await?;
|
||||
@@ -657,7 +699,11 @@ impl App {
|
||||
AppEvent::AppendMessageHistoryEntry { thread_id, text } => {
|
||||
self.append_message_history_entry(thread_id, text);
|
||||
}
|
||||
AppEvent::SyncThreadGitBranch { thread_id, branch } => {
|
||||
AppEvent::SyncThreadGitBranch {
|
||||
thread_id,
|
||||
branch,
|
||||
cwd: _cwd,
|
||||
} => {
|
||||
if let Err(err) = app_server
|
||||
.thread_metadata_update_branch(thread_id, branch)
|
||||
.await
|
||||
@@ -692,7 +738,10 @@ impl App {
|
||||
self.enqueue_thread_history_entry_response(thread_id, event)
|
||||
.await?;
|
||||
}
|
||||
AppEvent::DiffResult(text) => {
|
||||
AppEvent::DiffResult(cwd, text) => {
|
||||
if cwds_differ(&cwd, self.chat_widget.config_ref().cwd.as_path()) {
|
||||
return Ok(AppRunControl::Continue);
|
||||
}
|
||||
// Clear the in-progress state in the bottom pane
|
||||
self.chat_widget.on_diff_complete();
|
||||
// Enter alternate screen using TUI helper and build pager lines
|
||||
@@ -1028,7 +1077,7 @@ impl App {
|
||||
} => {
|
||||
self.handle_mcp_inventory_result(result, detail, thread_id);
|
||||
}
|
||||
AppEvent::SkillsListLoaded { result } => {
|
||||
AppEvent::SkillsListLoaded { result, .. } => {
|
||||
self.handle_skills_list_result(
|
||||
result.map_err(|err| color_eyre::eyre::eyre!(err)),
|
||||
"failed to load skills on startup",
|
||||
@@ -1959,7 +2008,7 @@ impl App {
|
||||
AppEvent::RefreshPluginMentions => {
|
||||
self.refresh_plugin_mentions(app_server);
|
||||
}
|
||||
AppEvent::PluginMentionsLoaded { mut plugins } => {
|
||||
AppEvent::PluginMentionsLoaded { mut plugins, .. } => {
|
||||
if !self.config.features.enabled(Feature::Plugins) {
|
||||
plugins = None;
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user