mirror of
https://github.com/openai/codex.git
synced 2026-10-21 16:38:24 +00:00
Preserve timestamps when importing external agent sessions (#34989)
## Why Imported sessions previously used the import time for their thread metadata, discarding the chronology recorded by the source session. ## What changed - Set `created_at` and `updated_at` from the earliest turn start and latest turn completion timestamps in the imported rollout. - Use the latest source timestamp for thread recency, while retaining the current time as a fallback when the rollout has no valid turn timestamps. ## Testing - Update the external agent import integration test to verify the persisted creation, update, and recency timestamps. GitOrigin-RevId: 396d5b7e7463a305a5a828ba3a432b4a97e5d2b9
This commit is contained in:
committed by
copyberry
parent
5c94796dc9
commit
0d4910331d
@@ -1,6 +1,7 @@
|
||||
use std::path::PathBuf;
|
||||
use std::sync::Arc;
|
||||
|
||||
use chrono::DateTime;
|
||||
use chrono::Utc;
|
||||
use codex_arg0::Arg0DispatchPaths;
|
||||
use codex_core::ThreadManager;
|
||||
@@ -19,7 +20,9 @@ use codex_external_agent_migration::sessions::record_completed_session_imports;
|
||||
use codex_models_manager::manager::RefreshStrategy;
|
||||
use codex_protocol::ThreadId;
|
||||
use codex_protocol::models::BaseInstructions;
|
||||
use codex_protocol::protocol::EventMsg;
|
||||
use codex_protocol::protocol::MultiAgentVersion;
|
||||
use codex_protocol::protocol::RolloutItem;
|
||||
use codex_protocol::protocol::ThreadHistoryMode;
|
||||
use codex_protocol::protocol::ThreadMemoryMode;
|
||||
use codex_rollout::is_persisted_rollout_item;
|
||||
@@ -330,6 +333,28 @@ impl ExternalAgentSessionImporter {
|
||||
},
|
||||
};
|
||||
rollout_items.retain(|item| is_persisted_rollout_item(item, ThreadHistoryMode::Legacy));
|
||||
let (created_at, updated_at) = rollout_items
|
||||
.iter()
|
||||
.filter_map(|item| match item {
|
||||
RolloutItem::EventMsg(EventMsg::TurnStarted(event)) => event.started_at,
|
||||
RolloutItem::EventMsg(EventMsg::TurnComplete(event)) => event.completed_at,
|
||||
_ => None,
|
||||
})
|
||||
.fold(None, |chronology: Option<(i64, i64)>, timestamp| {
|
||||
Some(match chronology {
|
||||
Some((created_at, updated_at)) => {
|
||||
(created_at.min(timestamp), updated_at.max(timestamp))
|
||||
}
|
||||
None => (timestamp, timestamp),
|
||||
})
|
||||
})
|
||||
.and_then(|(created_at, updated_at)| {
|
||||
Some((
|
||||
DateTime::from_timestamp(created_at, /*nsecs*/ 0)?,
|
||||
DateTime::from_timestamp(updated_at, /*nsecs*/ 0)?,
|
||||
))
|
||||
})
|
||||
.unwrap_or((now, now));
|
||||
let title = title
|
||||
.as_deref()
|
||||
.and_then(codex_core::util::normalize_thread_name);
|
||||
@@ -337,8 +362,9 @@ impl ExternalAgentSessionImporter {
|
||||
title,
|
||||
preview: first_user_message.clone(),
|
||||
model_provider: Some(model_provider),
|
||||
created_at: Some(now),
|
||||
updated_at: Some(now),
|
||||
created_at: Some(created_at),
|
||||
updated_at: Some(updated_at),
|
||||
advance_recency_at: Some(updated_at),
|
||||
source: Some(source.clone()),
|
||||
thread_source: Some(None),
|
||||
agent_nickname: Some(source.get_nickname()),
|
||||
|
||||
@@ -1513,7 +1513,12 @@ async fn external_agent_config_import_creates_session_rollouts() -> Result<()> {
|
||||
let codex_home = TempDir::new()?;
|
||||
MockResponsesConfig::new(&server.uri()).write(codex_home.path())?;
|
||||
let project_root = codex_home.path().join("repo");
|
||||
let recent_timestamp = chrono::Utc::now().to_rfc3339_opts(chrono::SecondsFormat::Secs, true);
|
||||
let source_created_at_text = "2024-01-02T03:04:05Z";
|
||||
let source_updated_at_text = "2024-03-01T04:05:06Z";
|
||||
let source_created_at =
|
||||
chrono::DateTime::parse_from_rfc3339(source_created_at_text)?.timestamp();
|
||||
let source_updated_at =
|
||||
chrono::DateTime::parse_from_rfc3339(source_updated_at_text)?.timestamp();
|
||||
let session_dir = external_agent_home(codex_home.path()).join("projects/repo");
|
||||
let session_path = session_dir.join("session.jsonl");
|
||||
let manifest_dir = connector_metadata_root(codex_home.path())
|
||||
@@ -1540,21 +1545,21 @@ async fn external_agent_config_import_creates_session_rollouts() -> Result<()> {
|
||||
serde_json::json!({
|
||||
"type": "user",
|
||||
"cwd": &project_root,
|
||||
"timestamp": &recent_timestamp,
|
||||
"timestamp": source_created_at_text,
|
||||
"message": { "content": control_request },
|
||||
})
|
||||
.to_string(),
|
||||
serde_json::json!({
|
||||
"type": "user",
|
||||
"cwd": &project_root,
|
||||
"timestamp": &recent_timestamp,
|
||||
"timestamp": "2024-01-03T00:00:00Z",
|
||||
"message": { "content": first_request },
|
||||
})
|
||||
.to_string(),
|
||||
serde_json::json!({
|
||||
"type": "assistant",
|
||||
"cwd": &project_root,
|
||||
"timestamp": &recent_timestamp,
|
||||
"timestamp": source_updated_at_text,
|
||||
"attributionMcpServer": "gmail-server",
|
||||
"message": { "content": "first answer" },
|
||||
})
|
||||
@@ -1659,7 +1664,7 @@ async fn external_agent_config_import_creates_session_rollouts() -> Result<()> {
|
||||
archived: None,
|
||||
is_pinned: None,
|
||||
cwd: None,
|
||||
use_state_db_only: false,
|
||||
use_state_db_only: true,
|
||||
search_term: None,
|
||||
parent_thread_id: None,
|
||||
ancestor_thread_id: None,
|
||||
@@ -1675,6 +1680,9 @@ async fn external_agent_config_import_creates_session_rollouts() -> Result<()> {
|
||||
assert_eq!(imported_thread_id, thread.id.to_string());
|
||||
assert_eq!(thread.preview, control_request);
|
||||
assert_eq!(thread.name.as_deref(), Some("Fix auth flow"));
|
||||
assert_eq!(thread.created_at, source_created_at);
|
||||
assert_eq!(thread.updated_at, source_updated_at);
|
||||
assert_eq!(thread.recency_at, Some(source_updated_at));
|
||||
|
||||
let request_id = mcp
|
||||
.send_thread_read_request(ThreadReadParams {
|
||||
|
||||
Reference in New Issue
Block a user