Files
codex/codex-rs/external-agent-sessions/src/title_tests.rs
T
stefanstokic-oai 1d14221af2 [codex] Sanitize imported session fallback titles (#29875)
## Summary

- sanitize Claude-derived fallback titles without changing explicit
custom or generated source titles
- recognize known leading control wrappers, including `ide_opened_file`,
`ide_selection`, `local-command-stdout`, and `local-command-stderr`
- skip separate control-only user records and use the next meaningful
user message as the fallback title
- use `Imported session` only when every user record is control-only
- preserve raw imported messages, previews, and transcript provenance
unchanged

## Why

Sessions without an explicit source title fall back to user-message
content. Claude metadata can arrive as separate leading user records, so
stripping only the first record still exposed raw control markup or
produced an unhelpful fallback.

## Impact

Imported chats now receive readable fallback titles such as `Fix auth
flow`, while the original wrapper-bearing messages remain visible in the
imported transcript.

## Validation

- `just test -p codex-external-agent-sessions` — 37 tests passed
- `just test -p codex-app-server
external_agent_config_import_creates_session_rollouts` — focused
integration test passed
- `just fix -p codex-external-agent-sessions`
- `just fix -p codex-app-server`
- `just bazel-lock-check`
- `just fmt`
2026-07-08 15:20:40 -04:00

124 lines
3.6 KiB
Rust

use super::*;
#[test]
fn preserves_valid_custom_title_unchanged() {
let custom_title = "<system-reminder>Keep this custom title</system-reminder>";
assert_eq!(
SessionTitleCandidates {
custom_title: Some(custom_title.to_string()),
ai_title: Some("AI title".to_string()),
fallback_title: Some("fallback title".to_string()),
}
.select(),
Some(custom_title.to_string())
);
}
#[test]
fn preserves_valid_ai_title_unchanged_without_custom_title() {
let ai_title = "<command-message>Keep this AI title</command-message>";
assert_eq!(
SessionTitleCandidates {
custom_title: None,
ai_title: Some(ai_title.to_string()),
fallback_title: Some("fallback title".to_string()),
}
.select(),
Some(ai_title.to_string())
);
}
#[test]
fn strips_nested_repeated_and_multiline_leading_control_wrappers() {
let message = "\
<system-reminder>\n\
outer context\n\
<command-message>\n\
nested context\n\
</command-message>\n\
</system-reminder>\n\
<ide_opened_file>\n\
src/auth.rs\n\
</ide_opened_file>\n\
\n\
Fix auth flow\n\
Additional details";
assert_eq!(
fallback_title_from_user_message(message),
Some("Fix auth flow".to_string())
);
}
#[test]
fn strips_observed_external_agent_control_wrapper_families() {
let cases = [
"<task-notification>\n\
<task-id>abc123</task-id>\n\
<status>completed</status>\n\
</task-notification>\n\
Fix auth flow",
"<command-message>review</command-message>\n\
<command-name>/review</command-name>\n\
<command-args>src/auth.rs</command-args>\n\
Fix auth flow",
"<local-command-caveat>Command output follows</local-command-caveat>\n\
<local-command-stdout>tests passed</local-command-stdout>\n\
Fix auth flow",
"<local-command-stderr>tests failed</local-command-stderr>\n\
Fix auth flow",
"<ide_selection>src/auth.rs:1-5</ide_selection>\n\
Fix auth flow",
];
for message in cases {
assert_eq!(
fallback_title_from_user_message(message),
Some("Fix auth flow".to_string())
);
}
}
#[test]
fn returns_no_candidate_for_empty_or_control_only_messages() {
assert_eq!(fallback_title_from_user_message(""), None);
assert_eq!(
fallback_title_from_user_message(
"<command-message>review</command-message>\n\
<system-reminder>context</system-reminder>"
),
None
);
}
#[test]
fn uses_first_meaningful_line_from_ordinary_messages() {
assert_eq!(
fallback_title_from_user_message("\n \n Fix auth flow \nAdditional details"),
Some("Fix auth flow".to_string())
);
}
#[test]
fn preserves_unknown_and_user_authored_angle_bracket_text() {
assert_eq!(
fallback_title_from_user_message("<user-note>Keep this text</user-note> Fix auth flow"),
Some("<user-note>Keep this text</user-note> Fix auth flow".to_string())
);
assert_eq!(
fallback_title_from_user_message("Explain <system-reminder> tags"),
Some("Explain <system-reminder> tags".to_string())
);
}
#[test]
fn bounds_fallback_titles_to_120_characters() {
let message = "x".repeat(121);
let title = fallback_title_from_user_message(&message).expect("title");
assert_eq!(title.chars().count(), SESSION_TITLE_MAX_LEN);
assert_eq!(title, format!("{}...", "x".repeat(117)));
}