From c5d242fa7907bff1b7a7e26e95febc548c0a6963 Mon Sep 17 00:00:00 2001 From: Owen Lin Date: Fri, 2 Oct 2026 18:38:06 +0000 Subject: [PATCH] Consolidate command execution output into `aggregated_output` (#50402) ## What changed - Remove `stdout`, `stderr`, and `formatted_output` from `CommandExecutionItem` and `ExecCommandEndEvent`, retaining `aggregated_output` as the output field. - Update command event producers, legacy conversion, rollout traces, and the precomputed schema. Use aggregated output for terminal trace results. - Clear `interaction_input` in emitted and migrated command completion items. ## Testing Update command execution and trace tests to use aggregated output, and assert that legacy rollout migration preserves `aggregatedOutput`. GitOrigin-RevId: ed59827d4a5ec9a767084ee4feac72c2042440f2 --- .../app-server-exports-stable.json.zst | Bin 157509 -> 157454 bytes .../src/protocol/thread_history.rs | 18 ---------- .../src/protocol/v2/tests.rs | 3 -- codex-rs/core/src/session/tests.rs | 2 +- codex-rs/core/src/tasks/user_shell.rs | 19 ----------- codex-rs/core/src/tools/events.rs | 31 +++--------------- .../src/unified_exec/async_watcher_tests.rs | 7 ---- codex-rs/core/tests/suite/shell_snapshot.rs | 28 +++++++++------- codex-rs/core/tests/suite/step_settings.rs | 8 ----- codex-rs/core/tests/suite/unified_exec.rs | 8 ++--- codex-rs/core/tests/suite/user_shell_cmd.rs | 23 ++++++------- codex-rs/protocol/src/items.rs | 9 ----- codex-rs/protocol/src/legacy_events.rs | 3 -- codex-rs/protocol/src/protocol.rs | 12 ------- codex-rs/rollout-trace/src/protocol_event.rs | 9 ----- .../rollout-trace/src/protocol_event_tests.rs | 10 ++---- .../src/reducer/tool/terminal.rs | 11 +++---- .../src/reducer/tool/terminal_tests.rs | 6 ++-- .../local/rollout_migration/legacy_event.rs | 6 +--- .../src/local/rollout_migration_tests.rs | 1 + 20 files changed, 47 insertions(+), 167 deletions(-) diff --git a/codex-rs/app-server-protocol/schema/precomputed/app-server-exports-stable.json.zst b/codex-rs/app-server-protocol/schema/precomputed/app-server-exports-stable.json.zst index 04ec7af91f17bc127ae2a3b6db84b4c5ae4b2c56..97809f7e6a7007cce9c9003d8d9141b5df0470aa 100644 GIT binary patch delta 20314 zcmV(rK<>ZA&Iyjr34nwFv;uP%e+>HomM(!SfNMH4Fat9L7&swN+dC!2orw^Uo9raJ zrAZ{04hB7&ApkYA*p20mBBKFw0Cxa%0D7tP^cMUiI$hda3n3>uchY&KPH9!nx#?ap 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 Y0SC?HW anyhow::R let event = rx.recv().await.expect("channel open"); if let EventMsg::ExecCommandEnd(event) = event.msg { assert_eq!(event.exit_code, 0); - assert_eq!(event.stdout.trim(), "not-set"); + assert_eq!(event.aggregated_output.trim(), "not-set"); break; } } diff --git a/codex-rs/core/src/tasks/user_shell.rs b/codex-rs/core/src/tasks/user_shell.rs index e802122c0f..a58d3dbb59 100644 --- a/codex-rs/core/src/tasks/user_shell.rs +++ b/codex-rs/core/src/tasks/user_shell.rs @@ -21,7 +21,6 @@ use crate::session::TurnInput; use crate::session::turn_context::TurnContext; use crate::shell::Shell; use crate::state::TaskKind; -use crate::tools::format_exec_output_str; use crate::tools::runtimes::RuntimePathPrepends; #[cfg(unix)] use crate::tools::runtimes::apply_package_path_prepend; @@ -200,12 +199,9 @@ pub(crate) async fn execute_user_shell_command( source: ExecCommandSource::UserShell, interaction_input: None, status: CommandExecutionStatus::InProgress, - stdout: None, - stderr: None, aggregated_output: None, exit_code: None, duration: None, - formatted_output: None, }), ) .await; @@ -281,12 +277,9 @@ pub(crate) async fn execute_user_shell_command( source: ExecCommandSource::UserShell, interaction_input: None, status: CommandExecutionStatus::Failed, - stdout: Some(String::new()), - stderr: Some(aborted_message.clone()), aggregated_output: Some(aborted_message.clone()), exit_code: Some(-1), duration: Some(Duration::ZERO), - formatted_output: Some(aborted_message), }), ) .await; @@ -312,15 +305,9 @@ pub(crate) async fn execute_user_shell_command( } else { CommandExecutionStatus::Failed }, - stdout: Some(output.stdout.text.clone()), - stderr: Some(output.stderr.text.clone()), aggregated_output: Some(output.aggregated_output.text.clone()), exit_code: Some(output.exit_code), duration: Some(output.duration), - formatted_output: Some(format_exec_output_str( - &output, - turn_context.model_info().truncation_policy.into(), - )), }), ) .await; @@ -355,15 +342,9 @@ pub(crate) async fn execute_user_shell_command( source: ExecCommandSource::UserShell, interaction_input: None, status: CommandExecutionStatus::Failed, - stdout: Some(exec_output.stdout.text.clone()), - stderr: Some(exec_output.stderr.text.clone()), aggregated_output: Some(exec_output.aggregated_output.text.clone()), exit_code: Some(exec_output.exit_code), duration: Some(exec_output.duration), - formatted_output: Some(format_exec_output_str( - &exec_output, - turn_context.model_info().truncation_policy.into(), - )), }), ) .await; diff --git a/codex-rs/core/src/tools/events.rs b/codex-rs/core/src/tools/events.rs index 61eedddc7d..2d5262ee9b 100644 --- a/codex-rs/core/src/tools/events.rs +++ b/codex-rs/core/src/tools/events.rs @@ -34,8 +34,6 @@ use std::collections::HashMap; use std::path::PathBuf; use std::time::Duration; -use super::format_exec_output_str; - const REJECTION_MESSAGE_MAX_TOKENS: usize = 900; pub(super) fn truncate_rejection_message(message: &str) -> String { @@ -185,12 +183,9 @@ async fn emit_exec_command_begin(ctx: ToolEventCtx<'_>, exec_input: &ExecCommand source: exec_input.source, interaction_input: exec_input.interaction_input.map(str::to_owned), status: CommandExecutionStatus::InProgress, - stdout: None, - stderr: None, aggregated_output: None, exit_code: None, duration: None, - formatted_output: None, }), ) .await; @@ -511,12 +506,9 @@ impl<'a> ExecCommandInput<'a> { } struct ExecCommandResult { - stdout: String, - stderr: String, aggregated_output: String, exit_code: i32, duration: Duration, - formatted_output: String, status: ExecCommandStatus, } @@ -532,15 +524,9 @@ async fn emit_exec_stage( ToolEventStage::Success { output, .. } | ToolEventStage::Failure(ToolEventFailure::Output(output)) => { let exec_result = ExecCommandResult { - stdout: output.stdout.text.clone(), - stderr: output.stderr.text.clone(), - aggregated_output: output.aggregated_output.text.clone(), + aggregated_output: output.aggregated_output.text, exit_code: output.exit_code, duration: output.duration, - formatted_output: format_exec_output_str( - &output, - ctx.model_info.truncation_policy.into(), - ), status: if output.exit_code == 0 { ExecCommandStatus::Completed } else { @@ -552,12 +538,9 @@ async fn emit_exec_stage( ToolEventStage::Failure(ToolEventFailure::Message(message)) => { let text = message.to_string(); let exec_result = ExecCommandResult { - stdout: String::new(), - stderr: text.clone(), - aggregated_output: text.clone(), + aggregated_output: text, exit_code: -1, duration: Duration::ZERO, - formatted_output: text, status: ExecCommandStatus::Failed, }; emit_exec_end(ctx, exec_input, exec_result).await; @@ -565,12 +548,9 @@ async fn emit_exec_stage( ToolEventStage::Failure(ToolEventFailure::Rejected { message, .. }) => { let text = message.to_string(); let exec_result = ExecCommandResult { - stdout: String::new(), - stderr: text.clone(), - aggregated_output: text.clone(), + aggregated_output: text, exit_code: -1, duration: Duration::ZERO, - formatted_output: text, status: ExecCommandStatus::Declined, }; emit_exec_end(ctx, exec_input, exec_result).await; @@ -598,14 +578,11 @@ async fn emit_exec_end( cwd: exec_input.cwd.clone(), parsed_cmd: exec_input.parsed_cmd.to_vec(), source: exec_input.source, - interaction_input: exec_input.interaction_input.map(str::to_owned), + interaction_input: None, status: exec_result.status.into(), - stdout: Some(exec_result.stdout), - stderr: Some(exec_result.stderr), aggregated_output: Some(exec_result.aggregated_output), exit_code: Some(exec_result.exit_code), duration: Some(exec_result.duration), - formatted_output: Some(exec_result.formatted_output), }), ) .await; diff --git a/codex-rs/core/src/unified_exec/async_watcher_tests.rs b/codex-rs/core/src/unified_exec/async_watcher_tests.rs index d5e1b9ba3e..8443994551 100644 --- a/codex-rs/core/src/unified_exec/async_watcher_tests.rs +++ b/codex-rs/core/src/unified_exec/async_watcher_tests.rs @@ -357,13 +357,6 @@ async fn exit_watcher_waits_for_late_network_denial_before_classifying_end() -> Some("LATE_DENIAL") ) ); - assert_eq!( - item.formatted_output, - Some(codex_utils_output_truncation::formatted_truncate_text( - "LATE_DENIAL", - codex_utils_output_truncation::TruncationPolicy::Bytes(4), - )) - ); assert!( elapsed >= Duration::from_millis(10) && elapsed < TRAILING_OUTPUT_GRACE, "completion should wait for denial without falling back to the output grace: {elapsed:?}" diff --git a/codex-rs/core/tests/suite/shell_snapshot.rs b/codex-rs/core/tests/suite/shell_snapshot.rs index af6881b332..7e047b44f4 100644 --- a/codex-rs/core/tests/suite/shell_snapshot.rs +++ b/codex-rs/core/tests/suite/shell_snapshot.rs @@ -905,7 +905,7 @@ async fn shell_snapshot_v2_filters_profile_secrets_without_creating_files() -> R assert_eq!(end.exit_code, 0); assert_eq!( - normalize_newlines(&end.stdout).trim(), + normalize_newlines(&end.aggregated_output).trim(), "helper|path|policy|missing" ); } @@ -953,7 +953,7 @@ async fn shell_snapshot_v2_preserves_legacy_snapshots_for_user_shell() -> Result .await; assert_eq!(end.exit_code, 0); - assert_eq!(normalize_newlines(&end.stdout).trim(), "legacy"); + assert_eq!(normalize_newlines(&end.aggregated_output).trim(), "legacy"); Ok(()) } @@ -962,7 +962,7 @@ async fn shell_snapshot_v2_preserves_legacy_snapshots_for_user_shell() -> Result async fn linux_unified_exec_uses_shell_snapshot() -> Result<()> { let command = "echo snapshot-linux"; let run = run_snapshot_command(command).await?; - let stdout = normalize_newlines(&run.end.stdout); + let output = normalize_newlines(&run.end.aggregated_output); assert_eq!(run.begin.command.get(1).map(String::as_str), Some("-lc")); assert_eq!(run.begin.command.get(2).map(String::as_str), Some(command)); @@ -971,8 +971,8 @@ async fn linux_unified_exec_uses_shell_snapshot() -> Result<()> { assert_posix_snapshot_sections(&run.snapshot_content); assert_eq!(run.end.exit_code, 0); assert!( - stdout.contains("snapshot-linux"), - "stdout should contain snapshot marker; stdout={stdout:?}" + output.contains("snapshot-linux"), + "output should contain snapshot marker; output={output:?}" ); Ok(()) @@ -1018,7 +1018,7 @@ async fn unified_exec_snapshot_preserves_shell_environment_policy_set() -> Resul .await?; assert_eq!( - normalize_newlines(&end.stdout).trim(), + normalize_newlines(&end.aggregated_output).trim(), POLICY_SUCCESS_OUTPUT ); assert_eq!(end.exit_code, 0); @@ -1239,10 +1239,13 @@ async fn macos_unified_exec_resolves_command_from_tied_path_snapshot( assert_eq!( end.exit_code, 0, - "tied-path command failed: stderr={:?}", - end.stderr + "tied-path command failed: output={:?}", + end.aggregated_output + ); + assert_eq!( + normalize_newlines(&end.aggregated_output).trim(), + "tied-path-command" ); - assert_eq!(normalize_newlines(&end.stdout).trim(), "tied-path-command"); Ok(()) } @@ -1274,7 +1277,10 @@ async fn macos_unified_exec_uses_shell_snapshot() -> Result<()> { assert!(run.snapshot_path.starts_with(&run.codex_home)); assert_posix_snapshot_sections(&run.snapshot_content); - assert_eq!(normalize_newlines(&run.end.stdout).trim(), "snapshot-macos"); + assert_eq!( + normalize_newlines(&run.end.aggregated_output).trim(), + "snapshot-macos" + ); assert_eq!(run.end.exit_code, 0); Ok(()) @@ -1308,7 +1314,7 @@ async fn windows_unified_exec_uses_shell_snapshot() -> Result<()> { assert!(run.snapshot_content.contains("# aliases ")); assert!(run.snapshot_content.contains("# exports ")); assert_eq!( - normalize_newlines(&run.end.stdout).trim(), + normalize_newlines(&run.end.aggregated_output).trim(), "snapshot-windows" ); assert_eq!(run.end.exit_code, 0); diff --git a/codex-rs/core/tests/suite/step_settings.rs b/codex-rs/core/tests/suite/step_settings.rs index 8308c42436..5334a593b7 100644 --- a/codex-rs/core/tests/suite/step_settings.rs +++ b/codex-rs/core/tests/suite/step_settings.rs @@ -2815,8 +2815,6 @@ async fn captured_step_controls_exec_completion_and_write_stdin_output() -> Resu .await; let end = end.expect("exec completion"); - use codex_utils_output_truncation::TruncationPolicy; - use codex_utils_output_truncation::formatted_truncate_text; let requests = responses.requests(); let exec = requests[2] .function_call_output_text("exec-b") @@ -2836,13 +2834,7 @@ async fn captured_step_controls_exec_completion_and_write_stdin_output() -> Resu "{stdin}" ); assert_eq!(end.exit_code, 7); - // The formatting check needs an untruncated chunk, independent of how the executor - // aggregates output across the initial command and later stdin interactions. assert!(end.aggregated_output.contains(&output)); - assert_eq!( - end.formatted_output, - formatted_truncate_text(&end.aggregated_output, TruncationPolicy::Bytes(400)) - ); Ok(()) } diff --git a/codex-rs/core/tests/suite/unified_exec.rs b/codex-rs/core/tests/suite/unified_exec.rs index 3d95f61190..194652b926 100644 --- a/codex-rs/core/tests/suite/unified_exec.rs +++ b/codex-rs/core/tests/suite/unified_exec.rs @@ -1113,12 +1113,8 @@ async fn unified_exec_emits_output_delta_for_exec_command() -> Result<()> { ); let end_event = end_event.expect("expected command completion"); assert_eq!( - ( - end_event.exit_code, - end_event.stdout, - end_event.aggregated_output - ), - (0, "HELLO-UEXECé�".to_string(), "HELLO-UEXECé�".to_string()) + (end_event.exit_code, end_event.aggregated_output), + (0, "HELLO-UEXECé�".to_string()) ); Ok(()) } diff --git a/codex-rs/core/tests/suite/user_shell_cmd.rs b/codex-rs/core/tests/suite/user_shell_cmd.rs index 48e42a0e37..49d27cf395 100644 --- a/codex-rs/core/tests/suite/user_shell_cmd.rs +++ b/codex-rs/core/tests/suite/user_shell_cmd.rs @@ -77,15 +77,17 @@ async fn user_shell_cmd_ls_and_cat_in_temp_dir() { .unwrap(); let msg = wait_for_event(&codex, |ev| matches!(ev, EventMsg::ExecCommandEnd(_))).await; let EventMsg::ExecCommandEnd(ExecCommandEndEvent { - stdout, exit_code, .. + aggregated_output, + exit_code, + .. }) = msg else { unreachable!() }; assert_eq!(exit_code, 0); assert!( - stdout.contains(file_name), - "ls output should include {file_name}, got: {stdout:?}" + aggregated_output.contains(file_name), + "ls output should include {file_name}, got: {aggregated_output:?}" ); // 2) shell command should print the file contents verbatim @@ -99,7 +101,7 @@ async fn user_shell_cmd_ls_and_cat_in_temp_dir() { .unwrap(); let msg = wait_for_event(&codex, |ev| matches!(ev, EventMsg::ExecCommandEnd(_))).await; let EventMsg::ExecCommandEnd(ExecCommandEndEvent { - mut stdout, + mut aggregated_output, exit_code, .. }) = msg @@ -109,9 +111,9 @@ async fn user_shell_cmd_ls_and_cat_in_temp_dir() { assert_eq!(exit_code, 0); if cfg!(windows) { // Windows shells emit CRLF line endings; normalize so the assertion remains portable. - stdout = stdout.replace("\r\n", "\n"); + aggregated_output = aggregated_output.replace("\r\n", "\n"); } - assert_eq!(stdout, contents); + assert_eq!(aggregated_output, contents); } #[tokio::test] @@ -447,7 +449,7 @@ async fn user_shell_command_history_is_persisted_and_shared_with_model() -> anyh }) .await; assert_eq!(end_event.exit_code, 0); - assert_eq!(end_event.stdout.trim(), "not-set"); + assert_eq!(end_event.aggregated_output.trim(), "not-set"); let _ = wait_for_event(&test.codex, |ev| matches!(ev, EventMsg::TurnComplete(_))).await; @@ -507,8 +509,7 @@ async fn user_shell_command_does_not_set_network_sandbox_env_var() -> anyhow::Re let ExecCommandEndEvent { exit_code, - stdout, - stderr, + aggregated_output, .. } = wait_for_event_match(&test.codex, |ev| match ev { EventMsg::ExecCommandEnd(event) => Some(event.clone()), @@ -518,9 +519,9 @@ async fn user_shell_command_does_not_set_network_sandbox_env_var() -> anyhow::Re assert_eq!( exit_code, 0, - "shell command should execute successfully. stdout=`{stdout}`, stderr=`{stderr}`", + "shell command should execute successfully. output=`{aggregated_output}`", ); - assert_eq!(stdout.trim(), "not-set"); + assert_eq!(aggregated_output.trim(), "not-set"); Ok(()) } diff --git a/codex-rs/protocol/src/items.rs b/codex-rs/protocol/src/items.rs index 0ba13300f8..0e48870650 100644 --- a/codex-rs/protocol/src/items.rs +++ b/codex-rs/protocol/src/items.rs @@ -271,12 +271,6 @@ pub struct CommandExecutionItem { pub status: CommandExecutionStatus, #[serde(default, skip_serializing_if = "Option::is_none")] #[ts(optional)] - pub stdout: Option, - #[serde(default, skip_serializing_if = "Option::is_none")] - #[ts(optional)] - pub stderr: Option, - #[serde(default, skip_serializing_if = "Option::is_none")] - #[ts(optional)] pub aggregated_output: Option, #[serde(default, skip_serializing_if = "Option::is_none")] #[ts(optional)] @@ -284,9 +278,6 @@ pub struct CommandExecutionItem { #[serde(default, skip_serializing_if = "Option::is_none")] #[ts(type = "string", optional)] pub duration: Option, - #[serde(default, skip_serializing_if = "Option::is_none")] - #[ts(optional)] - pub formatted_output: Option, } #[derive(Debug, Clone, Copy, Deserialize, Serialize, TS, JsonSchema, PartialEq, Eq)] diff --git a/codex-rs/protocol/src/legacy_events.rs b/codex-rs/protocol/src/legacy_events.rs index fb59d1a0ad..05a66f6cee 100644 --- a/codex-rs/protocol/src/legacy_events.rs +++ b/codex-rs/protocol/src/legacy_events.rs @@ -222,12 +222,9 @@ impl CommandExecutionItem { parsed_cmd: self.parsed_cmd.clone(), source: self.source, interaction_input: self.interaction_input.clone(), - stdout: self.stdout.clone().unwrap_or_default(), - stderr: self.stderr.clone().unwrap_or_default(), aggregated_output: self.aggregated_output.clone().unwrap_or_default(), exit_code: self.exit_code.unwrap_or_default(), duration: self.duration.unwrap_or_default(), - formatted_output: self.formatted_output.clone().unwrap_or_default(), status, })) } diff --git a/codex-rs/protocol/src/protocol.rs b/codex-rs/protocol/src/protocol.rs index 2ccac2bff4..b3b5f3aaf3 100644 --- a/codex-rs/protocol/src/protocol.rs +++ b/codex-rs/protocol/src/protocol.rs @@ -3625,10 +3625,6 @@ pub struct ExecCommandEndEvent { #[ts(optional)] pub interaction_input: Option, - /// Captured stdout - pub stdout: String, - /// Captured stderr - pub stderr: String, /// Captured aggregated output #[serde(default)] pub aggregated_output: String, @@ -3637,8 +3633,6 @@ pub struct ExecCommandEndEvent { /// The duration of the command execution. #[ts(type = "string")] pub duration: Duration, - /// Formatted output from the command, as seen by the model. - pub formatted_output: String, /// Completion status for this command execution. pub status: ExecCommandStatus, } @@ -5598,12 +5592,9 @@ mod tests { source: ExecCommandSource::Agent, interaction_input: None, status: CommandExecutionStatus::InProgress, - stdout: None, - stderr: None, aggregated_output: None, exit_code: None, duration: None, - formatted_output: None, }), }; let completed = ItemCompletedEvent { @@ -5626,12 +5617,9 @@ mod tests { source: ExecCommandSource::Agent, interaction_input: None, status: CommandExecutionStatus::Completed, - stdout: Some("done\n".into()), - stderr: Some(String::new()), aggregated_output: Some("done\n".into()), exit_code: Some(0), duration: Some(Duration::from_millis(5)), - formatted_output: Some("done\n".into()), }), }; diff --git a/codex-rs/rollout-trace/src/protocol_event.rs b/codex-rs/rollout-trace/src/protocol_event.rs index dadc4bb0ac..b5e23153d5 100644 --- a/codex-rs/rollout-trace/src/protocol_event.rs +++ b/codex-rs/rollout-trace/src/protocol_event.rs @@ -220,12 +220,9 @@ struct ExecCommandEndTracePayload<'a> { source: ExecCommandSource, #[serde(skip_serializing_if = "Option::is_none")] interaction_input: Option<&'a str>, - stdout: &'a str, - stderr: &'a str, aggregated_output: &'a str, exit_code: i32, duration: Duration, - formatted_output: &'a str, status: &'a ExecCommandStatus, } @@ -243,12 +240,9 @@ impl<'a> From<&'a ExecCommandEndEvent> for ExecCommandEndTracePayload<'a> { parsed_cmd, source, interaction_input, - stdout, - stderr, aggregated_output, exit_code, duration, - formatted_output, status, } = event; Self { @@ -263,12 +257,9 @@ impl<'a> From<&'a ExecCommandEndEvent> for ExecCommandEndTracePayload<'a> { parsed_cmd, source: *source, interaction_input: interaction_input.as_deref(), - stdout, - stderr, aggregated_output, exit_code: *exit_code, duration: *duration, - formatted_output, status, } } diff --git a/codex-rs/rollout-trace/src/protocol_event_tests.rs b/codex-rs/rollout-trace/src/protocol_event_tests.rs index c7c53c3ef3..acd1f0231a 100644 --- a/codex-rs/rollout-trace/src/protocol_event_tests.rs +++ b/codex-rs/rollout-trace/src/protocol_event_tests.rs @@ -93,12 +93,9 @@ fn exec_command_trace_payloads_use_inferred_native_cwd() -> anyhow::Result<()> { parsed_cmd: Vec::new(), source: ExecCommandSource::UnifiedExecInteraction, interaction_input: Some("input".to_string()), - stdout: "output".to_string(), - stderr: String::new(), - aggregated_output: "output".to_string(), + aggregated_output: "combined output".to_string(), exit_code: 0, duration: Duration::from_millis(250), - formatted_output: "output".to_string(), status: ExecCommandStatus::Completed, }); @@ -138,12 +135,9 @@ fn exec_command_trace_payloads_use_inferred_native_cwd() -> anyhow::Result<()> { "parsed_cmd": [], "source": "unified_exec_interaction", "interaction_input": "input", - "stdout": "output", - "stderr": "", - "aggregated_output": "output", + "aggregated_output": "combined output", "exit_code": 0, "duration": {"secs": 0, "nanos": 250000000}, - "formatted_output": "output", "status": "completed" }) ); diff --git a/codex-rs/rollout-trace/src/reducer/tool/terminal.rs b/codex-rs/rollout-trace/src/reducer/tool/terminal.rs index cdd28ec5da..20d5a34a4e 100644 --- a/codex-rs/rollout-trace/src/reducer/tool/terminal.rs +++ b/codex-rs/rollout-trace/src/reducer/tool/terminal.rs @@ -450,9 +450,9 @@ fn parse_protocol_terminal_response(payload: ExecCommandEndPayload) -> ParsedTer terminal_id: payload.process_id, result: TerminalResult { exit_code: Some(payload.exit_code), - stdout: payload.stdout, - stderr: payload.stderr, - formatted_output: Some(payload.formatted_output), + stdout: payload.aggregated_output, + stderr: String::new(), + formatted_output: None, original_token_count: None, chunk_id: None, }, @@ -557,10 +557,9 @@ struct ExecCommandBeginPayload { #[derive(Deserialize)] struct ExecCommandEndPayload { process_id: Option, - stdout: String, - stderr: String, + #[serde(default)] + aggregated_output: String, exit_code: i32, - formatted_output: String, } #[derive(Deserialize)] diff --git a/codex-rs/rollout-trace/src/reducer/tool/terminal_tests.rs b/codex-rs/rollout-trace/src/reducer/tool/terminal_tests.rs index e51e54a740..a42f3bbd0d 100644 --- a/codex-rs/rollout-trace/src/reducer/tool/terminal_tests.rs +++ b/codex-rs/rollout-trace/src/reducer/tool/terminal_tests.rs @@ -81,10 +81,8 @@ fn exec_tool_reduces_to_terminal_operation_and_session() -> anyhow::Result<()> { "turn_id": "turn-1", "command": ["cargo", "test"], "cwd": "/repo", - "stdout": "ok\n", - "stderr": "", + "aggregated_output": "ok\n", "exit_code": 0, - "formatted_output": "ok\n", "status": "completed" }), )?; @@ -180,7 +178,7 @@ fn exec_tool_reduces_to_terminal_operation_and_session() -> anyhow::Result<()> { exit_code: Some(0), stdout: "ok\n".to_string(), stderr: String::new(), - formatted_output: Some("ok\n".to_string()), + formatted_output: None, original_token_count: None, chunk_id: None, }), diff --git a/codex-rs/thread-store/src/local/rollout_migration/legacy_event.rs b/codex-rs/thread-store/src/local/rollout_migration/legacy_event.rs index ce2e0691f6..a1481d62cd 100644 --- a/codex-rs/thread-store/src/local/rollout_migration/legacy_event.rs +++ b/codex-rs/thread-store/src/local/rollout_migration/legacy_event.rs @@ -277,16 +277,12 @@ pub(super) fn completed_item( cwd: event.cwd.clone(), parsed_cmd: event.parsed_cmd.clone(), source: event.source, - interaction_input: event.interaction_input.clone(), + interaction_input: None, status: event.status.clone().into(), - stdout: (!event.stdout.is_empty()).then(|| event.stdout.clone()), - stderr: (!event.stderr.is_empty()).then(|| event.stderr.clone()), aggregated_output: (!event.aggregated_output.is_empty()) .then(|| event.aggregated_output.clone()), exit_code: Some(event.exit_code), duration: Some(event.duration), - formatted_output: (!event.formatted_output.is_empty()) - .then(|| event.formatted_output.clone()), }), Some(event.turn_id.clone()), )), diff --git a/codex-rs/thread-store/src/local/rollout_migration_tests.rs b/codex-rs/thread-store/src/local/rollout_migration_tests.rs index d27cbd035c..c040d43a52 100644 --- a/codex-rs/thread-store/src/local/rollout_migration_tests.rs +++ b/codex-rs/thread-store/src/local/rollout_migration_tests.rs @@ -471,6 +471,7 @@ async fn migration_projects_explicit_and_implicit_legacy_completed_items() { let reasoning: serde_json::Value = serde_json::from_slice(&items.items[1].item_json).expect("parse projected reasoning"); assert_eq!(command["type"], "commandExecution"); + assert_eq!(command["aggregatedOutput"], "ok"); assert_eq!(reasoning["type"], "reasoning"); assert_eq!(reasoning["summary"], json!(["summary"])); assert_eq!(reasoning["content"], json!(["raw"]));