feat: grab-bag of improvements to exec output (#1179)

Fixes:

* Instantiate `EventProcessor` earlier in `lib.rs` so
`print_config_summary()` can be an instance method of it and leverage
its various `Style` fields to ensure it honors `with_ansi` properly.
* After printing the config summary, print out user's prompt with the
heading `User instructions:`. As noted in the comment, now that we can
read the instructions via stdin as of #1178, it is helpful to the user
to ensure they know what instructions were given to Codex.
* Use same colors/bold/italic settings for headers as the TUI, making
the output a bit easier to read.
This commit is contained in:
Michael Bolin
2025-05-30 16:22:10 -07:00
committed by GitHub
parent ae743d56b0
commit cf1d070538
2 changed files with 47 additions and 32 deletions

View File

@@ -1,4 +1,3 @@
use chrono::Utc;
use codex_common::elapsed::format_elapsed;
use codex_core::config::Config;
use codex_core::protocol::AgentMessageEvent;
@@ -37,11 +36,13 @@ pub(crate) struct EventProcessor {
// using .style() with one of these fields. If you need a new style, add a
// new field here.
bold: Style,
italic: Style,
dimmed: Style,
magenta: Style,
red: Style,
green: Style,
cyan: Style,
}
impl EventProcessor {
@@ -55,10 +56,12 @@ impl EventProcessor {
call_id_to_command,
call_id_to_patch,
bold: Style::new().bold(),
italic: Style::new().italic(),
dimmed: Style::new().dimmed(),
magenta: Style::new().magenta(),
red: Style::new().red(),
green: Style::new().green(),
cyan: Style::new().cyan(),
call_id_to_tool_call,
}
} else {
@@ -66,10 +69,12 @@ impl EventProcessor {
call_id_to_command,
call_id_to_patch,
bold: Style::new(),
italic: Style::new(),
dimmed: Style::new(),
magenta: Style::new(),
red: Style::new(),
green: Style::new(),
cyan: Style::new(),
call_id_to_tool_call,
}
}
@@ -94,43 +99,47 @@ struct PatchApplyBegin {
auto_approved: bool,
}
#[macro_export]
macro_rules! ts_println {
($($arg:tt)*) => {{
let now = Utc::now();
let now = chrono::Utc::now();
let formatted = now.format("%Y-%m-%dT%H:%M:%S").to_string();
print!("[{}] ", formatted);
println!($($arg)*);
}};
}
/// Print a concise summary of the effective configuration that will be used
/// for the session. This mirrors the information shown in the TUI welcome
/// screen.
pub(crate) fn print_config_summary(config: &Config, with_ansi: bool) {
let bold = if with_ansi {
Style::new().bold()
} else {
Style::new()
};
impl EventProcessor {
/// Print a concise summary of the effective configuration that will be used
/// for the session. This mirrors the information shown in the TUI welcome
/// screen.
pub(crate) fn print_config_summary(&mut self, config: &Config, prompt: &str) {
ts_println!("OpenAI Codex (research preview)\n--------");
ts_println!("OpenAI Codex (research preview)\n--------");
let entries = vec![
("workdir", config.cwd.display().to_string()),
("model", config.model.clone()),
("provider", config.model_provider_id.clone()),
("approval", format!("{:?}", config.approval_policy)),
("sandbox", format!("{:?}", config.sandbox_policy)),
];
let entries = vec![
("workdir", config.cwd.display().to_string()),
("model", config.model.clone()),
("provider", config.model_provider_id.clone()),
("approval", format!("{:?}", config.approval_policy)),
("sandbox", format!("{:?}", config.sandbox_policy)),
];
for (key, value) in entries {
println!("{} {}", format!("{key}: ").style(self.bold), value);
}
for (key, value) in entries {
println!("{} {}", format!("{key}: ").style(bold), value);
println!("--------");
// Echo the prompt that will be sent to the agent so it is visible in the
// transcript/logs before any events come in. Note the prompt may have been
// read from stdin, so it may not be visible in the terminal otherwise.
ts_println!(
"{}\n{}",
"User instructions:".style(self.bold).style(self.cyan),
prompt
);
}
println!("--------\n");
}
impl EventProcessor {
pub(crate) fn process_event(&mut self, event: Event) {
let Event { id: _, msg } = event;
match msg {
@@ -145,8 +154,10 @@ impl EventProcessor {
// Ignore.
}
EventMsg::AgentMessage(AgentMessageEvent { message }) => {
let prefix = "Agent message:".style(self.bold);
ts_println!("{prefix} {message}");
ts_println!(
"{}\n{message}",
"codex".style(self.bold).style(self.magenta)
);
}
EventMsg::ExecCommandBegin(ExecCommandBeginEvent {
call_id,
@@ -394,7 +405,11 @@ impl EventProcessor {
// Should we exit?
}
EventMsg::AgentReasoning(agent_reasoning_event) => {
println!("thinking: {}", agent_reasoning_event.text);
ts_println!(
"{}\n{}",
"thinking".style(self.italic).style(self.magenta),
agent_reasoning_event.text
);
}
EventMsg::SessionConfigured(session_configured_event) => {
let SessionConfiguredEvent {

View File

@@ -20,7 +20,6 @@ use codex_core::protocol::SandboxPolicy;
use codex_core::protocol::TaskCompleteEvent;
use codex_core::util::is_inside_git_repo;
use event_processor::EventProcessor;
use event_processor::print_config_summary;
use tracing::debug;
use tracing::error;
use tracing::info;
@@ -113,8 +112,10 @@ pub async fn run_main(cli: Cli, codex_linux_sandbox_exe: Option<PathBuf>) -> any
};
let config = Config::load_with_cli_overrides(cli_kv_overrides, overrides)?;
// Print the effective configuration so users can see what Codex is using.
print_config_summary(&config, stdout_with_ansi);
let mut event_processor = EventProcessor::create_with_ansi(stdout_with_ansi);
// Print the effective configuration and prompt so users can see what Codex
// is using.
event_processor.print_config_summary(&config, &prompt);
if !skip_git_repo_check && !is_inside_git_repo(&config) {
eprintln!("Not inside a Git repo and --skip-git-repo-check was not specified.");
@@ -204,7 +205,6 @@ pub async fn run_main(cli: Cli, codex_linux_sandbox_exe: Option<PathBuf>) -> any
info!("Sent prompt with event ID: {initial_prompt_task_id}");
// Run the loop until the task is complete.
let mut event_processor = EventProcessor::create_with_ansi(stdout_with_ansi);
while let Some(event) = rx.recv().await {
let (is_last_event, last_assistant_message) = match &event.msg {
EventMsg::TaskComplete(TaskCompleteEvent { last_agent_message }) => {