Skip to content

Commit f979800

Browse files
LynricsyWineFoxDev
andcommitted
fix(dashboard): 🐛 deduplicate multi-root sessions
Co-authored-by: Wine Fox <fox@ling.plus>
1 parent 829f218 commit f979800

1 file changed

Lines changed: 64 additions & 14 deletions

File tree

packages/dashboard/src-tauri/src/pi_sessions.rs

Lines changed: 64 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -121,15 +121,19 @@ fn append_omp_profile_roots(roots: &mut Vec<PathBuf>, profiles_dir: &Path, xdg:
121121
let Ok(entries) = fs::read_dir(profiles_dir) else {
122122
return;
123123
};
124-
for entry in entries.flatten() {
125-
if entry.file_type().is_ok_and(|kind| kind.is_dir()) {
126-
roots.push(if xdg {
124+
let mut discovered = entries
125+
.flatten()
126+
.filter(|entry| entry.file_type().is_ok_and(|kind| kind.is_dir()))
127+
.map(|entry| {
128+
if xdg {
127129
entry.path().join("sessions")
128130
} else {
129131
entry.path().join("agent/sessions")
130-
});
131-
}
132-
}
132+
}
133+
})
134+
.collect::<Vec<_>>();
135+
discovered.sort();
136+
roots.extend(discovered);
133137
}
134138

135139
fn pi_compatible_session_roots() -> Vec<PathBuf> {
@@ -181,18 +185,42 @@ fn pi_compatible_session_roots() -> Vec<PathBuf> {
181185
roots
182186
}
183187

188+
fn deduplicate_pi_sessions(mut sessions: Vec<PiSessionMeta>) -> Vec<PiSessionMeta> {
189+
sessions.sort_by(|left, right| {
190+
right
191+
.modified
192+
.cmp(&left.modified)
193+
.then_with(|| left.jsonl_path.cmp(&right.jsonl_path))
194+
});
195+
let mut seen_paths = HashSet::new();
196+
let mut seen_ids = HashSet::new();
197+
sessions.retain(|session| {
198+
let canonical_path =
199+
fs::canonicalize(&session.jsonl_path).unwrap_or_else(|_| session.jsonl_path.clone());
200+
if !seen_paths.insert(canonical_path) {
201+
return false;
202+
}
203+
session.session_id.is_empty() || seen_ids.insert(session.session_id.clone())
204+
});
205+
sessions
206+
}
207+
184208
pub fn scan_pi_session_dir() -> Vec<PiSessionMeta> {
185-
pi_compatible_session_roots()
186-
.into_iter()
187-
.flat_map(|root| scan_pi_session_dir_at(&root))
188-
.collect()
209+
deduplicate_pi_sessions(
210+
pi_compatible_session_roots()
211+
.into_iter()
212+
.flat_map(|root| scan_pi_session_dir_at(&root))
213+
.collect(),
214+
)
189215
}
190216

191217
pub fn scan_pi_cache_session_dir() -> Vec<PiSessionMeta> {
192-
pi_compatible_session_roots()
193-
.into_iter()
194-
.flat_map(|root| scan_pi_cache_session_dir_at(&root))
195-
.collect()
218+
deduplicate_pi_sessions(
219+
pi_compatible_session_roots()
220+
.into_iter()
221+
.flat_map(|root| scan_pi_cache_session_dir_at(&root))
222+
.collect(),
223+
)
196224
}
197225

198226
fn scan_pi_cache_session_dir_at(root: &Path) -> Vec<PiSessionMeta> {
@@ -771,6 +799,28 @@ mod tests {
771799
assert!(scan_pi_cache_session_dir_at(dir.path()).is_empty());
772800
}
773801

802+
#[test]
803+
fn multi_root_discovery_deduplicates_logical_sessions() {
804+
clear_caches_for_tests();
805+
let first = tempfile::tempdir().unwrap();
806+
let second = tempfile::tempdir().unwrap();
807+
let content = r#"{"type":"session","id":"same-session","timestamp":"2026-01-01T00:00:00.000Z","cwd":"/tmp/proj"}"#;
808+
fixture_path(&first, content);
809+
fixture_path(&second, content);
810+
811+
let mut first_sessions = scan_pi_session_dir_at(first.path());
812+
first_sessions[0].modified = 100;
813+
let mut second_sessions = scan_pi_session_dir_at(second.path());
814+
second_sessions[0].modified = 200;
815+
let newest_path = second_sessions[0].jsonl_path.clone();
816+
let sessions =
817+
deduplicate_pi_sessions(first_sessions.into_iter().chain(second_sessions).collect());
818+
819+
assert_eq!(sessions.len(), 1);
820+
assert_eq!(sessions[0].session_id, "same-session");
821+
assert_eq!(sessions[0].jsonl_path, newest_path);
822+
}
823+
774824
#[test]
775825
fn compaction_entry_handling() {
776826
clear_caches_for_tests();

0 commit comments

Comments
 (0)