preserve thread name state semantics

This commit is contained in:
anais
2026-06-21 15:25:30 -07:00
parent f02d4d3cb7
commit 7ddcf58cd9
5 changed files with 61 additions and 30 deletions
+12 -4
View File
@@ -377,7 +377,7 @@ ON CONFLICT(child_thread_id) DO NOTHING
search_term: None,
},
);
builder.push(" AND COALESCE(NULLIF(threads.name, ''), threads.title) = ");
builder.push(" AND COALESCE(threads.name, threads.title) = ");
builder.push_bind(title);
if let Some(cwd) = cwd {
builder.push(" AND threads.cwd = ");
@@ -658,11 +658,19 @@ ON CONFLICT(id) DO NOTHING
thread_id: ThreadId,
name: Option<&str>,
) -> anyhow::Result<bool> {
let result = sqlx::query("UPDATE threads SET name = ? WHERE id = ?")
.bind(name)
let result = match name {
Some(name) => sqlx::query("UPDATE threads SET name = ? WHERE id = ?")
.bind(name)
.bind(thread_id.to_string())
.execute(self.pool.as_ref())
.await?,
None => sqlx::query(
"UPDATE threads SET title = CASE WHEN name IS NOT NULL AND title = name THEN '' ELSE title END, name = '' WHERE id = ?",
)
.bind(thread_id.to_string())
.execute(self.pool.as_ref())
.await?;
.await?,
};
Ok(result.rows_affected() > 0)
}
+24 -10
View File
@@ -176,15 +176,21 @@ pub(super) fn permission_profile_to_metadata_value(
}
}
pub(super) fn thread_metadata_name(metadata: &ThreadMetadata) -> Option<String> {
let explicit_name = metadata
.name
.as_deref()
.map(str::trim)
.filter(|name| !name.is_empty())
.map(str::to_string);
if explicit_name.is_some() {
return explicit_name;
#[derive(Clone)]
pub(super) enum ThreadMetadataName {
Explicit(String),
Legacy(String),
Cleared,
}
pub(super) fn thread_metadata_name(metadata: &ThreadMetadata) -> Option<ThreadMetadataName> {
if let Some(name) = metadata.name.as_deref() {
let name = name.trim();
return Some(if name.is_empty() {
ThreadMetadataName::Cleared
} else {
ThreadMetadataName::Explicit(name.to_string())
});
}
// Preserve display names from rows written before explicit names had their own column.
@@ -194,7 +200,15 @@ pub(super) fn thread_metadata_name(metadata: &ThreadMetadata) -> Option<String>
{
None
} else {
Some(legacy_title.to_string())
Some(ThreadMetadataName::Legacy(legacy_title.to_string()))
}
}
pub(super) fn apply_thread_metadata_name(thread: &mut StoredThread, name: ThreadMetadataName) {
match name {
ThreadMetadataName::Explicit(name) => thread.name = Some(name),
ThreadMetadataName::Legacy(name) => set_thread_name_from_title(thread, name),
ThreadMetadataName::Cleared => thread.name = None,
}
}
@@ -8,7 +8,8 @@ use codex_rollout::find_thread_names_by_ids;
use codex_rollout::parse_cursor;
use super::LocalThreadStore;
use super::helpers::set_thread_name_from_title;
use super::helpers::ThreadMetadataName;
use super::helpers::apply_thread_metadata_name;
use super::helpers::stored_thread_from_rollout_item;
use super::helpers::thread_metadata_name;
use crate::ListThreadsParams;
@@ -80,14 +81,14 @@ pub(super) async fn list_threads(
.iter()
.map(|thread| thread.thread_id)
.collect::<HashSet<_>>();
let mut names = HashMap::<ThreadId, String>::with_capacity(thread_ids.len());
let mut names = HashMap::<ThreadId, ThreadMetadataName>::with_capacity(thread_ids.len());
if let Some(state_db_ctx) = store.state_db().await {
for &thread_id in &thread_ids {
let Ok(Some(metadata)) = state_db_ctx.get_thread(thread_id).await else {
continue;
};
if let Some(title) = thread_metadata_name(&metadata) {
names.insert(thread_id, title);
if let Some(name) = thread_metadata_name(&metadata) {
names.insert(thread_id, name);
}
}
}
@@ -96,12 +97,14 @@ pub(super) async fn list_threads(
find_thread_names_by_ids(store.config.codex_home.as_path(), &thread_ids).await
{
for (thread_id, title) in legacy_names {
names.entry(thread_id).or_insert(title);
names
.entry(thread_id)
.or_insert(ThreadMetadataName::Legacy(title));
}
}
for thread in &mut items {
if let Some(title) = names.get(&thread.thread_id).cloned() {
set_thread_name_from_title(thread, title);
if let Some(name) = names.get(&thread.thread_id).cloned() {
apply_thread_metadata_name(thread, name);
}
}
@@ -13,6 +13,8 @@ use codex_rollout::read_thread_item_from_rollout;
use codex_state::ThreadMetadata;
use super::LocalThreadStore;
use super::helpers::ThreadMetadataName;
use super::helpers::apply_thread_metadata_name;
use super::helpers::git_info_from_parts;
use super::helpers::permission_profile_from_metadata_value;
use super::helpers::rollout_path_is_archived;
@@ -118,7 +120,7 @@ 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);
apply_thread_metadata_name(&mut thread, name);
}
let existing_git_info = thread.git_info.take();
let (fallback_sha, fallback_branch, fallback_origin_url) = match existing_git_info {
@@ -307,7 +309,8 @@ async fn stored_thread_from_sqlite_metadata(
metadata: ThreadMetadata,
) -> StoredThread {
let name = match thread_metadata_name(&metadata) {
Some(title) => Some(title),
Some(ThreadMetadataName::Explicit(name) | ThreadMetadataName::Legacy(name)) => Some(name),
Some(ThreadMetadataName::Cleared) => None,
None => find_thread_name_by_id(store.config.codex_home.as_path(), &metadata.id)
.await
.ok()
@@ -10,7 +10,8 @@ use codex_rollout::parse_cursor;
use codex_rollout::search_rollout_matches;
use super::LocalThreadStore;
use super::helpers::set_thread_name_from_title;
use super::helpers::ThreadMetadataName;
use super::helpers::apply_thread_metadata_name;
use super::helpers::stored_thread_from_rollout_item;
use super::helpers::thread_metadata_name;
use super::list_threads::list_rollout_threads;
@@ -205,14 +206,14 @@ async fn set_thread_search_result_names(
.iter()
.map(|item| item.thread.thread_id)
.collect::<HashSet<_>>();
let mut names = HashMap::<ThreadId, String>::with_capacity(thread_ids.len());
let mut names = HashMap::<ThreadId, ThreadMetadataName>::with_capacity(thread_ids.len());
if let Some(state_db_ctx) = store.state_db().await {
for &thread_id in &thread_ids {
let Ok(Some(metadata)) = state_db_ctx.get_thread(thread_id).await else {
continue;
};
if let Some(title) = thread_metadata_name(&metadata) {
names.insert(thread_id, title);
if let Some(name) = thread_metadata_name(&metadata) {
names.insert(thread_id, name);
}
}
}
@@ -221,12 +222,14 @@ async fn set_thread_search_result_names(
find_thread_names_by_ids(store.config.codex_home.as_path(), &thread_ids).await
{
for (thread_id, title) in legacy_names {
names.entry(thread_id).or_insert(title);
names
.entry(thread_id)
.or_insert(ThreadMetadataName::Legacy(title));
}
}
for item in items {
if let Some(title) = names.get(&item.thread.thread_id).cloned() {
set_thread_name_from_title(&mut item.thread, title);
if let Some(name) = names.get(&item.thread.thread_id).cloned() {
apply_thread_metadata_name(&mut item.thread, name);
}
}
}