Files
codex/codex-rs/tui/src/chatwidget/session_flow.rs
T
Eric Traut 83d1fe0e67 Remove the Plan mode composer nudge (#40200)
## What changed

- Keep the normal composer footer visible when a draft contains the word `plan`.
- Remove the contextual “Create a plan?” prompt, its Escape dismissal behavior, and its per-thread dismissal state.

## Testing

- Update wide and narrow composer snapshots to cover the standard footer for plan-related drafts.

GitOrigin-RevId: 1a3c8cfce9d4effa2149790975133f652aa7c914
2026-08-23 07:24:57 +00:00

269 lines
11 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
//! Session configuration and thread-header orchestration for `ChatWidget`.
use super::*;
impl ChatWidget {
fn on_session_configured_with_display_and_fork_parent_title(
&mut self,
session: ThreadSessionState,
display: SessionConfiguredDisplay,
fork_parent_title: Option<String>,
) {
self.transcript.reset_copy_history();
let history_metadata = session.message_history.unwrap_or_default();
self.bottom_pane.set_history_metadata(
session.thread_id,
history_metadata.log_id,
history_metadata.entry_count,
);
self.set_skills(/*skills*/ None);
self.session_network_proxy = session.network_proxy.clone();
let previous_thread_id = self.thread_id;
let connector_scope_changed = previous_thread_id != Some(session.thread_id)
|| self.config.cwd.as_path() != session.cwd.as_path();
self.thread_id = Some(session.thread_id);
self.bottom_pane
.set_queue_submissions(/*queue_submissions*/ false);
if previous_thread_id != self.thread_id {
self.review.recent_auto_review_denials = RecentAutoReviewDenials::default();
self.clear_thread_usage_state();
}
self.turn_lifecycle.reset_thread();
self.clear_safety_buffering();
self.thread_name = session.thread_name.clone();
self.current_goal_status_indicator = None;
self.current_goal_status = None;
self.update_collaboration_mode_indicator();
self.forked_from = session.forked_from_id;
self.current_rollout_path = session.rollout_path.clone();
self.current_cwd = Some(session.cwd.to_path_buf());
self.config.cwd = session.cwd.clone();
if connector_scope_changed {
self.invalidate_connector_scope();
}
let runtime_workspace_roots = session.runtime_workspace_roots.clone();
self.config.workspace_roots = runtime_workspace_roots.clone();
self.config
.permissions
.set_workspace_roots(runtime_workspace_roots);
self.effective_service_tier = session.service_tier.clone();
if let Err(err) = self
.config
.permissions
.approval_policy
.set(session.approval_policy.to_core())
{
tracing::warn!(%err, "failed to sync approval_policy from SessionConfigured");
self.config.permissions.approval_policy =
Constrained::allow_only(session.approval_policy.to_core());
}
let permission_snapshot = PermissionProfileSnapshot::from_session_snapshot(
session.permission_profile.clone(),
session.active_permission_profile.clone(),
);
let permission_sync = self
.config
.permissions
.set_permission_profile_from_session_snapshot(permission_snapshot.clone());
if let Err(err) = permission_sync {
tracing::warn!(%err, "failed to sync permissions from SessionConfigured");
if let Err(replace_err) = self
.config
.permissions
.replace_permission_profile_from_session_snapshot(permission_snapshot)
{
tracing::error!(
%replace_err,
"failed to replace permissions from SessionConfigured after constraint fallback"
);
}
}
self.config.approvals_reviewer = session.approvals_reviewer;
self.config.personality = session.personality;
self.status_line_project_root_name_cache = None;
let forked_from_id = session.forked_from_id;
let default_model = session.model.clone();
self.current_collaboration_mode = self.current_collaboration_mode.with_updates(
Some(default_model.clone()),
Some(session.reasoning_effort.clone()),
/*developer_instructions*/ None,
);
if session.reasoning_effort == Some(ReasoningEffortConfig::Ultra) {
self.set_plan_mode_reasoning_effort(Some(ReasoningEffortConfig::Ultra));
}
match session.collaboration_mode.as_deref() {
Some(collaboration_mode) => {
self.set_effective_collaboration_mode(collaboration_mode.clone());
}
None => {
self.active_collaboration_mask = Self::initial_collaboration_mask(
&self.config,
self.model_catalog.as_ref(),
Some(&default_model),
);
if let Some(mask) = self.active_collaboration_mask.as_mut() {
mask.reasoning_effort = Some(session.reasoning_effort.clone());
}
self.update_collaboration_mode_indicator();
}
}
let effort = self.effective_reasoning_effort();
self.bottom_pane
.set_active_reasoning_effort_baseline(effort.as_ref());
self.refresh_model_display();
self.refresh_status_surfaces();
self.sync_service_tier_commands();
self.sync_personality_command_enabled();
self.sync_plugins_command_enabled();
self.sync_goal_command_enabled();
self.refresh_plugin_mentions();
let model_for_header = self.current_model().to_string();
if matches!(
display,
SessionConfiguredDisplay::Normal | SessionConfiguredDisplay::PromptEdit
) {
let startup_tooltip_override = self.startup_tooltip_override.take();
let show_fast_status = self
.should_show_fast_status(&model_for_header, self.effective_service_tier.as_deref());
let session_info_cell = history_cell::new_session_info(
&self.config,
&model_for_header,
&session,
self.show_welcome_banner,
startup_tooltip_override,
self.plan_type,
show_fast_status,
);
self.apply_session_info_cell(session_info_cell);
} else if self
.transcript
.active_cell
.as_ref()
.is_some_and(|cell| cell.as_any().is::<history_cell::SessionHeaderHistoryCell>())
{
self.transcript.active_cell = None;
self.bump_active_cell_revision();
}
self.transcript.saw_copy_source_this_turn = false;
self.refresh_skills_for_current_cwd(/*force_reload*/ true);
self.refresh_connector_mentions(/*force_refresh*/ false);
let initial_user_message_pending = self.initial_user_message.is_some();
self.submit_initial_user_message_if_pending();
if self.mcp_startup_status.is_none()
&& (!initial_user_message_pending || self.is_user_turn_pending_or_running())
{
self.maybe_send_next_queued_input();
}
if display == SessionConfiguredDisplay::Normal
&& let Some(forked_from_id) = forked_from_id
{
self.emit_forked_thread_event(forked_from_id, fork_parent_title);
}
if !self.suppress_session_configured_redraw {
self.request_redraw();
}
}
pub(crate) fn handle_thread_session(&mut self, session: ThreadSessionState) {
self.instruction_source_paths = session.instruction_source_paths.clone();
let fork_parent_title = session.fork_parent_title.clone();
self.on_session_configured_with_display_and_fork_parent_title(
session,
SessionConfiguredDisplay::Normal,
fork_parent_title,
);
}
pub(crate) fn handle_thread_session_quiet(&mut self, session: ThreadSessionState) {
self.instruction_source_paths = session.instruction_source_paths.clone();
self.on_session_configured_with_display_and_fork_parent_title(
session,
SessionConfiguredDisplay::Quiet,
/*fork_parent_title*/ None,
);
}
pub(crate) fn handle_prompt_edit_thread_session(&mut self, session: ThreadSessionState) {
self.instruction_source_paths = session.instruction_source_paths.clone();
let fork_parent_title = session.fork_parent_title.clone();
self.on_session_configured_with_display_and_fork_parent_title(
session,
SessionConfiguredDisplay::PromptEdit,
fork_parent_title,
);
}
pub(crate) fn handle_side_thread_session(&mut self, session: ThreadSessionState) {
self.instruction_source_paths = session.instruction_source_paths.clone();
let fork_parent_title = session.fork_parent_title.clone();
self.on_session_configured_with_display_and_fork_parent_title(
session,
SessionConfiguredDisplay::SideConversation,
fork_parent_title,
);
}
pub(super) fn emit_forked_thread_event(
&mut self,
forked_from_id: ThreadId,
fork_parent_title: Option<String>,
) {
let forked_from_id_text = forked_from_id.to_string();
let line: Line<'static> = if let Some(name) = fork_parent_title
&& !name.trim().is_empty()
{
vec![
"• ".dim(),
"Thread forked from ".into(),
name.cyan(),
" (".into(),
forked_from_id_text.cyan(),
")".into(),
]
.into()
} else {
vec![
"• ".dim(),
"Thread forked from ".into(),
forked_from_id_text.cyan(),
]
.into()
};
self.app_event_tx.send(AppEvent::InsertHistoryCell(Box::new(
PlainHistoryCell::new(vec![line]),
)));
}
pub(crate) fn emit_prompt_edit_thread_event(&mut self) {
let line: Line<'static> = vec![
"• ".dim(),
"You’re continuing from this point in a new conversation".into(),
]
.into();
self.app_event_tx.send(AppEvent::InsertHistoryCell(Box::new(
PlainHistoryCell::new(vec![line]),
)));
}
pub(super) fn on_thread_name_updated(
&mut self,
thread_id: ThreadId,
thread_name: Option<String>,
) {
if self.thread_id == Some(thread_id) {
if let Some(name) = thread_name.as_deref() {
let cell = Self::rename_confirmation_cell(name, self.thread_id);
self.add_boxed_history(Box::new(cell));
}
self.thread_name = thread_name;
self.refresh_status_surfaces();
self.request_redraw();
self.maybe_send_next_queued_input();
}
}
pub(super) fn set_skills(&mut self, skills: Option<Vec<SkillMetadata>>) {
self.bottom_pane.set_skills(skills);
}
}