Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 27 additions & 1 deletion codex-rs/core/src/features.rs
Original file line number Diff line number Diff line change
Expand Up @@ -686,7 +686,11 @@ pub const FEATURES: &[FeatureSpec] = &[
FeatureSpec {
id: Feature::DefaultModeRequestUserInput,
key: "default_mode_request_user_input",
stage: Stage::UnderDevelopment,
stage: Stage::Experimental {
name: "Default mode request_user_input",
menu_description: "Allow Codex to use the request_user_input tool in Default mode when it truly cannot proceed safely with a reasonable assumption. Restart Codex after enabling.",
announcement: "NEW: Allow request_user_input in Default mode. Enable it in /experimental and restart Codex!",
},
default_enabled: false,
},
FeatureSpec {
Expand Down Expand Up @@ -884,6 +888,28 @@ mod tests {
assert_eq!(Feature::JsRepl.default_enabled(), false);
}

#[test]
fn default_mode_request_user_input_is_experimental_and_user_toggleable() {
let spec = Feature::DefaultModeRequestUserInput.info();
let stage = spec.stage;

assert!(matches!(stage, Stage::Experimental { .. }));
assert_eq!(
stage.experimental_menu_name(),
Some("Default mode request_user_input")
);
assert_eq!(
stage.experimental_menu_description(),
Some(
"Allow Codex to use the request_user_input tool in Default mode when it truly cannot proceed safely with a reasonable assumption. Restart Codex after enabling."
)
);
assert_eq!(
Feature::DefaultModeRequestUserInput.default_enabled(),
false
);
}

#[test]
fn image_generation_is_under_development() {
assert_eq!(Feature::ImageGeneration.stage(), Stage::UnderDevelopment);
Expand Down
54 changes: 35 additions & 19 deletions codex-rs/core/tests/suite/request_user_input.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

use std::collections::HashMap;

use codex_core::config::Config;
use codex_core::features::Feature;
use codex_protocol::config_types::CollaborationMode;
use codex_protocol::config_types::ModeKind;
Expand Down Expand Up @@ -70,32 +71,27 @@ fn call_output_content_and_success(

#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn request_user_input_round_trip_resolves_pending() -> anyhow::Result<()> {
request_user_input_round_trip_for_mode(ModeKind::Plan).await
request_user_input_round_trip_for_mode(ModeKind::Plan, |_| {}).await
}

async fn request_user_input_round_trip_for_mode(mode: ModeKind) -> anyhow::Result<()> {
async fn request_user_input_round_trip_for_mode<C>(
mode: ModeKind,
configure: C,
) -> anyhow::Result<()>
where
C: FnOnce(&mut Config) + Send + 'static,
{
skip_if_no_network!(Ok(()));

let server = start_mock_server().await;

let builder = test_codex();
#[allow(clippy::expect_used)]
let mut builder = test_codex().with_config(configure);
let TestCodex {
codex,
cwd,
session_configured,
..
} = builder
.with_config(move |config| {
if mode == ModeKind::Default {
config
.features
.enable(Feature::DefaultModeRequestUserInput)
.expect("test config should allow feature update");
}
})
.build(&server)
.await?;
} = builder.build(&server).await?;

let call_id = "user-input-call";
let request_args = json!({
Expand Down Expand Up @@ -196,15 +192,20 @@ async fn request_user_input_round_trip_for_mode(mode: ModeKind) -> anyhow::Resul
Ok(())
}

async fn assert_request_user_input_rejected<F>(mode_name: &str, build_mode: F) -> anyhow::Result<()>
async fn assert_request_user_input_rejected_with_config<F, C>(
mode_name: &str,
build_mode: F,
configure: C,
) -> anyhow::Result<()>
where
F: FnOnce(String) -> CollaborationMode,
C: FnOnce(&mut Config) + Send + 'static,
{
skip_if_no_network!(Ok(()));

let server = start_mock_server().await;

let mut builder = test_codex();
let mut builder = test_codex().with_config(configure);
let TestCodex {
codex,
cwd,
Expand Down Expand Up @@ -278,6 +279,13 @@ where
Ok(())
}

async fn assert_request_user_input_rejected<F>(mode_name: &str, build_mode: F) -> anyhow::Result<()>
where
F: FnOnce(String) -> CollaborationMode,
{
assert_request_user_input_rejected_with_config(mode_name, build_mode, |_| {}).await
}

#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn request_user_input_rejected_in_execute_mode_alias() -> anyhow::Result<()> {
assert_request_user_input_rejected("Execute", |model| CollaborationMode {
Expand Down Expand Up @@ -305,8 +313,16 @@ async fn request_user_input_rejected_in_default_mode_by_default() -> anyhow::Res
}

#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn request_user_input_round_trip_in_default_mode_with_feature() -> anyhow::Result<()> {
request_user_input_round_trip_for_mode(ModeKind::Default).await
async fn request_user_input_round_trip_in_default_mode_when_feature_enabled() -> anyhow::Result<()>
{
request_user_input_round_trip_for_mode(ModeKind::Default, |config| {
#[allow(clippy::expect_used)]
config
.features
.enable(Feature::DefaultModeRequestUserInput)
.expect("test config should allow feature update");
})
.await
}

#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
Expand Down
7 changes: 6 additions & 1 deletion codex-rs/tui/src/chatwidget/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6932,8 +6932,13 @@ async fn experimental_popup_shows_js_repl_node_requirement() {
chat.open_experimental_popup();

let popup = render_bottom_popup(&chat, 120);
let normalized_popup = popup.split_whitespace().collect::<Vec<_>>().join(" ");
let normalized_requirement = node_requirement
.split_whitespace()
.collect::<Vec<_>>()
.join(" ");
assert!(
popup.contains(node_requirement),
normalized_popup.contains(&normalized_requirement),
"expected js_repl feature description to mention the required Node version, got:\n{popup}"
);
}
Expand Down
Loading