mirror of
https://github.com/openai/codex.git
synced 2026-10-21 09:54:56 +00:00
Start side conversations without replaying inherited turns (#34198)
## What changed - Fork side conversations with `exclude_turns` so their initial UI starts empty instead of displaying the parent thread's turns. - Keep regular fork responses unchanged, including for ephemeral threads. ## Testing - Add coverage that compares regular and side-conversation forks of the same saved thread. GitOrigin-RevId: 662607e9f588c20a2e06656deefa5b70bde19cd2
This commit is contained in:
@@ -574,7 +574,10 @@ impl App {
|
||||
.await;
|
||||
|
||||
let fork_config = self.side_fork_config();
|
||||
match app_server.fork_thread(fork_config, parent_thread_id).await {
|
||||
match app_server
|
||||
.fork_side_thread(fork_config, parent_thread_id)
|
||||
.await
|
||||
{
|
||||
Ok(forked) => {
|
||||
let child_thread_id = forked.session.thread_id;
|
||||
let channel = self.ensure_thread_channel(child_thread_id);
|
||||
|
||||
@@ -145,6 +145,12 @@ pub(crate) enum ForkGoalContinuation {
|
||||
DeferUntilNextTurn,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||
enum ForkPresentation {
|
||||
Regular,
|
||||
SideConversation,
|
||||
}
|
||||
|
||||
fn bootstrap_request_error(context: &'static str, err: TypedRequestError) -> color_eyre::Report {
|
||||
color_eyre::eyre::eyre!("{context}: {err}")
|
||||
}
|
||||
@@ -574,6 +580,42 @@ impl AppServerSession {
|
||||
last_turn_id: Option<String>,
|
||||
before_turn_id: Option<String>,
|
||||
goal_continuation: ForkGoalContinuation,
|
||||
) -> Result<AppServerStartedThread> {
|
||||
self.fork_thread_at_with_presentation(
|
||||
config,
|
||||
thread_id,
|
||||
last_turn_id,
|
||||
before_turn_id,
|
||||
goal_continuation,
|
||||
ForkPresentation::Regular,
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
pub(crate) async fn fork_side_thread(
|
||||
&mut self,
|
||||
config: Config,
|
||||
thread_id: ThreadId,
|
||||
) -> Result<AppServerStartedThread> {
|
||||
self.fork_thread_at_with_presentation(
|
||||
config,
|
||||
thread_id,
|
||||
/*last_turn_id*/ None,
|
||||
/*before_turn_id*/ None,
|
||||
ForkGoalContinuation::StartIfIdle,
|
||||
ForkPresentation::SideConversation,
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
async fn fork_thread_at_with_presentation(
|
||||
&mut self,
|
||||
config: Config,
|
||||
thread_id: ThreadId,
|
||||
last_turn_id: Option<String>,
|
||||
before_turn_id: Option<String>,
|
||||
goal_continuation: ForkGoalContinuation,
|
||||
presentation: ForkPresentation,
|
||||
) -> Result<AppServerStartedThread> {
|
||||
let request_id = self.next_request_id();
|
||||
let session_config = self.session_config_with_effective_service_tier(&config);
|
||||
@@ -586,6 +628,7 @@ impl AppServerSession {
|
||||
before_turn_id,
|
||||
defer_goal_continuation: goal_continuation
|
||||
== ForkGoalContinuation::DeferUntilNextTurn,
|
||||
exclude_turns: presentation == ForkPresentation::SideConversation,
|
||||
..thread_fork_params_from_config(
|
||||
session_config,
|
||||
thread_id,
|
||||
@@ -2444,6 +2487,41 @@ mod tests {
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn side_fork_excludes_turns_without_clearing_regular_ephemeral_fork() -> Result<()> {
|
||||
let codex_home = tempfile::tempdir().expect("tempdir");
|
||||
let mut config = build_config(&codex_home).await;
|
||||
config.ephemeral = true;
|
||||
let thread_id = ThreadId::from_string(
|
||||
&create_fake_rollout(
|
||||
codex_home.path(),
|
||||
"2025-01-05T12-00-00",
|
||||
"2025-01-05T12:00:00Z",
|
||||
"Saved user message",
|
||||
Some(config.model_provider_id.as_str()),
|
||||
/*git_info*/ None,
|
||||
)
|
||||
.expect("create rollout"),
|
||||
)?;
|
||||
let mut app_server = crate::start_embedded_app_server_for_picker(&config).await?;
|
||||
|
||||
let regular = app_server.fork_thread(config.clone(), thread_id).await?;
|
||||
let side = app_server.fork_side_thread(config, thread_id).await?;
|
||||
|
||||
assert_eq!(regular.turns.len(), 1);
|
||||
assert!(matches!(
|
||||
regular.turns[0].items.as_slice(),
|
||||
[codex_app_server_protocol::ThreadItem::UserMessage { content, .. }]
|
||||
if content == &[UserInput::Text {
|
||||
text: "Saved user message".to_string(),
|
||||
text_elements: Vec::new(),
|
||||
}]
|
||||
));
|
||||
assert_eq!(side.turns, Vec::<Turn>::new());
|
||||
app_server.shutdown().await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn terminal_visualization_instructions_are_gated_for_all_tui_thread_flows() {
|
||||
let temp_dir = tempfile::tempdir().expect("tempdir");
|
||||
|
||||
Reference in New Issue
Block a user