diff --git a/src-tauri/src/parser/entry.rs b/src-tauri/src/parser/entry.rs index ef2ab90..8959b24 100644 --- a/src-tauri/src/parser/entry.rs +++ b/src-tauri/src/parser/entry.rs @@ -270,6 +270,19 @@ mod tests { assert_eq!(e.entry_type, "response_item"); } + // Codex v0.130.0 (PR #21683): "research preview" text removed from startup banner. + // codex-trace reads version from session_meta.payload.cli_version — not from any + // banner text output by `codex exec`. This test confirms v0.130.0 sessions parse + // correctly and that version detection is unaffected by the banner wording change. + #[test] + fn v0130_startup_banner_research_preview_removal_does_not_affect_version_detection() { + let line = r#"{"timestamp":"2026-05-08T10:00:00Z","type":"session_meta","payload":{"id":"v0130-session","timestamp":"2026-05-08T10:00:00Z","cwd":"/tmp","cli_version":"0.130.0","model_provider":"openai"}}"#; + let e = RawEntry::parse(line).unwrap(); + assert_eq!(e.entry_type, "session_meta"); + assert_eq!(e.payload["cli_version"], "0.130.0"); + assert_eq!(e.payload["id"], "v0130-session"); + } + #[test] fn log_db_log_writer_refactor_does_not_affect_jsonl_session_parser() { // Codex v0.128.0 PRs #19234/#19959 refactored the internal log DB into a @@ -298,4 +311,32 @@ mod tests { let meta = RawEntry::parse(lines[0]).unwrap(); assert_eq!(meta.payload["cli_version"], "0.128.0"); } + + #[test] + fn codex_v0_130_0_startup_banner_change_does_not_affect_session_parsing() { + // Codex v0.130.0 (PR #21683) removed "research preview" from the `codex exec` + // startup banner. Session JSONL files at ~/.codex/sessions/ contain structured + // data, not banner text — so this UI change has no effect on parsing. Verify + // that all standard entry types from a v0.130.0 session parse correctly. + let lines = [ + r#"{"timestamp":"2026-05-08T10:00:00Z","type":"session_meta","payload":{"id":"v0130-session","timestamp":"2026-05-08T10:00:00Z","cwd":"/tmp","cli_version":"0.130.0","model_provider":"openai"}}"#, + r#"{"timestamp":"2026-05-08T10:00:01Z","type":"event_msg","payload":{"type":"task_started","turn_id":"turn-1"}}"#, + r#"{"timestamp":"2026-05-08T10:00:02Z","type":"response_item","payload":{"type":"message","role":"assistant","content":"Hello"}}"#, + r#"{"timestamp":"2026-05-08T10:00:03Z","type":"turn_context","payload":{"model":"gpt-5","cwd":"/tmp"}}"#, + r#"{"timestamp":"2026-05-08T10:00:04Z","type":"event_msg","payload":{"type":"task_complete","turn_id":"turn-1","completed_at":1746700804.0}}"#, + ]; + let expected_types = [ + "session_meta", + "event_msg", + "response_item", + "turn_context", + "event_msg", + ]; + for (line, expected) in lines.iter().zip(expected_types.iter()) { + let entry = RawEntry::parse(line).expect("parse failed"); + assert_eq!(entry.entry_type, *expected, "wrong type for: {line}"); + } + let meta = RawEntry::parse(lines[0]).unwrap(); + assert_eq!(meta.payload["cli_version"], "0.130.0"); + } } diff --git a/src-tauri/src/parser/toolcall.rs b/src-tauri/src/parser/toolcall.rs index ce99129..ef8626d 100644 --- a/src-tauri/src/parser/toolcall.rs +++ b/src-tauri/src/parser/toolcall.rs @@ -962,7 +962,7 @@ fn extract_mcp_output(payload: &Value) -> Option { #[cfg(test)] mod tests { - use super::parse_mcp_namespace; + use super::{parse_exec_function_output, parse_mcp_namespace}; #[test] fn namespace_with_tool_prefix_keeps_full_namespace_as_server() { @@ -997,4 +997,31 @@ mod tests { assert_eq!(server, None); assert_eq!(tool, None); } + + #[test] + fn exec_output_parsing_unaffected_by_codex_v0_130_0_banner_change() { + // Codex v0.130.0 (PR #21683) removed "research preview" from the `codex exec` + // startup banner. codex-trace must not pattern-match on banner wording — only + // stable structural markers (Output:, exit code, wall time, session id) are used. + let old_banner = "Codex - a coding agent (research preview)\nOutput:\nhello\nExit code: 0\nWall time: 0.5s\n"; + let new_banner = "Codex - a coding agent\nOutput:\nhello\nExit code: 0\nWall time: 0.5s\n"; + + let old = parse_exec_function_output(old_banner); + let new = parse_exec_function_output(new_banner); + + assert_eq!( + old.status, new.status, + "status differs between banner formats" + ); + assert_eq!(old.exit_code, new.exit_code); + assert_eq!(old.duration_secs, new.duration_secs); + let old_out = old.output.unwrap_or_default(); + let new_out = new.output.unwrap_or_default(); + assert!(old_out.contains("hello"), "old banner output missing hello"); + assert!(new_out.contains("hello"), "new banner output missing hello"); + assert!( + !old_out.contains("research preview"), + "banner text leaked into output" + ); + } }