Skip to content
Merged
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
6 changes: 6 additions & 0 deletions codex-rs/core/config.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -614,6 +614,9 @@
"terminal_resize_reflow": {
"type": "boolean"
},
"terminal_visualization_instructions": {
"type": "boolean"
},
"tool_call_mcp_elicitation": {
"type": "boolean"
},
Expand Down Expand Up @@ -4737,6 +4740,9 @@
"terminal_resize_reflow": {
"type": "boolean"
},
"terminal_visualization_instructions": {
"type": "boolean"
},
"tool_call_mcp_elicitation": {
"type": "boolean"
},
Expand Down
8 changes: 8 additions & 0 deletions codex-rs/features/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,8 @@ pub enum Feature {
UnifiedExecZshFork,
/// Reflow transcript scrollback when the terminal is resized.
TerminalResizeReflow,
/// Add terminal-specific visualization guidance to TUI developer instructions.
TerminalVisualizationInstructions,
/// Stream structured progress while apply_patch input is being generated.
ApplyPatchStreamingEvents,
/// Allow exec tools to request additional permissions while staying sandboxed.
Expand Down Expand Up @@ -1118,6 +1120,12 @@ pub const FEATURES: &[FeatureSpec] = &[
stage: Stage::UnderDevelopment,
default_enabled: false,
},
FeatureSpec {
id: Feature::TerminalVisualizationInstructions,
key: "terminal_visualization_instructions",
stage: Stage::UnderDevelopment,
default_enabled: false,
},
FeatureSpec {
id: Feature::GuardianApproval,
key: "guardian_approval",
Expand Down
86 changes: 85 additions & 1 deletion codex-rs/tui/src/app_server_session.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ use crate::session_state::MessageHistoryMetadata;
use crate::session_state::ThreadSessionState;
use crate::status::StatusAccountDisplay;
use crate::status::plan_type_display_name;
use crate::terminal_visualization_instructions::with_terminal_visualization_instructions;
use codex_app_server_client::AppServerClient;
use codex_app_server_client::AppServerEvent;
use codex_app_server_client::AppServerRequestHandle;
Expand Down Expand Up @@ -1412,6 +1413,9 @@ fn thread_start_params_from_config(
ephemeral: Some(config.ephemeral),
session_start_source,
thread_source: Some(ThreadSource::User),
developer_instructions: with_terminal_visualization_instructions(
config, /*control_instructions*/ None,
),
..ThreadStartParams::default()
}
}
Expand Down Expand Up @@ -1444,6 +1448,9 @@ fn thread_resume_params_from_config(
sandbox,
permissions,
config: config_request_overrides_from_config(&config),
developer_instructions: with_terminal_visualization_instructions(
&config, /*control_instructions*/ None,
),
..ThreadResumeParams::default()
}
}
Expand Down Expand Up @@ -1477,7 +1484,10 @@ fn thread_fork_params_from_config(
permissions,
config: config_request_overrides_from_config(&config),
base_instructions: config.base_instructions.clone(),
developer_instructions: config.developer_instructions.clone(),
developer_instructions: with_terminal_visualization_instructions(
&config,
config.developer_instructions.clone(),
),
ephemeral: config.ephemeral,
thread_source: Some(ThreadSource::User),
..ThreadForkParams::default()
Expand Down Expand Up @@ -1748,6 +1758,7 @@ mod tests {
use codex_app_server_protocol::ThreadStatus;
use codex_app_server_protocol::Turn;
use codex_app_server_protocol::TurnStatus;
use codex_features::Feature;
use codex_protocol::config_types::Personality;
use codex_protocol::config_types::ReasoningSummary;
use codex_protocol::config_types::ServiceTier;
Expand Down Expand Up @@ -2235,6 +2246,79 @@ mod tests {
);
}

#[tokio::test]
async fn terminal_visualization_instructions_are_gated_for_all_tui_thread_flows() {
let temp_dir = tempfile::tempdir().expect("tempdir");
let mut config = build_config(&temp_dir).await;
config.developer_instructions = Some("Developer override.".to_string());
let thread_id = ThreadId::new();

let control_start = thread_start_params_from_config(
&config,
ThreadParamsMode::Embedded,
/*remote_cwd_override*/ None,
/*session_start_source*/ None,
);
let control_resume = thread_resume_params_from_config(
config.clone(),
thread_id,
ThreadParamsMode::Embedded,
/*remote_cwd_override*/ None,
);
let control_fork = thread_fork_params_from_config(
config.clone(),
thread_id,
ThreadParamsMode::Embedded,
/*remote_cwd_override*/ None,
);

assert_eq!(control_start.developer_instructions, None);
assert_eq!(control_resume.developer_instructions, None);
assert_eq!(
control_fork.developer_instructions.as_deref(),
Some("Developer override.")
);

let _ = config
.features
.enable(Feature::TerminalVisualizationInstructions);
let treatment_start = thread_start_params_from_config(
&config,
ThreadParamsMode::Embedded,
/*remote_cwd_override*/ None,
/*session_start_source*/ None,
);
let treatment_resume = thread_resume_params_from_config(
config.clone(),
thread_id,
ThreadParamsMode::Embedded,
/*remote_cwd_override*/ None,
);
let treatment_fork = thread_fork_params_from_config(
config,
thread_id,
ThreadParamsMode::Embedded,
/*remote_cwd_override*/ None,
);
let expected = format!(
"Developer override.\n\n{}",
crate::terminal_visualization_instructions::TERMINAL_VISUALIZATION_INSTRUCTIONS
);

assert_eq!(
treatment_start.developer_instructions.as_deref(),
Some(expected.as_str())
);
assert_eq!(
treatment_resume.developer_instructions.as_deref(),
Some(expected.as_str())
);
assert_eq!(
treatment_fork.developer_instructions.as_deref(),
Some(expected.as_str())
);
}

#[tokio::test]
async fn resume_response_restores_turns_from_thread_items() {
let temp_dir = tempfile::tempdir().expect("tempdir");
Expand Down
1 change: 1 addition & 0 deletions codex-rs/tui/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -188,6 +188,7 @@ mod terminal_hyperlinks;
mod terminal_palette;
mod terminal_probe;
mod terminal_title;
mod terminal_visualization_instructions;
mod text_formatting;
mod theme_picker;
mod token_usage;
Expand Down
29 changes: 29 additions & 0 deletions codex-rs/tui/src/terminal_visualization_instructions.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
use crate::legacy_core::config::Config;
use codex_features::Feature;

pub(crate) const TERMINAL_VISUALIZATION_INSTRUCTIONS: &str = "\
- This surface is a terminal. When the formatting rules require a visual, include one in the final answer using compact ASCII diagrams, trees, timelines, or tables.
- Use tables for exact mappings or comparisons rather than collapsing known mappings into prose.
- Use trees for hierarchy or one-to-many relationships, and diagrams or timelines for sequence, change, or state transferred between records across event order.
- Use only ASCII characters in visuals.";

pub(crate) fn with_terminal_visualization_instructions(
config: &Config,
control_instructions: Option<String>,
) -> Option<String> {
if !config
.features
.enabled(Feature::TerminalVisualizationInstructions)
{
return control_instructions;
}

let existing_instructions =
control_instructions.or_else(|| config.developer_instructions.clone());
Some(match existing_instructions.as_deref() {
Some(existing) if !existing.trim().is_empty() => {
format!("{existing}\n\n{TERMINAL_VISUALIZATION_INSTRUCTIONS}")
}
_ => TERMINAL_VISUALIZATION_INSTRUCTIONS.to_string(),
})
}
Loading