mirror of
https://github.com/openai/codex.git
synced 2026-11-13 14:46:14 +00:00
Branch conversations when editing earlier TUI prompts (#33201)
## What changed - Fork the conversation immediately before the selected prompt instead of rolling back the current thread in place, preserving the original conversation. - Reopen the selected prompt in the new conversation with its images, text elements, and mention bindings intact. - Start a fresh conversation when editing the first prompt, show a dedicated continuation notice, and restore the prompt with an error if branching fails. ## Testing - Cover branching before first and later prompts, source preservation, prompt restoration, hidden review prompts, mid-turn steering, in-progress turns, stale transcript entries, and mention bindings. GitOrigin-RevId: 15473535b3f9733b0300711a498969e6d389db79
This commit is contained in:
1 parent
72b41c55fb
commit
469ce0db51
17 files changed
+928
-549
No files matched your search
@@ -163,8 +163,6 @@ use codex_terminal_detection::terminal_info;
|
||||
use codex_utils_absolute_path::AbsolutePathBuf;
|
||||
use codex_utils_cli::resume_hint;
|
||||
use codex_utils_path_uri::PathUri;
|
||||
use codex_utils_plugins::mention_syntax::PLUGIN_TEXT_MENTION_SIGIL;
|
||||
use codex_utils_plugins::mention_syntax::TOOL_MENTION_SIGIL;
|
||||
use crossterm::event::KeyCode;
|
||||
use crossterm::event::KeyEvent;
|
||||
use crossterm::event::KeyEventKind;
|
||||
@@ -436,6 +434,7 @@ use self::user_messages::UserMessageHistoryOverride;
|
||||
use self::user_messages::UserMessageHistoryRecord;
|
||||
use self::user_messages::app_server_text_elements;
|
||||
pub(crate) use self::user_messages::create_initial_user_message;
|
||||
pub(crate) use self::user_messages::mention_bindings_from_user_inputs;
|
||||
use self::user_messages::merge_user_messages;
|
||||
use self::user_messages::merge_user_messages_with_history_record;
|
||||
#[cfg(test)]
|
||||
@@ -796,6 +795,7 @@ pub(crate) enum ReplayKind {
|
||||
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||
enum SessionConfiguredDisplay {
|
||||
Normal,
|
||||
PromptEdit,
|
||||
/// Apply session state without emitting the session info cell.
|
||||
Quiet,
|
||||
SideConversation,
|
||||
@@ -1262,69 +1262,13 @@ impl ChatWidget {
|
||||
if self.review.is_review_mode {
|
||||
return;
|
||||
}
|
||||
let message = display.message.as_str();
|
||||
let mention_start = |sigil: char, mention: &str| {
|
||||
let token = format!("{sigil}{mention}");
|
||||
message.match_indices(&token).find_map(|(start, _)| {
|
||||
let end = start + token.len();
|
||||
message
|
||||
.as_bytes()
|
||||
.get(end)
|
||||
.is_none_or(|byte| {
|
||||
!byte.is_ascii_alphanumeric() && !matches!(byte, b'_' | b'-')
|
||||
})
|
||||
.then_some(start)
|
||||
})
|
||||
};
|
||||
let mut mention_bindings: Vec<MentionBinding> = items
|
||||
.iter()
|
||||
.filter_map(|item| match item {
|
||||
UserInput::Skill { name, path } => Some(MentionBinding {
|
||||
sigil: TOOL_MENTION_SIGIL,
|
||||
mention: name.clone(),
|
||||
path: path.to_string_lossy().into_owned(),
|
||||
}),
|
||||
UserInput::Mention { name, path } => {
|
||||
let plugin_id = path.strip_prefix("plugin://");
|
||||
let mention = if let Some(plugin_id) = plugin_id {
|
||||
plugin_id
|
||||
.split_once('@')
|
||||
.map(|(plugin_name, _)| plugin_name)
|
||||
.unwrap_or(plugin_id)
|
||||
.to_string()
|
||||
} else if path.starts_with("app://") {
|
||||
codex_connectors::metadata::connector_mention_slug_from_name(name)
|
||||
} else {
|
||||
name.clone()
|
||||
};
|
||||
let sigil = if plugin_id.is_some()
|
||||
&& mention_start(PLUGIN_TEXT_MENTION_SIGIL, &mention).is_some()
|
||||
{
|
||||
PLUGIN_TEXT_MENTION_SIGIL
|
||||
} else {
|
||||
TOOL_MENTION_SIGIL
|
||||
};
|
||||
Some(MentionBinding {
|
||||
sigil,
|
||||
mention,
|
||||
path: path.clone(),
|
||||
})
|
||||
}
|
||||
UserInput::Text { .. }
|
||||
| UserInput::Image { .. }
|
||||
| UserInput::LocalImage { .. } => None,
|
||||
})
|
||||
.collect();
|
||||
mention_bindings.sort_by_key(|binding| {
|
||||
mention_start(binding.sigil, &binding.mention).unwrap_or(usize::MAX)
|
||||
});
|
||||
self.bottom_pane
|
||||
.record_replayed_user_message_history(HistoryEntry {
|
||||
text: display.message.clone(),
|
||||
text_elements: display.text_elements.clone(),
|
||||
local_image_paths: display.local_images.clone(),
|
||||
remote_image_urls: display.remote_image_urls.clone(),
|
||||
mention_bindings,
|
||||
mention_bindings: mention_bindings_from_user_inputs(items, &display.message),
|
||||
pending_pastes: Vec::new(),
|
||||
});
|
||||
self.on_user_message_display(display);
|
||||
@@ -1745,18 +1689,6 @@ impl ChatWidget {
|
||||
self.bottom_pane.insert_str(text);
|
||||
}
|
||||
|
||||
/// Replace the composer content with the provided text and reset cursor.
|
||||
pub(crate) fn set_composer_text(
|
||||
&mut self,
|
||||
text: String,
|
||||
text_elements: Vec<TextElement>,
|
||||
local_image_paths: Vec<PathBuf>,
|
||||
) {
|
||||
self.bottom_pane
|
||||
.set_composer_text(text, text_elements, local_image_paths);
|
||||
self.refresh_plan_mode_nudge();
|
||||
}
|
||||
|
||||
pub(crate) fn set_remote_image_urls(&mut self, remote_image_urls: Vec<String>) {
|
||||
self.bottom_pane.set_remote_image_urls(remote_image_urls);
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user