mirror of
https://github.com/pchuan98/codex.git
synced 2026-07-01 00:31:56 +08:00
[codex] Restore thread recency with compatible migration history (#28671)
## Summary - Revert #28655, restoring the thread `recencyAt` behavior introduced by #27910. - Move `threads_recency_at` to migration 0039 so it no longer collides with `external_agent_config_imports` at version 0038. - Repair databases that already applied the recency migration as version 38 by moving the matching migration-history row to version 39 before SQLx validation. The current version-38 migration can then apply normally. ## Validation - `just test -p codex-state migrations::tests::repairs_recency_migration_that_was_applied_as_version_38` - `just test -p codex-state -p codex-rollout -p codex-thread-store -p codex-app-server-protocol -p codex-tui`: 3,439 passed; six TUI tests could not open the machine's existing read-only incident database at `~/.codex/sqlite/state_5.sqlite`. - `just fix -p codex-state` - `just fmt` - Verified that state migration versions are unique.
This commit is contained in:
@@ -18,6 +18,8 @@ pub enum SortKey {
|
||||
CreatedAt,
|
||||
/// Sort by the thread's last update timestamp.
|
||||
UpdatedAt,
|
||||
/// Sort by the thread's product recency timestamp.
|
||||
RecencyAt,
|
||||
}
|
||||
|
||||
/// Sort direction to use when listing threads.
|
||||
@@ -32,6 +34,8 @@ pub enum SortDirection {
|
||||
pub struct Anchor {
|
||||
/// The timestamp component of the anchor.
|
||||
pub ts: DateTime<Utc>,
|
||||
/// The thread ID component used to disambiguate equal recency timestamps.
|
||||
pub id: Option<ThreadId>,
|
||||
}
|
||||
|
||||
/// A single page of thread metadata results.
|
||||
@@ -67,6 +71,8 @@ pub struct ThreadMetadata {
|
||||
pub created_at: DateTime<Utc>,
|
||||
/// The last update timestamp.
|
||||
pub updated_at: DateTime<Utc>,
|
||||
/// The product recency timestamp.
|
||||
pub recency_at: DateTime<Utc>,
|
||||
/// The session source (stringified enum).
|
||||
pub source: String,
|
||||
/// Optional analytics source classification for this thread.
|
||||
@@ -120,6 +126,8 @@ pub struct ThreadMetadataBuilder {
|
||||
pub created_at: DateTime<Utc>,
|
||||
/// The last update timestamp, if known.
|
||||
pub updated_at: Option<DateTime<Utc>>,
|
||||
/// The product recency timestamp, if known.
|
||||
pub recency_at: Option<DateTime<Utc>>,
|
||||
/// The session source.
|
||||
pub source: SessionSource,
|
||||
/// Optional analytics source classification for this thread.
|
||||
@@ -163,6 +171,7 @@ impl ThreadMetadataBuilder {
|
||||
rollout_path,
|
||||
created_at,
|
||||
updated_at: None,
|
||||
recency_at: None,
|
||||
source,
|
||||
thread_source: None,
|
||||
agent_nickname: None,
|
||||
@@ -190,11 +199,16 @@ impl ThreadMetadataBuilder {
|
||||
.updated_at
|
||||
.map(canonicalize_datetime)
|
||||
.unwrap_or(created_at);
|
||||
let recency_at = self
|
||||
.recency_at
|
||||
.map(canonicalize_datetime)
|
||||
.unwrap_or(updated_at);
|
||||
ThreadMetadata {
|
||||
id: self.id,
|
||||
rollout_path: self.rollout_path.clone(),
|
||||
created_at,
|
||||
updated_at,
|
||||
recency_at,
|
||||
source,
|
||||
thread_source: self.thread_source.clone(),
|
||||
agent_nickname: self.agent_nickname.clone(),
|
||||
@@ -340,6 +354,7 @@ pub(crate) struct ThreadRow {
|
||||
rollout_path: String,
|
||||
created_at: i64,
|
||||
updated_at: i64,
|
||||
recency_at: i64,
|
||||
source: String,
|
||||
thread_source: Option<String>,
|
||||
agent_nickname: Option<String>,
|
||||
@@ -369,6 +384,7 @@ impl ThreadRow {
|
||||
rollout_path: row.try_get("rollout_path")?,
|
||||
created_at: row.try_get("created_at")?,
|
||||
updated_at: row.try_get("updated_at")?,
|
||||
recency_at: row.try_get("recency_at")?,
|
||||
source: row.try_get("source")?,
|
||||
thread_source: row.try_get("thread_source")?,
|
||||
agent_nickname: row.try_get("agent_nickname")?,
|
||||
@@ -402,6 +418,7 @@ impl TryFrom<ThreadRow> for ThreadMetadata {
|
||||
rollout_path,
|
||||
created_at,
|
||||
updated_at,
|
||||
recency_at,
|
||||
source,
|
||||
thread_source,
|
||||
agent_nickname,
|
||||
@@ -432,6 +449,7 @@ impl TryFrom<ThreadRow> for ThreadMetadata {
|
||||
rollout_path: PathBuf::from(rollout_path),
|
||||
created_at: epoch_millis_to_datetime(created_at)?,
|
||||
updated_at: epoch_millis_to_datetime(updated_at)?,
|
||||
recency_at: epoch_millis_to_datetime(recency_at)?,
|
||||
source,
|
||||
thread_source,
|
||||
agent_nickname,
|
||||
@@ -461,8 +479,12 @@ pub(crate) fn anchor_from_item(item: &ThreadMetadata, sort_key: SortKey) -> Opti
|
||||
let ts = match sort_key {
|
||||
SortKey::CreatedAt => item.created_at,
|
||||
SortKey::UpdatedAt => item.updated_at,
|
||||
SortKey::RecencyAt => item.recency_at,
|
||||
};
|
||||
Some(Anchor { ts })
|
||||
Some(Anchor {
|
||||
ts,
|
||||
id: (sort_key == SortKey::RecencyAt).then_some(item.id),
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) fn datetime_to_epoch_millis(dt: DateTime<Utc>) -> i64 {
|
||||
@@ -519,6 +541,7 @@ mod tests {
|
||||
rollout_path: "/tmp/rollout-123.jsonl".to_string(),
|
||||
created_at: 1_700_000_000,
|
||||
updated_at: 1_700_000_100,
|
||||
recency_at: 1_700_000_100,
|
||||
source: "cli".to_string(),
|
||||
thread_source: None,
|
||||
agent_nickname: None,
|
||||
@@ -549,6 +572,7 @@ mod tests {
|
||||
rollout_path: PathBuf::from("/tmp/rollout-123.jsonl"),
|
||||
created_at: DateTime::<Utc>::from_timestamp(1_700_000_000, 0).expect("timestamp"),
|
||||
updated_at: DateTime::<Utc>::from_timestamp(1_700_000_100, 0).expect("timestamp"),
|
||||
recency_at: DateTime::<Utc>::from_timestamp(1_700_000_100, 0).expect("timestamp"),
|
||||
source: "cli".to_string(),
|
||||
thread_source: None,
|
||||
agent_nickname: None,
|
||||
|
||||
Reference in New Issue
Block a user