Files
codex/codex-rs/tui/src/exec_cell/transcript.rs
T
Eric Traut f43faf132d Align persisted TUI activity groups and reasoning with live output (#46711)
## Why

Persisted transcripts rendered tool calls individually and displayed reasoning differently from live replay. Enabling raw reasoning also replaced the summary instead of retaining it.

## What changed

- Group computer activity and compatible exploration commands across intervening reasoning, retaining details for the expanded transcript. Respect visible content, command failure boundaries, and turn boundaries.
- Separate paginated history at every turn boundary before filtering hidden items, while adding completion metadata only for successful turns.
- Keep persisted reasoning out of the compact display, preserve hyperlinks in detailed rendering, and append enabled raw reasoning to the summary. Preserve standalone reasoning Markdown in transcript exports.
- Add helpers to rejoin activity groups and attach reasoning across page boundaries using persisted item identities and turn adjacency.

## Testing

Add regression coverage for reasoning replay parity, computer groups split across history pages, turn boundaries, and grouped terminal input. Update export and raw-reasoning tests to verify retained summaries and Markdown.

GitOrigin-RevId: a716db571440f891cc2c6894ae93fbe0e149aed4
2026-09-19 20:27:28 +00:00

76 lines
3.0 KiB
Rust

//! Expanded command history with source-backed selection and chronological activity details.
//! Raw history omits transcript-only reasoning while retaining full command output.
use super::model::ExecCell;
use crate::exec_command::strip_bash_lc_and_escape;
use crate::history_cell::HistoryRenderMode;
use crate::render::highlight::highlight_bash_to_lines;
use crate::terminal_hyperlinks::HyperlinkLine;
use crate::terminal_hyperlinks::adaptive_wrap_hyperlink_lines;
use crate::terminal_hyperlinks::plain_hyperlink_lines;
use crate::wrapping::RtOptions;
use codex_ansi_escape::ansi_escape_line;
use codex_utils_elapsed::format_duration;
use ratatui::prelude::*;
impl ExecCell {
pub(super) fn detailed_hyperlink_lines(
&self,
width: u16,
mode: HistoryRenderMode,
) -> Vec<HyperlinkLine> {
let mut lines: Vec<HyperlinkLine> = vec![];
for (i, call) in self.iter_calls().enumerate() {
if i > 0 {
lines.push("".into());
}
let script = strip_bash_lc_and_escape(&call.command);
let highlighted_script = highlight_bash_to_lines(&script);
let cmd_display = adaptive_wrap_hyperlink_lines(
&plain_hyperlink_lines(highlighted_script),
RtOptions::new(width as usize)
.initial_indent("$ ".magenta().into())
.subsequent_indent(" ".into()),
);
lines.extend(cmd_display);
if let Some(output) = call.output.as_ref() {
if !call.is_unified_exec_interaction() {
let wrap_width = width.max(/*other*/ 1) as usize;
let wrap_opts = RtOptions::new(wrap_width);
for unwrapped in output
.transcript_lines()
.map(|line| ansi_escape_line(line.as_ref()))
{
lines.extend(adaptive_wrap_hyperlink_lines(
&[unwrapped.into()],
wrap_opts.clone(),
));
}
}
if call.duration.is_some() || output.exit_code != 0 {
let mut result: Line = if output.exit_code == 0 {
Line::from("✓".green().bold())
} else {
Line::from(vec![
"✗".red().bold(),
format!(" ({})", output.exit_code).into(),
])
};
if let Some(duration) = call.duration {
let duration = format_duration(duration);
result.push_span(format!(" • {duration}").dim());
}
lines.push(result.into());
}
}
lines.extend(self.group.details.lines_after(i + 1, width, mode));
}
lines
}
}
#[cfg(test)]
#[path = "transcript_tests.rs"]
mod tests;