Make external session detection limits configurable (#34449)

## What changed

- Add `maxSessionAgeDays` and `maxSessions` to the external-agent config detection request.
- Apply the requested limits when discovering sessions from supported migration sources.
- Preserve the existing defaults of a 30-day maximum age and 50 sessions when either option is omitted.

GitOrigin-RevId: 46376e94b64f1f61d8776e34d82cdd2cdeb36ca0
This commit is contained in:
charlesgong-openai authored and copyberry committed 2026-07-21 02:55:11 +00:00
1 parent 9078e32371
commit 3bc49e1721
18 files changed
+195 -25

No files matched your search

@@ -378,9 +378,11 @@ impl ExternalAgentConfigService {
}
if scope.is_home() {
let sessions = self
.source
.recent_sessions(&self.external_agent_home, &self.codex_home)?;
let sessions = self.source.recent_sessions(
&self.external_agent_home,
&self.codex_home,
self.session_import_limits,
)?;
if !sessions.is_empty() {
items.push(ExternalAgentConfigMigrationItem {
item_type: ExternalAgentConfigMigrationItemType::Sessions,
@@ -1,7 +1,6 @@
#[cfg(test)]
use super::common::SESSION_IMPORT_MAX_COUNT;
use super::common::SessionFileCandidate;
use super::common::detect_recent_sessions;
use crate::model::ExternalAgentSessionImportLimits;
use crate::sessions::ExternalAgentSessionMigration;
use std::fs;
use std::io;
@@ -10,6 +9,18 @@ use std::path::Path;
pub fn detect_recent_cla_sessions(
external_agent_home: &Path,
codex_home: &Path,
) -> io::Result<Vec<ExternalAgentSessionMigration>> {
detect_recent_cla_sessions_with_limits(
external_agent_home,
codex_home,
ExternalAgentSessionImportLimits::default(),
)
}
pub(crate) fn detect_recent_cla_sessions_with_limits(
external_agent_home: &Path,
codex_home: &Path,
limits: ExternalAgentSessionImportLimits,
) -> io::Result<Vec<ExternalAgentSessionMigration>> {
let projects_root = external_agent_home.join("projects");
if !projects_root.is_dir() {
@@ -42,7 +53,9 @@ pub fn detect_recent_cla_sessions(
});
}
}
detect_recent_sessions(codex_home, candidates, /*require_existing_cwd*/ true)
detect_recent_sessions(
codex_home, candidates, /*require_existing_cwd*/ true, limits,
)
}
#[cfg(test)]
@@ -233,7 +246,8 @@ mod tests {
let timestamp = chrono::Utc::now().to_rfc3339_opts(chrono::SecondsFormat::Secs, true);
let modified_at = SystemTime::now();
let mut expected = Vec::new();
for index in 0..=SESSION_IMPORT_MAX_COUNT {
let default_limits = ExternalAgentSessionImportLimits::default();
for index in 0..=default_limits.max_sessions {
let file_name = format!("{index:02}-session.jsonl");
let title = format!("session {index}");
let path = write_session(
@@ -275,7 +289,7 @@ mod tests {
}
let changed_at = SystemTime::now()
+ Duration::from_secs(/*secs*/ SESSION_IMPORT_MAX_COUNT as u64 + 1);
+ Duration::from_secs(/*secs*/ default_limits.max_sessions as u64 + 1);
for (index, session) in all_sessions.iter().enumerate() {
let title = session.title.as_deref().expect("session title");
std::fs::write(
@@ -1,3 +1,4 @@
use crate::model::ExternalAgentSessionImportLimits;
use crate::sessions::ExternalAgentSessionMigration;
use crate::sessions::ledger::load_import_ledger;
use crate::sessions::ledger::save_import_ledger;
@@ -9,10 +10,6 @@ use std::fs;
use std::io;
use std::path::Path;
use std::path::PathBuf;
use std::time::Duration;
pub(super) const SESSION_IMPORT_MAX_COUNT: usize = 50;
const SESSION_IMPORT_MAX_AGE: Duration = Duration::from_secs(30 * 24 * 60 * 60);
pub(super) struct SessionFileCandidate {
pub path: PathBuf,
@@ -23,11 +20,12 @@ pub(super) fn detect_recent_sessions(
codex_home: &Path,
candidates: impl IntoIterator<Item = SessionFileCandidate>,
require_existing_cwd: bool,
limits: ExternalAgentSessionImportLimits,
) -> io::Result<Vec<ExternalAgentSessionMigration>> {
let now = now_unix_seconds();
let mut ledger = load_import_ledger(codex_home)?;
let source_states = ledger.source_states();
let mut recent = BinaryHeap::with_capacity(SESSION_IMPORT_MAX_COUNT + 1);
let mut recent = BinaryHeap::new();
for candidate in candidates {
let Ok(metadata) = fs::metadata(&candidate.path) else {
@@ -39,9 +37,7 @@ pub(super) fn detect_recent_sessions(
let Ok(modified_at) = modified_at.duration_since(std::time::UNIX_EPOCH) else {
continue;
};
if (modified_at.as_secs() as i64)
< now.saturating_sub(SESSION_IMPORT_MAX_AGE.as_secs() as i64)
{
if (modified_at.as_secs() as i64) < now.saturating_sub(limits.max_age.as_secs() as i64) {
continue;
}
let Ok(modified_at_nanos) = i64::try_from(modified_at.as_nanos()) else {
@@ -62,7 +58,7 @@ pub(super) fn detect_recent_sessions(
candidate.path,
candidate.fallback_cwd,
));
if recent.len() > SESSION_IMPORT_MAX_COUNT {
if recent.len() > limits.max_sessions {
recent.pop();
}
}
@@ -1,7 +1,6 @@
#[cfg(test)]
use super::common::SESSION_IMPORT_MAX_COUNT;
use super::common::SessionFileCandidate;
use super::common::detect_recent_sessions;
use crate::model::ExternalAgentSessionImportLimits;
use crate::sessions::ExternalAgentSessionMigration;
use std::fs;
use std::io;
@@ -11,6 +10,18 @@ use std::path::PathBuf;
pub fn detect_recent_cur_sessions(
external_agent_home: &Path,
codex_home: &Path,
) -> io::Result<Vec<ExternalAgentSessionMigration>> {
detect_recent_cur_sessions_with_limits(
external_agent_home,
codex_home,
ExternalAgentSessionImportLimits::default(),
)
}
pub(crate) fn detect_recent_cur_sessions_with_limits(
external_agent_home: &Path,
codex_home: &Path,
limits: ExternalAgentSessionImportLimits,
) -> io::Result<Vec<ExternalAgentSessionMigration>> {
let projects_root = external_agent_home.join("projects");
if !projects_root.is_dir() {
@@ -34,7 +45,9 @@ pub fn detect_recent_cur_sessions(
});
}
}
detect_recent_sessions(codex_home, candidates, /*require_existing_cwd*/ false)
detect_recent_sessions(
codex_home, candidates, /*require_existing_cwd*/ false, limits,
)
}
fn cur_transcript_files(transcripts_root: &Path) -> Vec<PathBuf> {
@@ -171,7 +171,8 @@ fn detects_cur_sessions_in_batches_and_redetects_modified_imports() {
let encoded_project = encode_project_path(&project_root);
let modified_at = SystemTime::now();
let mut expected = Vec::new();
for index in 0..=SESSION_IMPORT_MAX_COUNT {
let default_limits = ExternalAgentSessionImportLimits::default();
for index in 0..=default_limits.max_sessions {
let session_id = format!("session-{index:02}");
let title = format!("request {index}");
let path = write_transcript(&external_agent_home, &encoded_project, &session_id, &title);
@@ -4,6 +4,8 @@ mod connectors_cla;
mod cur;
pub use cla::detect_recent_cla_sessions;
pub(crate) use cla::detect_recent_cla_sessions_with_limits;
pub use connectors_cla::ImportedSessionConnectorAttribution;
pub use connectors_cla::detect_imported_cla_session_connectors;
pub use cur::detect_recent_cur_sessions;
pub(crate) use cur::detect_recent_cur_sessions_with_limits;