mirror of
https://github.com/openai/codex.git
synced 2026-10-18 17:18:52 +00:00
Preserve working directories when importing external sessions (#36964)
## Why Cursor stores projectless chats under the reserved `empty-window` project, and their transcripts may not contain embedded working-directory metadata. ## What changed - Resolve `empty-window` sessions to the parent of the Cursor home directory, including when that home is configured as a relative path. - Record each created or appended session's working directory in its import success result instead of inheriting the migration item's directory. ## Testing - Cover projectless Cursor transcripts without embedded metadata. - Cover working-directory resolution from a relative Cursor home. GitOrigin-RevId: 21cc7ea6acab1ed80cc4016a881e07eca3d0abbd
This commit is contained in:
committed by
copyberry
parent
40d226e398
commit
e9a692d53b
@@ -46,6 +46,7 @@ use crate::config_manager::ConfigManager;
|
||||
const SESSION_IMPORT_CONCURRENCY: usize = 5;
|
||||
|
||||
struct CompletedSessionImport {
|
||||
cwd: PathBuf,
|
||||
import: CompletedExternalAgentSessionImport,
|
||||
connector_attribution: Option<ImportedSessionConnectorAttribution>,
|
||||
}
|
||||
@@ -53,6 +54,7 @@ struct CompletedSessionImport {
|
||||
enum SessionImportOutcome {
|
||||
Created(CompletedSessionImport),
|
||||
Appended {
|
||||
cwd: PathBuf,
|
||||
source_path: PathBuf,
|
||||
imported_thread_id: ThreadId,
|
||||
title: Option<String>,
|
||||
@@ -129,7 +131,8 @@ impl ExternalAgentSessionImporter {
|
||||
while let Some(result) = import_results.next().await {
|
||||
match result {
|
||||
Ok(Some(SessionImportOutcome::Created(completed_import))) => {
|
||||
item_result.record_success(
|
||||
item_result.record_success_with_cwd(
|
||||
Some(completed_import.cwd.clone()),
|
||||
Some(completed_import.import.source_path.display().to_string()),
|
||||
Some(completed_import.import.imported_thread_id.to_string()),
|
||||
completed_import.import.title.clone(),
|
||||
@@ -137,11 +140,13 @@ impl ExternalAgentSessionImporter {
|
||||
completed_imports.push(completed_import);
|
||||
}
|
||||
Ok(Some(SessionImportOutcome::Appended {
|
||||
cwd,
|
||||
source_path,
|
||||
imported_thread_id,
|
||||
title,
|
||||
})) => {
|
||||
item_result.record_success(
|
||||
item_result.record_success_with_cwd(
|
||||
Some(cwd),
|
||||
Some(source_path.display().to_string()),
|
||||
Some(imported_thread_id.to_string()),
|
||||
title,
|
||||
@@ -286,6 +291,7 @@ impl ExternalAgentSessionImporter {
|
||||
thread_id,
|
||||
expected_source_content_sha256,
|
||||
} => {
|
||||
let cwd = session.cwd.clone();
|
||||
let title = session.title.clone();
|
||||
let appended = append_existing_session(
|
||||
&self.codex_home,
|
||||
@@ -302,6 +308,7 @@ impl ExternalAgentSessionImporter {
|
||||
)
|
||||
.await;
|
||||
Ok(appended.then_some(SessionImportOutcome::Appended {
|
||||
cwd,
|
||||
source_path,
|
||||
imported_thread_id: thread_id,
|
||||
title,
|
||||
@@ -326,6 +333,7 @@ impl ExternalAgentSessionImporter {
|
||||
session_id: session_id.to_string(),
|
||||
server_ids: attributed_mcp_server_ids,
|
||||
});
|
||||
let cwd = session.cwd.clone();
|
||||
let title = session.title.clone();
|
||||
let imported_thread_id =
|
||||
self.persist_session(session)
|
||||
@@ -337,6 +345,7 @@ impl ExternalAgentSessionImporter {
|
||||
sub_error_type: failure.sub_error_type,
|
||||
})?;
|
||||
Ok(CompletedSessionImport {
|
||||
cwd,
|
||||
import: CompletedExternalAgentSessionImport {
|
||||
source_path,
|
||||
source_content_sha256,
|
||||
|
||||
@@ -1628,7 +1628,6 @@ async fn external_agent_config_import_creates_session_rollouts() -> Result<()> {
|
||||
session_success.item_type,
|
||||
ExternalAgentConfigMigrationItemType::Sessions
|
||||
);
|
||||
assert_eq!(session_success.cwd, None);
|
||||
let session_source = std::fs::canonicalize(&session_path)?.display().to_string();
|
||||
assert_eq!(
|
||||
session_success.source.as_deref(),
|
||||
|
||||
@@ -38,7 +38,7 @@ pub(crate) fn detect_recent_cur_sessions_with_limits(
|
||||
if !project_storage.is_dir() {
|
||||
continue;
|
||||
}
|
||||
let fallback_cwd = cur_project_cwd(&project_storage);
|
||||
let fallback_cwd = cur_project_cwd(&project_storage, external_agent_home);
|
||||
for path in cur_transcript_files(&project_storage.join("agent-transcripts")) {
|
||||
candidates.push(SessionFileCandidate {
|
||||
path,
|
||||
@@ -79,8 +79,17 @@ fn cur_transcript_files(transcripts_root: &Path) -> Vec<PathBuf> {
|
||||
files
|
||||
}
|
||||
|
||||
fn cur_project_cwd(project_storage: &Path) -> Option<PathBuf> {
|
||||
fn cur_project_cwd(project_storage: &Path, external_agent_home: &Path) -> Option<PathBuf> {
|
||||
let encoded = project_storage.file_name()?.to_str()?;
|
||||
// Cursor stores projectless chats under this reserved project name.
|
||||
if encoded == "empty-window" {
|
||||
let external_agent_home = if external_agent_home.is_absolute() {
|
||||
external_agent_home.to_path_buf()
|
||||
} else {
|
||||
std::env::current_dir().ok()?.join(external_agent_home)
|
||||
};
|
||||
return external_agent_home.parent().map(Path::to_path_buf);
|
||||
}
|
||||
decode_cur_project_path(encoded)
|
||||
}
|
||||
|
||||
|
||||
@@ -34,6 +34,43 @@ fn detects_cur_transcript_with_project_cwd() {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn detects_projectless_cur_transcript_without_embedded_metadata() {
|
||||
let root = TempDir::new().expect("tempdir");
|
||||
let external_agent_home = root.path().join(".cursor");
|
||||
let transcript = write_transcript(
|
||||
&external_agent_home,
|
||||
"empty-window",
|
||||
"projectless-session",
|
||||
"first request",
|
||||
);
|
||||
|
||||
let sessions =
|
||||
detect_recent_cur_sessions(&external_agent_home, root.path()).expect("detect sessions");
|
||||
|
||||
assert_eq!(
|
||||
sessions,
|
||||
vec![ExternalAgentSessionMigration {
|
||||
path: transcript,
|
||||
cwd: root.path().to_path_buf(),
|
||||
title: Some("first request".to_string()),
|
||||
}]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn resolves_projectless_cur_cwd_from_relative_home() {
|
||||
let current_dir = std::env::current_dir().expect("current dir");
|
||||
|
||||
assert_eq!(
|
||||
cur_project_cwd(
|
||||
Path::new(".cursor/projects/empty-window"),
|
||||
Path::new(".cursor"),
|
||||
),
|
||||
Some(current_dir)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn detects_cur_transcript_with_embedded_unc_cwd() {
|
||||
let root = TempDir::new().expect("tempdir");
|
||||
|
||||
@@ -146,11 +146,21 @@ impl ExternalAgentConfigImportItemResult {
|
||||
source: Option<String>,
|
||||
target: Option<String>,
|
||||
title: Option<String>,
|
||||
) {
|
||||
self.record_success_with_cwd(self.cwd.clone(), source, target, title);
|
||||
}
|
||||
|
||||
pub fn record_success_with_cwd(
|
||||
&mut self,
|
||||
cwd: Option<PathBuf>,
|
||||
source: Option<String>,
|
||||
target: Option<String>,
|
||||
title: Option<String>,
|
||||
) {
|
||||
self.success_count = self.success_count.saturating_add(1);
|
||||
self.successes.push(ExternalAgentConfigImportSuccess {
|
||||
item_type: self.item_type,
|
||||
cwd: self.cwd.clone(),
|
||||
cwd,
|
||||
source,
|
||||
target,
|
||||
title,
|
||||
|
||||
Reference in New Issue
Block a user