Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
76 changes: 74 additions & 2 deletions src-tauri/src/parser/discover.rs
Original file line number Diff line number Diff line change
Expand Up @@ -922,11 +922,16 @@ mod tests {
// the active profile; `instructions_file` is gone from config so instructions arrive
// via `base_instructions.text` or are absent entirely. The discover scanner must handle
// both cases without panicking.
//
// Note: As of Codex v0.134.0 (PRs #23883, #24051, #24055, #24059), --profile-v2 was
// renamed to --profile and all legacy profile v1 support was removed. See the v0134_*
// tests below for the corresponding v0.134.0 verification.

#[test]
fn discover_sessions_v0131_profile_v2_session_discovered_correctly() {
// session_meta with profile-v2 `profile` field: discover scanner must not panic
// and must populate cli_version correctly from the payload.
// session_meta with profile-v2 `profile` field (--profile-v2 renamed to --profile
// in v0.134.0): discover scanner must not panic and must populate cli_version
// correctly from the payload.
let tmp = tempdir().unwrap();
let day_dir = tmp.path().join("2026/05/18");
std::fs::create_dir_all(&day_dir).unwrap();
Expand Down Expand Up @@ -980,4 +985,71 @@ mod tests {
assert_eq!(session.turn_count, 1);
assert!(!session.is_ongoing);
}

// Codex v0.134.0 (PRs #23883, #24051, #24055, #24059): --profile-v2 renamed to --profile;
// legacy profile v1 support removed entirely.
//
// codex-trace reads JSONL session files only — it never invokes `codex` or reads Codex
// TOML config. Sessions from v0.134.0+ carry the same `profile` field in session_meta
// as v0.131.0+ sessions. The discover scanner is unaffected; these tests confirm
// v0.134.0 sessions are discovered and indexed correctly.

#[test]
fn discover_sessions_v0134_profile_session_discovered_correctly() {
// session_meta with --profile active (flag renamed from --profile-v2 in v0.134.0):
// discover scanner must not panic and must populate cli_version correctly.
let tmp = tempdir().unwrap();
let day_dir = tmp.path().join("2026/05/26");
std::fs::create_dir_all(&day_dir).unwrap();
let path = day_dir.join("rollout-2026-05-26T10-00-00-profile.jsonl");
std::fs::write(
&path,
[
r#"{"timestamp":"2026-05-26T10:00:00Z","type":"session_meta","payload":{"id":"v0134-disc-profile","timestamp":"2026-05-26T10:00:00Z","cwd":"/home/user","cli_version":"0.134.0","model_provider":"openai","profile":"work","base_instructions":{"text":"You are helpful."}}}"#,
r#"{"timestamp":"2026-05-26T10:00:01Z","type":"event_msg","payload":{"type":"task_started","turn_id":"turn-1"}}"#,
r#"{"timestamp":"2026-05-26T10:00:02Z","type":"event_msg","payload":{"type":"task_complete","turn_id":"turn-1","completed_at":1748254802.0}}"#,
]
.join("\n"),
)
.unwrap();

let sessions = discover_sessions(tmp.path()).unwrap();
let session = sessions
.iter()
.find(|s| s.id == "v0134-disc-profile")
.unwrap();
assert_eq!(session.cli_version.as_deref(), Some("0.134.0"));
assert_eq!(session.turn_count, 1);
assert!(!session.is_ongoing);
}

#[test]
fn discover_sessions_v0134_no_profile_discovered_correctly() {
// v0.134.0 session without --profile: no `profile` field in session_meta.
// The discover scanner must index the session normally — the absent field has no
// effect on discovery (legacy profile v1 removal is transparent to codex-trace).
let tmp = tempdir().unwrap();
let day_dir = tmp.path().join("2026/05/26");
std::fs::create_dir_all(&day_dir).unwrap();
let path = day_dir.join("rollout-2026-05-26T10-01-00-noprofile.jsonl");
std::fs::write(
&path,
[
r#"{"timestamp":"2026-05-26T10:01:00Z","type":"session_meta","payload":{"id":"v0134-disc-noprofile","timestamp":"2026-05-26T10:01:00Z","cwd":"/home/user","cli_version":"0.134.0","model_provider":"openai"}}"#,
r#"{"timestamp":"2026-05-26T10:01:01Z","type":"event_msg","payload":{"type":"task_started","turn_id":"turn-1"}}"#,
r#"{"timestamp":"2026-05-26T10:01:02Z","type":"event_msg","payload":{"type":"task_complete","turn_id":"turn-1","completed_at":1748254862.0}}"#,
]
.join("\n"),
)
.unwrap();

let sessions = discover_sessions(tmp.path()).unwrap();
let session = sessions
.iter()
.find(|s| s.id == "v0134-disc-noprofile")
.unwrap();
assert_eq!(session.cli_version.as_deref(), Some("0.134.0"));
assert_eq!(session.turn_count, 1);
assert!(!session.is_ongoing);
}
}
74 changes: 72 additions & 2 deletions src-tauri/src/parser/entry.rs
Original file line number Diff line number Diff line change
Expand Up @@ -363,6 +363,10 @@ mod tests {
// PR #22647 made Codex reject the legacy [profiles] TOML section when profile-v2 is active.
// PR #22724 removed the experimental `instructions_file` config key entirely.
//
// Note: As of Codex v0.134.0 (PRs #23883, #24051, #24055, #24059), --profile-v2 was
// renamed to --profile and all legacy profile v1 support was removed. See the v0134_*
// tests below for the corresponding v0.134.0 verification.
//
// codex-trace does NOT read Codex CLI config files (TOML profiles). It reads only the
// JSONL session files at ~/.codex/sessions/. The profile-v2 changes affect what Codex
// writes into session_meta entries:
Expand All @@ -377,8 +381,9 @@ mod tests {

#[test]
fn v0131_session_meta_with_profile_v2_field_does_not_panic() {
// session_meta from a v0.131.0 session started with --profile-v2 active.
// The `profile` field names the active profile; codex-trace ignores it gracefully.
// session_meta from a v0.131.0 session started with --profile-v2 active (renamed to
// --profile in v0.134.0). The `profile` field names the active profile; codex-trace
// ignores it gracefully.
let line = r#"{"timestamp":"2026-05-18T10:00:00Z","type":"session_meta","payload":{"id":"v0131-profile-v2","timestamp":"2026-05-18T10:00:00Z","cwd":"/tmp","cli_version":"0.131.0","profile":"work","base_instructions":{"text":"You are a helpful assistant."}}}"#;
let e = RawEntry::parse(line).expect("session_meta with profile-v2 fields must parse");
assert_eq!(e.entry_type, "session_meta");
Expand Down Expand Up @@ -537,4 +542,69 @@ mod tests {
assert_eq!(meta.payload["cli_version"], "0.134.0");
assert_eq!(meta.payload["id"], "v0134-session");
}

// Codex v0.134.0 (PRs #23883, #24051, #24055, #24059): --profile-v2 renamed to --profile;
// legacy profile v1 support removed entirely.
//
// PRs #23883, #24051, #24055, #24059 promoted --profile to the primary profile selector
// and removed all legacy profile v1 resolution and write paths. Passing a legacy profile
// selector now returns an error instead of silently falling back. This is a CLI-level
// change: codex-trace does NOT invoke `codex` at runtime and does NOT read Codex TOML
// config files. Sessions from v0.134.0+ carry the same `profile` field in session_meta
// as v0.131.0+ sessions — the only observable difference for codex-trace is the
// cli_version bump. The parser is unaffected; these tests confirm v0.134.0 sessions
// parse correctly.

#[test]
fn v0134_session_meta_with_profile_field_does_not_panic() {
// session_meta from v0.134.0 with --profile active (flag renamed from --profile-v2).
// The `profile` field names the active profile; codex-trace reads it gracefully.
let line = r#"{"timestamp":"2026-05-26T10:00:00Z","type":"session_meta","payload":{"id":"v0134-profile","timestamp":"2026-05-26T10:00:00Z","cwd":"/tmp","cli_version":"0.134.0","profile":"work","base_instructions":{"text":"You are a helpful assistant."}}}"#;
let e = RawEntry::parse(line).expect("session_meta with profile field must parse");
assert_eq!(e.entry_type, "session_meta");
assert_eq!(e.payload["id"], "v0134-profile");
assert_eq!(e.payload["cli_version"], "0.134.0");
assert_eq!(e.payload["profile"], "work");
assert_eq!(
e.payload["base_instructions"]["text"],
"You are a helpful assistant."
);
}

#[test]
fn v0134_session_meta_without_profile_does_not_panic() {
// v0.134.0 session started without --profile: no `profile` field in session_meta.
// The parser must handle the absent field gracefully (opt_str returns None).
let line = r#"{"timestamp":"2026-05-26T10:01:00Z","type":"session_meta","payload":{"id":"v0134-no-profile","timestamp":"2026-05-26T10:01:00Z","cwd":"/home/user","cli_version":"0.134.0","model_provider":"openai"}}"#;
let e = RawEntry::parse(line).expect("session_meta without profile must parse");
assert_eq!(e.entry_type, "session_meta");
assert_eq!(e.payload["id"], "v0134-no-profile");
assert!(e.payload.get("profile").is_none());
}

#[test]
fn v0134_all_standard_entry_types_parse_correctly_with_profile() {
// Regression guard: all four standard JSONL entry types must parse under v0.134.0.
let lines = [
r#"{"timestamp":"2026-05-26T10:02:00Z","type":"session_meta","payload":{"id":"v0134-session-profile","timestamp":"2026-05-26T10:02:00Z","cwd":"/tmp","cli_version":"0.134.0","profile":"default","model_provider":"openai"}}"#,
r#"{"timestamp":"2026-05-26T10:02:01Z","type":"event_msg","payload":{"type":"task_started","turn_id":"turn-1"}}"#,
r#"{"timestamp":"2026-05-26T10:02:02Z","type":"response_item","payload":{"type":"message","role":"assistant","content":"Hello"}}"#,
r#"{"timestamp":"2026-05-26T10:02:03Z","type":"turn_context","payload":{"model":"gpt-5","cwd":"/tmp"}}"#,
r#"{"timestamp":"2026-05-26T10:02:04Z","type":"event_msg","payload":{"type":"task_complete","turn_id":"turn-1","completed_at":1748254924.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.134.0");
assert_eq!(meta.payload["profile"], "default");
}
}
97 changes: 95 additions & 2 deletions src-tauri/src/parser/session.rs
Original file line number Diff line number Diff line change
Expand Up @@ -682,11 +682,16 @@ mod tests {
// the active profile, and instructions now come via `base_instructions.text` from the
// profile's system_prompt (the `instructions_file` config key is gone from Codex config).
// All cases below must parse without panics and produce correct field values.
//
// Note: As of Codex v0.134.0 (PRs #23883, #24051, #24055, #24059), --profile-v2 was
// renamed to --profile and all legacy profile v1 support was removed. See the v0134_*
// tests below for the corresponding v0.134.0 verification.

#[test]
fn v0131_profile_v2_session_parses_correctly() {
// session_meta from v0.131.0 with --profile-v2 active: carries `profile` field and
// instructions sourced from the profile's system_prompt via base_instructions.text.
// session_meta from v0.131.0 with --profile-v2 active (renamed to --profile in
// v0.134.0): carries `profile` field and instructions sourced from the profile's
// system_prompt via base_instructions.text.
let tmp = tempdir().unwrap();
let path = tmp
.path()
Expand Down Expand Up @@ -818,4 +823,92 @@ mod tests {
.expect("spawn_agent tool call should embed worker session");
assert_eq!(worker.id, "worker-v131");
}

// Codex v0.134.0 (PRs #23883, #24051, #24055, #24059): --profile-v2 renamed to --profile;
// legacy profile v1 support removed entirely.
//
// codex-trace reads JSONL session files only — it never invokes `codex` or reads Codex
// TOML config. Sessions from v0.134.0+ carry the same `profile` field in session_meta
// as v0.131.0+ sessions. The parser is unaffected; these tests confirm v0.134.0
// sessions parse correctly and produce the expected field values.

#[test]
fn v0134_profile_session_parses_correctly() {
// session_meta from v0.134.0 with --profile active (flag renamed from --profile-v2).
let tmp = tempdir().unwrap();
let path = tmp.path().join("rollout-2026-05-26T10-00-00-profile.jsonl");
std::fs::write(
&path,
[
r#"{"timestamp":"2026-05-26T10:00:00Z","type":"session_meta","payload":{"id":"v0134-profile","timestamp":"2026-05-26T10:00:00Z","cwd":"/home/user","cli_version":"0.134.0","model_provider":"openai","profile":"work","base_instructions":{"text":"You are a helpful assistant."}}}"#,
r#"{"timestamp":"2026-05-26T10:00:01Z","type":"event_msg","payload":{"type":"task_started","turn_id":"turn-1"}}"#,
r#"{"timestamp":"2026-05-26T10:00:02Z","type":"event_msg","payload":{"type":"task_complete","turn_id":"turn-1","completed_at":1748254802.0}}"#,
]
.join("\n"),
)
.unwrap();

let session = parse_session(&path).unwrap();
assert_eq!(session.id, "v0134-profile");
assert_eq!(session.cli_version.as_deref(), Some("0.134.0"));
assert_eq!(
session.instructions.as_deref(),
Some("You are a helpful assistant.")
);
assert_eq!(session.turns.len(), 1);
assert!(!session.is_ongoing);
}

#[test]
fn v0134_session_without_profile_parses_correctly() {
// v0.134.0 session started without --profile: no `profile` field in session_meta.
// parse_session must return None for instructions, not panic.
let tmp = tempdir().unwrap();
let path = tmp
.path()
.join("rollout-2026-05-26T10-01-00-noprofile.jsonl");
std::fs::write(
&path,
[
r#"{"timestamp":"2026-05-26T10:01:00Z","type":"session_meta","payload":{"id":"v0134-no-profile","timestamp":"2026-05-26T10:01:00Z","cwd":"/home/user","cli_version":"0.134.0","model_provider":"openai"}}"#,
r#"{"timestamp":"2026-05-26T10:01:01Z","type":"event_msg","payload":{"type":"task_started","turn_id":"turn-1"}}"#,
r#"{"timestamp":"2026-05-26T10:01:02Z","type":"event_msg","payload":{"type":"task_complete","turn_id":"turn-1","completed_at":1748254862.0}}"#,
]
.join("\n"),
)
.unwrap();

let session = parse_session(&path).unwrap();
assert_eq!(session.id, "v0134-no-profile");
assert_eq!(session.cli_version.as_deref(), Some("0.134.0"));
assert!(session.instructions.is_none());
assert_eq!(session.turns.len(), 1);
}

#[test]
fn v0134_legacy_profile_v1_absent_does_not_affect_session_parsing() {
// v0.134.0 removed legacy profile v1 support entirely. Since codex-trace reads only
// JSONL session files (never Codex TOML config), the removal has no effect on
// parsing. Standard v0.134.0 sessions must parse correctly regardless.
let tmp = tempdir().unwrap();
let path = tmp.path().join("rollout-2026-05-26T10-02-00-v0134.jsonl");
std::fs::write(
&path,
[
r#"{"timestamp":"2026-05-26T10:02:00Z","type":"session_meta","payload":{"id":"v0134-standard","timestamp":"2026-05-26T10:02:00Z","cwd":"/workspace","cli_version":"0.134.0","model_provider":"openai","profile":"default"}}"#,
r#"{"timestamp":"2026-05-26T10:02:01Z","type":"event_msg","payload":{"type":"task_started","turn_id":"turn-1"}}"#,
r#"{"timestamp":"2026-05-26T10:02:02Z","type":"response_item","payload":{"type":"message","role":"assistant","content":"Hello"}}"#,
r#"{"timestamp":"2026-05-26T10:02:03Z","type":"turn_context","payload":{"model":"gpt-5","cwd":"/workspace"}}"#,
r#"{"timestamp":"2026-05-26T10:02:04Z","type":"event_msg","payload":{"type":"task_complete","turn_id":"turn-1","completed_at":1748254924.0}}"#,
]
.join("\n"),
)
.unwrap();

let session = parse_session(&path).unwrap();
assert_eq!(session.id, "v0134-standard");
assert_eq!(session.cli_version.as_deref(), Some("0.134.0"));
assert_eq!(session.turns.len(), 1);
assert!(!session.is_ongoing);
}
}
Loading