mirror of
https://github.com/openai/codex.git
synced 2026-11-10 21:00:47 +00:00
separate thread names and make metadata repair atomic
This commit is contained in:
@@ -176,13 +176,13 @@ pub(super) fn permission_profile_to_metadata_value(
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn distinct_thread_metadata_title(metadata: &ThreadMetadata) -> Option<String> {
|
||||
let title = metadata.title.trim();
|
||||
if title.is_empty() || metadata.first_user_message.as_deref().map(str::trim) == Some(title) {
|
||||
None
|
||||
} else {
|
||||
Some(title.to_string())
|
||||
}
|
||||
pub(super) fn thread_metadata_name(metadata: &ThreadMetadata) -> Option<String> {
|
||||
metadata
|
||||
.name
|
||||
.as_deref()
|
||||
.map(str::trim)
|
||||
.filter(|name| !name.is_empty())
|
||||
.map(str::to_string)
|
||||
}
|
||||
|
||||
pub(super) fn set_thread_name_from_title(thread: &mut StoredThread, title: String) {
|
||||
|
||||
@@ -8,9 +8,9 @@ use codex_rollout::find_thread_names_by_ids;
|
||||
use codex_rollout::parse_cursor;
|
||||
|
||||
use super::LocalThreadStore;
|
||||
use super::helpers::distinct_thread_metadata_title;
|
||||
use super::helpers::set_thread_name_from_title;
|
||||
use super::helpers::stored_thread_from_rollout_item;
|
||||
use super::helpers::thread_metadata_name;
|
||||
use crate::ListThreadsParams;
|
||||
use crate::SortDirection;
|
||||
use crate::ThreadPage;
|
||||
@@ -86,7 +86,7 @@ pub(super) async fn list_threads(
|
||||
let Ok(Some(metadata)) = state_db_ctx.get_thread(thread_id).await else {
|
||||
continue;
|
||||
};
|
||||
if let Some(title) = distinct_thread_metadata_title(&metadata) {
|
||||
if let Some(title) = thread_metadata_name(&metadata) {
|
||||
names.insert(thread_id, title);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,12 +13,12 @@ use codex_rollout::read_thread_item_from_rollout;
|
||||
use codex_state::ThreadMetadata;
|
||||
|
||||
use super::LocalThreadStore;
|
||||
use super::helpers::distinct_thread_metadata_title;
|
||||
use super::helpers::git_info_from_parts;
|
||||
use super::helpers::permission_profile_from_metadata_value;
|
||||
use super::helpers::rollout_path_is_archived;
|
||||
use super::helpers::set_thread_name_from_title;
|
||||
use super::helpers::stored_thread_from_rollout_item;
|
||||
use super::helpers::thread_metadata_name;
|
||||
use super::live_writer;
|
||||
use crate::ReadThreadParams;
|
||||
use crate::StoredThread;
|
||||
@@ -117,6 +117,9 @@ pub(super) async fn read_thread_by_rollout_path(
|
||||
}
|
||||
if let Some(metadata) = read_sqlite_metadata(store, thread.thread_id).await {
|
||||
thread.recency_at = metadata.recency_at;
|
||||
if let Some(name) = thread_metadata_name(&metadata) {
|
||||
set_thread_name_from_title(&mut thread, name);
|
||||
}
|
||||
let existing_git_info = thread.git_info.take();
|
||||
let (fallback_sha, fallback_branch, fallback_origin_url) = match existing_git_info {
|
||||
Some(info) => (
|
||||
@@ -303,7 +306,7 @@ async fn stored_thread_from_sqlite_metadata(
|
||||
store: &LocalThreadStore,
|
||||
metadata: ThreadMetadata,
|
||||
) -> StoredThread {
|
||||
let name = match distinct_thread_metadata_title(&metadata) {
|
||||
let name = match thread_metadata_name(&metadata) {
|
||||
Some(title) => Some(title),
|
||||
None => find_thread_name_by_id(store.config.codex_home.as_path(), &metadata.id)
|
||||
.await
|
||||
@@ -701,7 +704,7 @@ mod tests {
|
||||
builder.cwd = home.path().to_path_buf();
|
||||
builder.cli_version = Some("test_version".to_string());
|
||||
let mut metadata = builder.build(config.default_model_provider_id.as_str());
|
||||
metadata.title = "Saved title".to_string();
|
||||
metadata.name = Some("Saved title".to_string());
|
||||
metadata.first_user_message = Some("Hello from user".to_string());
|
||||
runtime
|
||||
.upsert_thread(&metadata)
|
||||
@@ -852,7 +855,7 @@ mod tests {
|
||||
builder.model_provider = Some(config.default_model_provider_id.clone());
|
||||
builder.cwd = home.path().join("sqlite-workspace");
|
||||
let mut metadata = builder.build(config.default_model_provider_id.as_str());
|
||||
metadata.title = "Saved title".to_string();
|
||||
metadata.name = Some("Saved title".to_string());
|
||||
metadata.first_user_message = Some("Hello from sqlite".to_string());
|
||||
metadata.sandbox_policy = "workspace-write".to_string();
|
||||
runtime
|
||||
@@ -956,7 +959,7 @@ mod tests {
|
||||
builder.cwd = home.path().join("workspace");
|
||||
builder.cli_version = Some("sqlite-cli".to_string());
|
||||
let mut metadata = builder.build(config.default_model_provider_id.as_str());
|
||||
metadata.title = "Command-only thread".to_string();
|
||||
metadata.name = Some("Command-only thread".to_string());
|
||||
runtime
|
||||
.upsert_thread(&metadata)
|
||||
.await
|
||||
|
||||
@@ -10,9 +10,9 @@ use codex_rollout::parse_cursor;
|
||||
use codex_rollout::search_rollout_matches;
|
||||
|
||||
use super::LocalThreadStore;
|
||||
use super::helpers::distinct_thread_metadata_title;
|
||||
use super::helpers::set_thread_name_from_title;
|
||||
use super::helpers::stored_thread_from_rollout_item;
|
||||
use super::helpers::thread_metadata_name;
|
||||
use super::list_threads::list_rollout_threads;
|
||||
use crate::ListThreadsParams;
|
||||
use crate::SearchThreadsParams;
|
||||
@@ -211,7 +211,7 @@ async fn set_thread_search_result_names(
|
||||
let Ok(Some(metadata)) = state_db_ctx.get_thread(thread_id).await else {
|
||||
continue;
|
||||
};
|
||||
if let Some(title) = distinct_thread_metadata_title(&metadata) {
|
||||
if let Some(title) = thread_metadata_name(&metadata) {
|
||||
names.insert(thread_id, title);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -92,7 +92,7 @@ pub(super) async fn update_thread_metadata(
|
||||
.await;
|
||||
|
||||
if let Some(name) = name {
|
||||
apply_thread_name(store, thread_id, name.unwrap_or_default()).await?;
|
||||
apply_thread_name(store, thread_id, name).await?;
|
||||
}
|
||||
|
||||
let resolved_git_info = match git_info {
|
||||
@@ -240,9 +240,6 @@ async fn apply_metadata_update(
|
||||
if let Some(preview) = patch.preview {
|
||||
metadata.preview = Some(preview);
|
||||
}
|
||||
if let Some(name) = patch.name {
|
||||
metadata.title = name.unwrap_or_default();
|
||||
}
|
||||
if let Some(title) = patch.title {
|
||||
metadata.title = title;
|
||||
}
|
||||
@@ -515,11 +512,11 @@ async fn apply_thread_git_info_to_rollout(
|
||||
async fn apply_thread_name(
|
||||
store: &LocalThreadStore,
|
||||
thread_id: ThreadId,
|
||||
name: String,
|
||||
name: Option<String>,
|
||||
) -> ThreadStoreResult<()> {
|
||||
if let Some(state_db) = store.state_db().await {
|
||||
let updated = state_db
|
||||
.update_thread_title(thread_id, &name)
|
||||
.update_thread_name(thread_id, name.as_deref())
|
||||
.await
|
||||
.map_err(|err| ThreadStoreError::Internal {
|
||||
message: format!("failed to set thread name: {err}"),
|
||||
@@ -531,11 +528,15 @@ async fn apply_thread_name(
|
||||
}
|
||||
}
|
||||
|
||||
append_thread_name(store.config.codex_home.as_path(), thread_id, &name)
|
||||
.await
|
||||
.map_err(|err| ThreadStoreError::Internal {
|
||||
message: format!("failed to index thread name: {err}"),
|
||||
})
|
||||
append_thread_name(
|
||||
store.config.codex_home.as_path(),
|
||||
thread_id,
|
||||
name.as_deref().unwrap_or_default(),
|
||||
)
|
||||
.await
|
||||
.map_err(|err| ThreadStoreError::Internal {
|
||||
message: format!("failed to index thread name: {err}"),
|
||||
})
|
||||
}
|
||||
|
||||
async fn apply_thread_memory_mode(
|
||||
@@ -1266,7 +1267,7 @@ mod tests {
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn metadata_patch_applies_title_over_existing_name() {
|
||||
async fn metadata_patch_preserves_explicit_name_over_derived_title() {
|
||||
let home = TempDir::new().expect("temp dir");
|
||||
let config = test_config(home.path());
|
||||
let runtime = codex_state::StateRuntime::init(
|
||||
@@ -1305,7 +1306,7 @@ mod tests {
|
||||
.await
|
||||
.expect("apply observed metadata");
|
||||
|
||||
assert_eq!(thread.name.as_deref(), Some("Derived first message"));
|
||||
assert_eq!(thread.name.as_deref(), Some("User chosen name"));
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
|
||||
Reference in New Issue
Block a user