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
1 change: 1 addition & 0 deletions codex-rs/tui/src/bottom_pane/slash_commands.rs
Original file line number Diff line number Diff line change
Expand Up @@ -294,6 +294,7 @@ mod tests {
SlashCommand::Ide,
SlashCommand::Copy,
SlashCommand::Raw,
SlashCommand::Lcs,
SlashCommand::Diff,
SlashCommand::Mention,
SlashCommand::Status,
Expand Down
35 changes: 35 additions & 0 deletions codex-rs/tui/src/chatwidget.rs
Original file line number Diff line number Diff line change
Expand Up @@ -535,6 +535,9 @@ pub(crate) struct ChatWidget {
transcript: TranscriptState,
config: Config,
raw_output_mode: bool,
/// Whether the LCS substrate (MCP tool calls) is rendered as it runs.
/// True by default; `/lcs hidden` sets it false.
show_lcs_substrate: bool,
/// Runtime value resolved by core. `config.service_tier` remains the explicit user choice.
effective_service_tier: Option<String>,
/// The unmasked collaboration mode settings (always Default mode).
Expand Down Expand Up @@ -1664,6 +1667,38 @@ impl ChatWidget {
enabled
}

/// Whether MCP tool calls are rendered as they run.
pub(crate) fn show_lcs_substrate(&self) -> bool {
self.show_lcs_substrate
}

pub(crate) fn set_lcs_substrate(&mut self, enabled: bool) {
self.show_lcs_substrate = enabled;
self.refresh_status_surfaces();
}

pub(crate) fn lcs_substrate_notice(enabled: bool) -> &'static str {
if enabled {
"LCS substrate visible: MCP tool calls are shown as they run."
} else {
"LCS substrate hidden: MCP tool calls run silently."
}
}

pub(crate) fn set_lcs_substrate_and_notify(&mut self, enabled: bool) {
self.set_lcs_substrate(enabled);
self.add_info_message(
Self::lcs_substrate_notice(enabled).to_string(),
/*hint*/ None,
);
}

pub(crate) fn toggle_lcs_substrate_and_notify(&mut self) -> bool {
let enabled = !self.show_lcs_substrate;
self.set_lcs_substrate_and_notify(enabled);
enabled
}

/// Update resize-sensitive chat widget state after the terminal width changes.
///
/// The app calls this even when terminal resize reflow is disabled so live stream wrapping
Expand Down
3 changes: 3 additions & 0 deletions codex-rs/tui/src/chatwidget/constructor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,9 @@ impl ChatWidget {
}),
transcript: TranscriptState::new(active_cell),
raw_output_mode: config.tui_raw_output_mode,
// The LCS substrate (MCP tool calls) is visible by default.
// `/lcs hidden` opts out.
show_lcs_substrate: true,
config,
effective_service_tier,
skills_all: Vec::new(),
Expand Down
10 changes: 10 additions & 0 deletions codex-rs/tui/src/chatwidget/slash_dispatch.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ const SIDE_SLASH_COMMAND_UNAVAILABLE_HINT: &str =
"Press Ctrl+C to return to the main thread first.";
const GOAL_USAGE_HINT: &str = "Example: /goal improve benchmark coverage";
const RAW_USAGE: &str = "Usage: /raw [on|off]";
const LCS_USAGE: &str = "Usage: /lcs [hidden|visible]";

