Parse Claude and Cursor session records separately (#35623)

## Why

Cursor session messages can place `<cursor_commands>` and `<timestamp>` context
before the actual `<user_query>`. Treating both external session formats with one
parser left that context in the imported message and generated title.

## What changed

- Route detected sessions through format-specific Claude and Cursor parsers.
- Strip recognized Cursor context wrappers when extracting a trailing
  `<user_query>`, while preserving messages with unknown leading context.
- Keep shared content-block, tool-call, and timestamp parsing in a common module.

## Testing

Add parser coverage for wrapped Cursor queries, unknown context, working-directory
precedence, Claude metadata, content hashing, and shared tool annotations.

GitOrigin-RevId: b49328d46ab4937e3c599588a3d3081e8ab798cd
This commit is contained in:
charlesgong-openai authored and copyberry committed 2026-07-27 14:31:50 +00:00
1 parent fea2d8b18f
commit bd2de422aa
13 files changed
+878 -566

No files matched your search

@@ -2,6 +2,7 @@ use super::common::SessionFileCandidate;
use super::common::detect_recent_sessions;
use crate::model::ExternalAgentSessionImportLimits;
use crate::sessions::ExternalAgentSessionMigration;
use crate::sessions::SessionRecordFormat;
use std::fs;
use std::io;
use std::path::Path;
@@ -50,6 +51,7 @@ pub(crate) fn detect_recent_cla_sessions_with_limits(
candidates.push(SessionFileCandidate {
path,
fallback_cwd: None,
record_format: SessionRecordFormat::Cla,
});
}
}
@@ -1,9 +1,11 @@
use crate::model::ExternalAgentSessionImportLimits;
use crate::sessions::ExternalAgentSessionMigration;
use crate::sessions::SessionRecordFormat;
use crate::sessions::ledger::load_import_ledger;
use crate::sessions::ledger::save_import_ledger;
use crate::sessions::now_unix_seconds;
use crate::sessions::records::summarize_session_with_cwd;
use crate::sessions::records_cla;
use crate::sessions::records_cur;
use std::cmp::Reverse;
use std::collections::BinaryHeap;
use std::fs;
@@ -14,6 +16,7 @@ use std::path::PathBuf;
pub(super) struct SessionFileCandidate {
pub path: PathBuf,
pub fallback_cwd: Option<PathBuf>,
pub record_format: SessionRecordFormat,
}
pub(super) fn detect_recent_sessions(
@@ -57,6 +60,7 @@ pub(super) fn detect_recent_sessions(
Reverse(modified_at_nanos),
candidate.path,
candidate.fallback_cwd,
candidate.record_format,
));
if recent.len() > limits.max_sessions {
recent.pop();
@@ -66,7 +70,7 @@ pub(super) fn detect_recent_sessions(
drop(source_states);
let mut migrations = Vec::new();
let mut ledger_changed = false;
for (modified_at, path, fallback_cwd) in recent.into_sorted_vec() {
for (modified_at, path, fallback_cwd, record_format) in recent.into_sorted_vec() {
match ledger.refresh_current_source(&path, modified_at.0) {
Ok(false) => {}
Ok(true) => {
@@ -75,7 +79,13 @@ pub(super) fn detect_recent_sessions(
}
Err(_) => continue,
}
let Ok(Some(summary)) = summarize_session_with_cwd(&path, fallback_cwd.as_deref()) else {
let summary = match record_format {
SessionRecordFormat::Cla => records_cla::summarize_session(&path),
SessionRecordFormat::Cur => {
records_cur::summarize_session(&path, fallback_cwd.as_deref())
}
};
let Ok(Some(summary)) = summary else {
continue;
};
if require_existing_cwd && !summary.migration.cwd.is_dir() {
@@ -2,6 +2,7 @@ use super::common::SessionFileCandidate;
use super::common::detect_recent_sessions;
use crate::model::ExternalAgentSessionImportLimits;
use crate::sessions::ExternalAgentSessionMigration;
use crate::sessions::SessionRecordFormat;
use std::fs;
use std::io;
use std::path::Path;
@@ -42,6 +43,7 @@ pub(crate) fn detect_recent_cur_sessions_with_limits(
candidates.push(SessionFileCandidate {
path,
fallback_cwd: fallback_cwd.clone(),
record_format: SessionRecordFormat::Cur,
});
}
}