Files
codex/codex-rs/tui/src/chatwidget/plan_implementation.rs
T
Felipe Coury 241136b0e9 feat(tui): show context used in plan implementation prompt (#18573)
# Summary

When a user finishes planning, the TUI asks whether to implement in the
current conversation or start fresh with the approved plan. The
clear-context choice is easier to evaluate when the prompt shows how
much context has already been used, because the user can see when
carrying the full prior conversation is likely to be less useful than
preserving only the plan.

<img width="1612" height="1312" alt="image"
src="https://github.com/user-attachments/assets/694bcf87-8be5-4e88-a412-e562af62d5f7"
/>
    
This PR adds that context signal directly to the clear-context option
while keeping the copy compact enough for the Plan-mode selection popup.

# What Changed

- Compute an optional context-usage label when opening the plan
implementation prompt.
- Show the label only on `Yes, clear context and implement`, where it
informs the cleanup decision.
- Prefer a percentage-used label when context-window information is
available, with a compact token-used fallback when only token totals are
known.
- Preserve the original option description when usage is unknown or
effectively zero.
- Add rustdoc comments around the prompt-copy boundary so future changes
keep the context label formatting and selection rendering
responsibilities clear.

# Testing

- `cargo test -p codex-tui plan_implementation`

# Notes

The footer continues to show context remaining as ambient status. The
implementation prompt intentionally shows context used because the user
is choosing whether to clean up the current thread before
implementation.
2026-04-19 14:01:58 -03:00

115 lines
4.7 KiB
Rust

use codex_protocol::config_types::CollaborationModeMask;
use crate::app_event::AppEvent;
use crate::bottom_pane::SelectionAction;
use crate::bottom_pane::SelectionItem;
use crate::bottom_pane::SelectionViewParams;
use crate::bottom_pane::popup_consts::standard_popup_hint_line;
pub(super) const PLAN_IMPLEMENTATION_TITLE: &str = "Implement this plan?";
const PLAN_IMPLEMENTATION_YES: &str = "Yes, implement this plan";
const PLAN_IMPLEMENTATION_CLEAR_CONTEXT: &str = "Yes, clear context and implement";
const PLAN_IMPLEMENTATION_NO: &str = "No, stay in Plan mode";
pub(super) const PLAN_IMPLEMENTATION_CODING_MESSAGE: &str = "Implement the plan.";
pub(super) const PLAN_IMPLEMENTATION_CLEAR_CONTEXT_PREFIX: &str = concat!(
"A previous agent produced the plan below to accomplish the user's task. ",
"Implement the plan in a fresh context. Treat the plan as the source of ",
"user intent, re-read files as needed, and carry the work through ",
"implementation and verification."
);
pub(super) const PLAN_IMPLEMENTATION_DEFAULT_UNAVAILABLE: &str = "Default mode unavailable";
pub(super) const PLAN_IMPLEMENTATION_NO_APPROVED_PLAN: &str = "No approved plan available";
/// Builds the confirmation prompt shown after a plan is approved in Plan mode.
///
/// The optional usage label is already phrased for display, such as `89% used`
/// or `123K used`. This module only decides where that label belongs in the
/// decision copy so action wiring stays separate from token accounting.
pub(super) fn selection_view_params(
default_mask: Option<CollaborationModeMask>,
plan_markdown: Option<&str>,
clear_context_usage_label: Option<&str>,
) -> SelectionViewParams {
let (implement_actions, implement_disabled_reason) = match default_mask.clone() {
Some(mask) => {
let user_text = PLAN_IMPLEMENTATION_CODING_MESSAGE.to_string();
let actions: Vec<SelectionAction> = vec![Box::new(move |tx| {
tx.send(AppEvent::SubmitUserMessageWithMode {
text: user_text.clone(),
collaboration_mode: mask.clone(),
});
})];
(actions, None)
}
None => (
Vec::new(),
Some(PLAN_IMPLEMENTATION_DEFAULT_UNAVAILABLE.to_string()),
),
};
let (clear_context_actions, clear_context_disabled_reason) = match (default_mask, plan_markdown)
{
(None, _) => (
Vec::new(),
Some(PLAN_IMPLEMENTATION_DEFAULT_UNAVAILABLE.to_string()),
),
(Some(_), Some(plan_markdown)) if !plan_markdown.trim().is_empty() => {
let user_text =
format!("{PLAN_IMPLEMENTATION_CLEAR_CONTEXT_PREFIX}\n\n{plan_markdown}");
let actions: Vec<SelectionAction> = vec![Box::new(move |tx| {
tx.send(AppEvent::ClearUiAndSubmitUserMessage {
text: user_text.clone(),
});
})];
(actions, None)
}
(Some(_), _) => (
Vec::new(),
Some(PLAN_IMPLEMENTATION_NO_APPROVED_PLAN.to_string()),
),
};
let clear_context_description = clear_context_usage_label.map_or_else(
|| "Fresh thread with this plan.".to_string(),
|label| format!("Fresh thread. Context: {label}."),
);
SelectionViewParams {
title: Some(PLAN_IMPLEMENTATION_TITLE.to_string()),
subtitle: None,
footer_hint: Some(standard_popup_hint_line()),
items: vec![
SelectionItem {
name: PLAN_IMPLEMENTATION_YES.to_string(),
description: Some("Switch to Default and start coding.".to_string()),
selected_description: None,
is_current: false,
actions: implement_actions,
disabled_reason: implement_disabled_reason,
dismiss_on_select: true,
..Default::default()
},
SelectionItem {
name: PLAN_IMPLEMENTATION_CLEAR_CONTEXT.to_string(),
description: Some(clear_context_description),
selected_description: None,
is_current: false,
actions: clear_context_actions,
disabled_reason: clear_context_disabled_reason,
dismiss_on_select: true,
..Default::default()
},
SelectionItem {
name: PLAN_IMPLEMENTATION_NO.to_string(),
description: Some("Continue planning with the model.".to_string()),
selected_description: None,
is_current: false,
actions: Vec::new(),
dismiss_on_select: true,
..Default::default()
},
],
..Default::default()
}
}