Skip to content

Commit 0914195

Browse files
committed
feat(tui2): add feature-flagged tui2 frontend
Introduce a new codex-tui2 crate that re-exports the existing interactive TUI surface and delegates run_main directly to codex-tui. This keeps behavior identical while giving tui2 its own crate for future viewport work. Wire the codex CLI to select the frontend via the tui2 feature flag. When the merged CLI overrides include features.tui2=true (e.g. via --enable tui2), interactive runs are routed through codex_tui2::run_main; otherwise they continue to use the original codex_tui::run_main. Register Feature::Tui2 in the core feature registry and add the tui2 crate and dependency entries so the new frontend builds alongside the existing TUI.
1 parent 05e546e commit 0914195

File tree

9 files changed

+151
-2
lines changed

9 files changed

+151
-2
lines changed

codex-rs/Cargo.lock

Lines changed: 13 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

codex-rs/Cargo.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ members = [
3434
"stdio-to-uds",
3535
"otel",
3636
"tui",
37+
"tui2",
3738
"utils/git",
3839
"utils/cache",
3940
"utils/image",
@@ -88,6 +89,7 @@ codex-responses-api-proxy = { path = "responses-api-proxy" }
8889
codex-rmcp-client = { path = "rmcp-client" }
8990
codex-stdio-to-uds = { path = "stdio-to-uds" }
9091
codex-tui = { path = "tui" }
92+
codex-tui2 = { path = "tui2" }
9193
codex-utils-cache = { path = "utils/cache" }
9294
codex-utils-image = { path = "utils/image" }
9395
codex-utils-json-to-toml = { path = "utils/json-to-toml" }

codex-rs/cli/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ codex-responses-api-proxy = { workspace = true }
3636
codex-rmcp-client = { workspace = true }
3737
codex-stdio-to-uds = { workspace = true }
3838
codex-tui = { workspace = true }
39+
codex-tui2 = { workspace = true }
3940
ctor = { workspace = true }
4041
libc = { workspace = true }
4142
owo-colors = { workspace = true }

codex-rs/cli/src/main.rs

Lines changed: 41 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ use codex_responses_api_proxy::Args as ResponsesApiProxyArgs;
2525
use codex_tui::AppExitInfo;
2626
use codex_tui::Cli as TuiCli;
2727
use codex_tui::update_action::UpdateAction;
28+
use codex_tui2 as tui2;
2829
use owo_colors::OwoColorize;
2930
use std::path::PathBuf;
3031
use supports_color::Stream;
@@ -37,6 +38,11 @@ use crate::mcp_cmd::McpCli;
3738

3839
use codex_core::config::Config;
3940
use codex_core::config::ConfigOverrides;
41+
use codex_core::config::find_codex_home;
42+
use codex_core::config::load_config_as_toml_with_cli_overrides;
43+
use codex_core::features::Feature;
44+
use codex_core::features::FeatureOverrides;
45+
use codex_core::features::Features;
4046
use codex_core::features::is_known_feature_key;
4147

4248
/// Codex CLI
@@ -444,7 +450,7 @@ async fn cli_main(codex_linux_sandbox_exe: Option<PathBuf>) -> anyhow::Result<()
444450
&mut interactive.config_overrides,
445451
root_config_overrides.clone(),
446452
);
447-
let exit_info = codex_tui::run_main(interactive, codex_linux_sandbox_exe).await?;
453+
let exit_info = run_interactive_tui(interactive, codex_linux_sandbox_exe).await?;
448454
handle_app_exit(exit_info)?;
449455
}
450456
Some(Subcommand::Exec(mut exec_cli)) => {
@@ -499,7 +505,7 @@ async fn cli_main(codex_linux_sandbox_exe: Option<PathBuf>) -> anyhow::Result<()
499505
all,
500506
config_overrides,
501507
);
502-
let exit_info = codex_tui::run_main(interactive, codex_linux_sandbox_exe).await?;
508+
let exit_info = run_interactive_tui(interactive, codex_linux_sandbox_exe).await?;
503509
handle_app_exit(exit_info)?;
504510
}
505511
Some(Subcommand::Login(mut login_cli)) => {
@@ -650,6 +656,39 @@ fn prepend_config_flags(
650656
.splice(0..0, cli_config_overrides.raw_overrides);
651657
}
652658

659+
/// Run the interactive Codex TUI, dispatching to either the legacy implementation or the
660+
/// experimental TUI v2 shim based on feature flags resolved from config.
661+
async fn run_interactive_tui(
662+
interactive: TuiCli,
663+
codex_linux_sandbox_exe: Option<PathBuf>,
664+
) -> std::io::Result<AppExitInfo> {
665+
if is_tui2_enabled(&interactive).await? {
666+
tui2::run_main(interactive, codex_linux_sandbox_exe).await
667+
} else {
668+
codex_tui::run_main(interactive, codex_linux_sandbox_exe).await
669+
}
670+
}
671+
672+
/// Returns `Ok(true)` when the resolved configuration enables the `tui2` feature flag.
673+
///
674+
/// This performs a lightweight config load (honoring the same precedence as the lower-level TUI
675+
/// bootstrap: `$CODEX_HOME`, config.toml, profile, and CLI `-c` overrides) solely to decide which
676+
/// TUI frontend to launch. The full configuration is still loaded later by the interactive TUI.
677+
async fn is_tui2_enabled(cli: &TuiCli) -> std::io::Result<bool> {
678+
let raw_overrides = cli.config_overrides.raw_overrides.clone();
679+
let overrides_cli = codex_common::CliConfigOverrides { raw_overrides };
680+
let cli_kv_overrides = overrides_cli
681+
.parse_overrides()
682+
.map_err(|e| std::io::Error::new(std::io::ErrorKind::InvalidInput, e))?;
683+
684+
let codex_home = find_codex_home()?;
685+
let config_toml = load_config_as_toml_with_cli_overrides(&codex_home, cli_kv_overrides).await?;
686+
let config_profile = config_toml.get_config_profile(cli.config_profile.clone())?;
687+
let overrides = FeatureOverrides::default();
688+
let features = Features::from_config(&config_toml, &config_profile, overrides);
689+
Ok(features.enabled(Feature::Tui2))
690+
}
691+
653692
/// Build the final `TuiCli` for a `codex resume` invocation.
654693
fn finalize_resume_interactive(
655694
mut interactive: TuiCli,

codex-rs/core/src/features.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,8 @@ pub enum Feature {
6262
Skills,
6363
/// Experimental shell snapshotting.
6464
ShellSnapshot,
65+
/// Experimental TUI v2 (viewport) implementation.
66+
Tui2,
6567
}
6668

6769
impl Feature {
@@ -367,4 +369,10 @@ pub const FEATURES: &[FeatureSpec] = &[
367369
stage: Stage::Experimental,
368370
default_enabled: false,
369371
},
372+
FeatureSpec {
373+
id: Feature::Tui2,
374+
key: "tui2",
375+
stage: Stage::Experimental,
376+
default_enabled: false,
377+
},
370378
];

codex-rs/tui2/Cargo.toml

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
[package]
2+
name = "codex-tui2"
3+
version.workspace = true
4+
edition.workspace = true
5+
license.workspace = true
6+
7+
[lib]
8+
name = "codex_tui2"
9+
path = "src/lib.rs"
10+
11+
[[bin]]
12+
name = "codex-tui2"
13+
path = "src/main.rs"
14+
15+
[features]
16+
# Keep feature surface aligned with codex-tui while tui2 delegates to it.
17+
vt100-tests = []
18+
debug-logs = []
19+
20+
[lints]
21+
workspace = true
22+
23+
[dependencies]
24+
anyhow = { workspace = true }
25+
clap = { workspace = true, features = ["derive"] }
26+
codex-arg0 = { workspace = true }
27+
codex-common = { workspace = true }
28+
codex-core = { workspace = true }
29+
codex-tui = { workspace = true }

codex-rs/tui2/src/lib.rs

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
#![deny(clippy::print_stdout, clippy::print_stderr)]
2+
#![deny(clippy::disallowed_methods)]
3+
4+
use std::path::PathBuf;
5+
6+
pub use codex_tui::AppExitInfo;
7+
pub use codex_tui::Cli;
8+
pub use codex_tui::update_action;
9+
10+
/// Entry point for the experimental TUI v2 crate.
11+
///
12+
/// Currently this is a thin shim that delegates to the existing `codex-tui`
13+
/// implementation so behavior and rendering remain identical while the new
14+
/// viewport is developed behind a feature toggle.
15+
pub async fn run_main(
16+
cli: Cli,
17+
codex_linux_sandbox_exe: Option<PathBuf>,
18+
) -> std::io::Result<AppExitInfo> {
19+
#[allow(clippy::print_stdout)] // for now
20+
{
21+
println!("Note: You are running the experimental TUI v2 implementation.");
22+
}
23+
codex_tui::run_main(cli, codex_linux_sandbox_exe).await
24+
}

codex-rs/tui2/src/main.rs

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
use clap::Parser;
2+
use codex_arg0::arg0_dispatch_or_else;
3+
use codex_common::CliConfigOverrides;
4+
use codex_core::protocol::FinalOutput;
5+
use codex_tui2::Cli;
6+
use codex_tui2::run_main;
7+
8+
#[derive(Parser, Debug)]
9+
struct TopCli {
10+
#[clap(flatten)]
11+
config_overrides: CliConfigOverrides,
12+
13+
#[clap(flatten)]
14+
inner: Cli,
15+
}
16+
17+
fn main() -> anyhow::Result<()> {
18+
arg0_dispatch_or_else(|codex_linux_sandbox_exe| async move {
19+
let top_cli = TopCli::parse();
20+
let mut inner = top_cli.inner;
21+
inner
22+
.config_overrides
23+
.raw_overrides
24+
.splice(0..0, top_cli.config_overrides.raw_overrides);
25+
let exit_info = run_main(inner, codex_linux_sandbox_exe).await?;
26+
let token_usage = exit_info.token_usage;
27+
if !token_usage.is_zero() {
28+
println!("{}", FinalOutput::from(token_usage));
29+
}
30+
Ok(())
31+
})
32+
}

docs/config.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ Supported features:
4949
| `experimental_sandbox_command_assessment` | false | Experimental | Enable model-based sandbox risk assessment |
5050
| `ghost_commit` | false | Experimental | Create a ghost commit each turn |
5151
| `enable_experimental_windows_sandbox` | false | Experimental | Use the Windows restricted-token sandbox |
52+
| `tui2` | false | Experimental | Use the experimental TUI v2 (viewport) implementation |
5253

5354
Notes:
5455

0 commit comments

Comments
 (0)