Compare commits

...

6 Commits

Author SHA1 Message Date
Gabriel Peal
482a4f6028 Format 2025-06-25 09:18:20 -04:00
Gabriel Peal
b031987156 I have read the CLA Document and I hereby sign the CLA 2025-06-25 09:04:58 -04:00
Gabriel Peal
9909ea466c fmt 2025-06-25 09:04:09 -04:00
Gabriel Peal
675e99329e Cleanup 2025-06-25 08:36:16 -04:00
Gabriel Peal
8edbd6e99e Clippy 2025-06-25 08:17:39 -04:00
Gabriel Peal
1824d74270 Works 2025-06-25 08:15:14 -04:00
14 changed files with 76 additions and 94 deletions

View File

@@ -565,7 +565,7 @@ async fn submission_loop(
notify,
cwd,
} => {
info!("Configuring session: model={model}; provider={provider:?}");
debug!("Configuring session: model={model}; provider={provider:?}");
if !cwd.is_absolute() {
let message = format!("cwd is not absolute: {cwd:?}");
error!(message);
@@ -1135,7 +1135,7 @@ async fn handle_response_item(
action,
} => {
let LocalShellAction::Exec(action) = action;
tracing::info!("LocalShellCall: {action:?}");
debug!("LocalShellCall: {action:?}");
let params = ShellToolCallParams {
command: action.command,
workdir: action.working_directory,

View File

@@ -353,6 +353,8 @@ pub enum EventMsg {
BackgroundEvent(BackgroundEventEvent),
Log(LogEvent),
/// Notification that the agent is about to apply a code patch. Mirrors
/// `ExecCommandBegin` so frontends can show progress indicators.
PatchApplyBegin(PatchApplyBeginEvent),
@@ -464,6 +466,11 @@ pub struct BackgroundEventEvent {
pub message: String,
}
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct LogEvent {
pub line: String,
}
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct PatchApplyBeginEvent {
/// Identifier so this can be paired with the PatchApplyEnd event.

View File

@@ -10,6 +10,7 @@ use codex_core::protocol::EventMsg;
use codex_core::protocol::ExecCommandBeginEvent;
use codex_core::protocol::ExecCommandEndEvent;
use codex_core::protocol::FileChange;
use codex_core::protocol::LogEvent;
use codex_core::protocol::McpToolCallBeginEvent;
use codex_core::protocol::McpToolCallEndEvent;
use codex_core::protocol::PatchApplyBeginEvent;
@@ -176,6 +177,9 @@ impl EventProcessor {
EventMsg::BackgroundEvent(BackgroundEventEvent { message }) => {
ts_println!(self, "{}", message.style(self.dimmed));
}
EventMsg::Log(LogEvent { line }) => {
ts_println!(self, "{}", line.style(self.dimmed));
}
EventMsg::TaskStarted | EventMsg::TaskComplete(_) => {
// Ignore.
}

View File

@@ -168,6 +168,7 @@ pub async fn run_codex_tool_session(
| EventMsg::ExecCommandBegin(_)
| EventMsg::ExecCommandEnd(_)
| EventMsg::BackgroundEvent(_)
| EventMsg::Log(_)
| EventMsg::PatchApplyBegin(_)
| EventMsg::PatchApplyEnd(_)
| EventMsg::GetHistoryEntryResponse(_) => {

View File

@@ -10,6 +10,8 @@ use crate::slash_command::SlashCommand;
use crate::tui;
use codex_core::config::Config;
use codex_core::protocol::Event;
use codex_core::protocol::EventMsg;
use codex_core::protocol::LogEvent;
use codex_core::protocol::Op;
use color_eyre::eyre::Result;
use crossterm::event::KeyCode;
@@ -228,7 +230,10 @@ impl<'a> App<'a> {
AppState::Login { .. } | AppState::GitWarning { .. } => {}
},
AppEvent::LatestLog(line) => match &mut self.app_state {
AppState::Chat { widget } => widget.update_latest_log(line),
AppState::Chat { widget } => widget.handle_codex_event(Event {
id: String::new(),
msg: EventMsg::Log(LogEvent { line: line.clone() }),
}),
AppState::Login { .. } | AppState::GitWarning { .. } => {}
},
AppEvent::DispatchCommand(command) => match command {

View File

@@ -5,12 +5,6 @@ use ratatui::layout::Rect;
use super::BottomPane;
/// Type to use for a method that may require a redraw of the UI.
pub(crate) enum ConditionalUpdate {
NeedsRedraw,
NoRedraw,
}
/// Trait implemented by every view that can be shown in the bottom pane.
pub(crate) trait BottomPaneView<'a> {
/// Handle a key event while the view is active. A redraw is always
@@ -28,11 +22,6 @@ pub(crate) trait BottomPaneView<'a> {
/// Render the view: this will be displayed in place of the composer.
fn render(&self, area: Rect, buf: &mut Buffer);
/// Update the status indicator text.
fn update_status_text(&mut self, _text: String) -> ConditionalUpdate {
ConditionalUpdate::NoRedraw
}
/// Called when task completes to check if the view should be hidden.
fn should_hide_when_task_is_done(&mut self) -> bool {
false

View File

@@ -1,7 +1,6 @@
//! Bottom pane: shows the ChatComposer or a BottomPaneView, if one is active.
use bottom_pane_view::BottomPaneView;
use bottom_pane_view::ConditionalUpdate;
use crossterm::event::KeyEvent;
use ratatui::buffer::Buffer;
use ratatui::layout::Rect;
@@ -78,21 +77,6 @@ impl BottomPane<'_> {
}
}
/// Update the status indicator text (only when the `StatusIndicatorView` is
/// active).
pub(crate) fn update_status_text(&mut self, text: String) {
if let Some(view) = &mut self.active_view {
match view.update_status_text(text) {
ConditionalUpdate::NeedsRedraw => {
self.request_redraw();
}
ConditionalUpdate::NoRedraw => {
// No redraw needed.
}
}
}
}
/// Update the UI to reflect whether this `BottomPane` has input focus.
pub(crate) fn set_input_focus(&mut self, has_focus: bool) {
self.has_input_focus = has_focus;

View File

@@ -6,7 +6,6 @@ use crate::app_event_sender::AppEventSender;
use crate::status_indicator_widget::StatusIndicatorWidget;
use super::BottomPaneView;
use super::bottom_pane_view::ConditionalUpdate;
pub(crate) struct StatusIndicatorView {
view: StatusIndicatorWidget,
@@ -18,18 +17,9 @@ impl StatusIndicatorView {
view: StatusIndicatorWidget::new(app_event_tx, height),
}
}
pub fn update_text(&mut self, text: String) {
self.view.update_text(text);
}
}
impl<'a> BottomPaneView<'a> for StatusIndicatorView {
fn update_status_text(&mut self, text: String) -> ConditionalUpdate {
self.update_text(text);
ConditionalUpdate::NeedsRedraw
}
fn should_hide_when_task_is_done(&mut self) -> bool {
true
}

View File

@@ -13,6 +13,7 @@ use codex_core::protocol::ExecApprovalRequestEvent;
use codex_core::protocol::ExecCommandBeginEvent;
use codex_core::protocol::ExecCommandEndEvent;
use codex_core::protocol::InputItem;
use codex_core::protocol::LogEvent;
use codex_core::protocol::McpToolCallBeginEvent;
use codex_core::protocol::McpToolCallEndEvent;
use codex_core::protocol::Op;
@@ -358,6 +359,10 @@ impl ChatWidget<'_> {
self.bottom_pane
.on_history_entry_response(log_id, offset, entry.map(|e| e.text));
}
EventMsg::Log(LogEvent { line }) => {
self.conversation_history.add_log_line(line);
self.request_redraw();
}
event => {
self.conversation_history
.add_background_event(format!("{event:?}"));
@@ -366,12 +371,6 @@ impl ChatWidget<'_> {
}
}
/// Update the live log preview while a task is running.
pub(crate) fn update_latest_log(&mut self, line: String) {
// Forward only if we are currently showing the status indicator.
self.bottom_pane.update_status_text(line);
}
fn request_redraw(&mut self) {
self.app_event_tx.send(AppEvent::Redraw);
}

View File

@@ -206,6 +206,10 @@ impl ConversationHistoryWidget {
self.add_to_history(HistoryCell::new_background_event(message));
}
pub fn add_log_line(&mut self, line: String) {
self.add_to_history(HistoryCell::new_log_line(line))
}
pub fn add_error(&mut self, message: String) {
self.add_to_history(HistoryCell::new_error_event(message));
}

View File

@@ -49,16 +49,24 @@ pub(crate) enum PatchEventType {
/// scrollable list.
pub(crate) enum HistoryCell {
/// Welcome message.
WelcomeMessage { view: TextBlock },
WelcomeMessage {
view: TextBlock,
},
/// Message from the user.
UserPrompt { view: TextBlock },
UserPrompt {
view: TextBlock,
},
/// Message from the agent.
AgentMessage { view: TextBlock },
AgentMessage {
view: TextBlock,
},
/// Reasoning event from the agent.
AgentReasoning { view: TextBlock },
AgentReasoning {
view: TextBlock,
},
/// An exec tool call that has not finished yet.
ActiveExecCommand {
@@ -70,7 +78,9 @@ pub(crate) enum HistoryCell {
},
/// Completed exec tool call.
CompletedExecCommand { view: TextBlock },
CompletedExecCommand {
view: TextBlock,
},
/// An MCP tool call that has not finished yet.
ActiveMcpToolCall {
@@ -82,7 +92,9 @@ pub(crate) enum HistoryCell {
},
/// Completed MCP tool call where we show the result serialized as JSON.
CompletedMcpToolCall { view: TextBlock },
CompletedMcpToolCall {
view: TextBlock,
},
/// Completed MCP tool call where the result is an image.
/// Admittedly, [mcp_types::CallToolResult] can have multiple content types,
@@ -101,18 +113,30 @@ pub(crate) enum HistoryCell {
},
/// Background event.
BackgroundEvent { view: TextBlock },
BackgroundEvent {
view: TextBlock,
},
/// Error event from the backend.
ErrorEvent { view: TextBlock },
ErrorEvent {
view: TextBlock,
},
/// Info describing the newly-initialized session.
SessionInfo { view: TextBlock },
SessionInfo {
view: TextBlock,
},
/// A pending code patch that is awaiting user approval. Mirrors the
/// behaviour of `ActiveExecCommand` so the user sees *what* patch the
/// model wants to apply before being prompted to approve or deny it.
PendingPatch { view: TextBlock },
PendingPatch {
view: TextBlock,
},
Log {
view: TextBlock,
},
}
const TOOL_CALL_MAX_LINES: usize = 5;
@@ -459,6 +483,14 @@ impl HistoryCell {
}
}
pub(crate) fn new_log_line(message: String) -> Self {
let mut lines: Vec<Line<'static>> = Vec::new();
lines.extend(message.lines().map(|l| Line::from(l.to_string()).dim()));
HistoryCell::Log {
view: TextBlock::new(lines),
}
}
pub(crate) fn new_error_event(message: String) -> Self {
let lines: Vec<Line<'static>> = vec![
vec!["ERROR: ".red().bold(), message.into()].into(),
@@ -554,6 +586,7 @@ impl CellWidget for HistoryCell {
| HistoryCell::CompletedMcpToolCall { view }
| HistoryCell::PendingPatch { view }
| HistoryCell::ActiveExecCommand { view, .. }
| HistoryCell::Log { view }
| HistoryCell::ActiveMcpToolCall { view, .. } => view.height(width),
HistoryCell::CompletedMcpToolCallWithImageOutput {
image,
@@ -575,6 +608,7 @@ impl CellWidget for HistoryCell {
| HistoryCell::CompletedMcpToolCall { view }
| HistoryCell::PendingPatch { view }
| HistoryCell::ActiveExecCommand { view, .. }
| HistoryCell::Log { view }
| HistoryCell::ActiveMcpToolCall { view, .. } => {
view.render_window(first_visible_line, area, buf)
}

View File

@@ -122,7 +122,7 @@ pub fn run_main(cli: Cli, codex_linux_sandbox_exe: Option<PathBuf>) -> std::io::
// Channel that carries formatted log lines to the UI.
let (log_tx, log_rx) = tokio::sync::mpsc::unbounded_channel::<String>();
let tui_layer = TuiLogLayer::new(log_tx.clone(), 120).with_filter(env_filter());
let tui_layer = TuiLogLayer::new(log_tx.clone()).with_filter(env_filter());
let _ = tracing_subscriber::registry()
.with(file_layer)

View File

@@ -19,22 +19,13 @@ use tracing_subscriber::Layer;
use tracing_subscriber::layer::Context;
use tracing_subscriber::registry::LookupSpan;
/// Maximum characters forwarded to the TUI. Longer messages are truncated so the
/// singleline status indicator cannot overflow the viewport.
#[allow(dead_code)]
const _DEFAULT_MAX_LEN: usize = 120;
pub struct TuiLogLayer {
tx: UnboundedSender<String>,
max_len: usize,
}
impl TuiLogLayer {
pub fn new(tx: UnboundedSender<String>, max_len: usize) -> Self {
Self {
tx,
max_len: max_len.max(8),
}
pub fn new(tx: UnboundedSender<String>) -> Self {
Self { tx }
}
}
@@ -67,27 +58,6 @@ where
event.record(&mut Visitor { buf: &mut buf });
// `String::truncate` operates on UTF8 codepoint boundaries and will
// panic if the provided index is not one. Because we limit the log
// line by its **byte** length we can not guarantee that the index we
// want to cut at happens to be on a boundary. Therefore we fall back
// to a simple, boundarysafe loop that pops complete characters until
// the string is within the designated size.
if buf.len() > self.max_len {
// Attempt direct truncate at the byte index. If that is not a
// valid boundary we advance to the next one ( ≤3 bytes away ).
if buf.is_char_boundary(self.max_len) {
buf.truncate(self.max_len);
} else {
let mut idx = self.max_len;
while idx < buf.len() && !buf.is_char_boundary(idx) {
idx += 1;
}
buf.truncate(idx);
}
}
let sanitized = buf.replace(['\n', '\r'], " ");
let _ = self.tx.send(sanitized);
}

View File

@@ -83,11 +83,6 @@ impl StatusIndicatorWidget {
pub(crate) fn get_height(&self) -> u16 {
self.height
}
/// Update the line that is displayed in the widget.
pub(crate) fn update_text(&mut self, text: String) {
self.text = text.replace(['\n', '\r'], " ");
}
}
impl Drop for StatusIndicatorWidget {