impl ChatWidget {
/// Dispatch a bare slash command and record its staged local-history entry.
Expand Down Expand Up @@ -374,6 +375,9 @@ impl ChatWidget {
let enabled = self.toggle_raw_output_mode_and_notify();
self.emit_raw_output_mode_changed(enabled);
}
SlashCommand::Lcs => {
self.toggle_lcs_substrate_and_notify();
}
SlashCommand::Diff => {
self.add_diff_in_progress();
let tx = self.app_event_tx.clone();
Expand Down Expand Up @@ -669,6 +673,11 @@ impl ChatWidget {
}
_ => self.add_error_message(RAW_USAGE.to_string()),
},
SlashCommand::Lcs => match trimmed.to_ascii_lowercase().as_str() {
"hidden" => self.set_lcs_substrate_and_notify(/*enabled*/ false),
"visible" => self.set_lcs_substrate_and_notify(/*enabled*/ true),
_ => self.add_error_message(LCS_USAGE.to_string()),
},
SlashCommand::Rename if !trimmed.is_empty() => {
if !self.ensure_thread_rename_allowed() {
return;
Expand Down Expand Up @@ -996,6 +1005,7 @@ impl ChatWidget {
| SlashCommand::Rollout
| SlashCommand::Copy
| SlashCommand::Raw
| SlashCommand::Lcs
| SlashCommand::Vim
| SlashCommand::Diff
| SlashCommand::App
Expand Down
38 changes: 38 additions & 0 deletions codex-rs/tui/src/chatwidget/tests/slash_commands.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2419,6 +2419,44 @@ async fn raw_slash_command_reports_usage_for_invalid_arg() {
);
}

#[tokio::test]
async fn lcs_slash_command_toggles_and_accepts_hidden_visible_args() {
let (mut chat, _rx, _op_rx) = make_chatwidget_manual(/*model_override*/ None).await;

// The LCS substrate (MCP tool calls) is visible by default.
assert!(chat.show_lcs_substrate());

chat.dispatch_command_with_args(SlashCommand::Lcs, "hidden".to_string(), Vec::new());
assert!(!chat.show_lcs_substrate());

chat.dispatch_command_with_args(SlashCommand::Lcs, "visible".to_string(), Vec::new());
assert!(chat.show_lcs_substrate());

// Bare `/lcs` toggles.
chat.dispatch_command(SlashCommand::Lcs);
assert!(!chat.show_lcs_substrate());
}

#[tokio::test]
async fn lcs_slash_command_reports_usage_for_invalid_arg() {
let (mut chat, mut rx, _op_rx) = make_chatwidget_manual(/*model_override*/ None).await;

chat.dispatch_command_with_args(SlashCommand::Lcs, "status".to_string(), Vec::new());

// Unchanged.
assert!(chat.show_lcs_substrate());
let cells = drain_insert_history(&mut rx);
let rendered = cells
.iter()
.map(|lines| lines_to_single_string(lines))
.collect::<Vec<_>>()
.join("\n");
assert!(
rendered.contains("Usage: /lcs [hidden|visible]"),
"expected lcs usage error, got {rendered:?}"
);
}

#[tokio::test]
async fn compact_queues_user_messages_snapshot() {
let (mut chat, _rx, _op_rx) = make_chatwidget_manual(/*model_override*/ None).await;
Expand Down
24 changes: 14 additions & 10 deletions codex-rs/tui/src/chatwidget/tool_lifecycle.rs
Original file line number Diff line number Diff line change
Expand Up @@ -50,19 +50,16 @@ impl ChatWidget {
}

pub(super) fn on_mcp_tool_call_started(&mut self, item: ThreadItem) {
let item2 = item.clone();
self.defer_or_handle(
|q| q.push_item_started(item),
|s| s.handle_mcp_tool_call_started_now(item2),
);
// Render MCP tool calls as they start, rather than batching them into
// the interrupt queue until the stream cycle ends. Flush pending
// interrupts first to preserve ordering, then handle immediately.
self.flush_interrupt_queue();
self.handle_mcp_tool_call_started_now(item);
}

pub(super) fn on_mcp_tool_call_completed(&mut self, item: ThreadItem) {
let item2 = item.clone();
self.defer_or_handle(
|q| q.push_item_completed(item),
|s| s.handle_mcp_tool_call_completed_now(item2),
);
self.flush_interrupt_queue();
self.handle_mcp_tool_call_completed_now(item);
}

pub(super) fn on_web_search_begin(&mut self, call_id: String) {
Expand Down Expand Up @@ -159,6 +156,9 @@ impl ChatWidget {

pub(crate) fn handle_mcp_tool_call_started_now(&mut self, item: ThreadItem) {
self.record_visible_turn_activity();
if !self.show_lcs_substrate {
return;
}
let ThreadItem::McpToolCall {
id,
server,
Expand Down Expand Up @@ -186,6 +186,10 @@ impl ChatWidget {

pub(crate) fn handle_mcp_tool_call_completed_now(&mut self, item: ThreadItem) {
self.flush_answer_stream_with_separator();
self.transcript.had_work_activity = true;
if !self.show_lcs_substrate {
return;
}

let ThreadItem::McpToolCall {
id,
Expand Down
8 changes: 8 additions & 0 deletions codex-rs/tui/src/slash_command.rs
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,8 @@ pub enum SlashCommand {
Btw,
Copy,
Raw,
/// Toggle whether the LCS substrate (MCP tool calls) is shown as it runs.
Lcs,
Diff,
Mention,
Status,
Expand Down Expand Up @@ -97,6 +99,9 @@ impl SlashCommand {
SlashCommand::Quit | SlashCommand::Exit => "exit Codex",
SlashCommand::Copy => "copy last response as markdown",
SlashCommand::Raw => "toggle raw scrollback mode for copy-friendly terminal selection",
SlashCommand::Lcs => {
"toggle LCS substrate visibility (MCP tool calls): /lcs hidden|visible"
}
SlashCommand::Diff => "show git diff (including untracked files)",
SlashCommand::Mention => "mention a file",
SlashCommand::Skills => "use skills to improve how Codex performs specific tasks",
Expand Down Expand Up @@ -163,6 +168,7 @@ impl SlashCommand {
| SlashCommand::Keymap
| SlashCommand::Mcp
| SlashCommand::Raw
| SlashCommand::Lcs
| SlashCommand::Pets
| SlashCommand::Side
| SlashCommand::Btw
Expand All @@ -177,6 +183,7 @@ impl SlashCommand {
self,
SlashCommand::Copy
| SlashCommand::Raw
| SlashCommand::Lcs
| SlashCommand::Diff
| SlashCommand::Mention
| SlashCommand::Status
Expand Down Expand Up @@ -211,6 +218,7 @@ impl SlashCommand {
SlashCommand::Diff
| SlashCommand::Copy
| SlashCommand::Raw
| SlashCommand::Lcs
| SlashCommand::Rename
| SlashCommand::Mention
| SlashCommand::Skills
Expand Down
Loading