From 4efbd45c6d64e48565274bd060af5d6edec42164 Mon Sep 17 00:00:00 2001 From: Teimur Gasanov Date: Tue, 14 Jul 2026 14:16:43 +0600 Subject: [PATCH] fix(tui): show Oh My Pi source --- crates/lethe-tui/src/app.rs | 64 ++++++++++++++++++++++++++----------- crates/lethe-tui/src/ui.rs | 3 +- 2 files changed, 47 insertions(+), 20 deletions(-) diff --git a/crates/lethe-tui/src/app.rs b/crates/lethe-tui/src/app.rs index b4c1173..c8ad03d 100644 --- a/crates/lethe-tui/src/app.rs +++ b/crates/lethe-tui/src/app.rs @@ -29,6 +29,8 @@ pub struct Stats { pub claude: usize, /// Memories whose source transcript is a Codex rollout. pub codex: usize, + /// Memories whose source transcript is an Oh My Pi session. + pub oh_my_pi: usize, /// `(display_name, count)` sorted descending by count. pub by_project: Vec<(String, usize)>, } @@ -42,16 +44,31 @@ pub fn project_name(root: &Path) -> String { ) } -/// Classify a memory by its anchor's transcript path. Codex rollouts are -/// named `rollout-*.jsonl` and live under `sessions/`; everything else is -/// Claude Code. `None` when the content carries no anchor. -fn source_is_codex(content: &str) -> Option { - let a = parse_anchor(content)?; - let base = Path::new(&a.transcript) +/// Agent that produced an indexed transcript. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +enum TranscriptSource { + ClaudeCode, + Codex, + OhMyPi, +} + +/// Classify a memory by its anchor's transcript path. Codex rollouts have a +/// stable `rollout-` filename prefix; other transcripts under `sessions/` +/// belong to Oh My Pi. `None` means the content carries no anchor. +fn transcript_source(content: &str) -> Option { + let anchor = parse_anchor(content)?; + let base = Path::new(&anchor.transcript) .file_name() - .and_then(|s| s.to_str()) + .and_then(|name| name.to_str()) .unwrap_or(""); - Some(base.starts_with("rollout-") || a.transcript.contains("/sessions/")) + if base.starts_with("rollout-") { + Some(TranscriptSource::Codex) + } else if anchor.transcript.contains("/sessions/") || anchor.transcript.contains("\\sessions\\") + { + Some(TranscriptSource::OhMyPi) + } else { + Some(TranscriptSource::ClaudeCode) + } } fn index_db_path(slug: &str) -> PathBuf { @@ -698,8 +715,8 @@ impl App { } } -/// Compute per-project memory counts + per-source (Claude Code vs Codex) -/// totals off-thread. Opens each project's DuckDB file directly (no +/// Compute per-project memory counts + per-source totals off-thread. Opens +/// each project's DuckDB file directly (no /// embeddings, no encoders) so first paint is fast even with many /// registered projects. fn spawn_stats(projects: Vec) -> mpsc::Receiver { @@ -709,6 +726,7 @@ fn spawn_stats(projects: Vec) -> mpsc::Receiver { let mut total = 0usize; let mut claude = 0usize; let mut codex = 0usize; + let mut oh_my_pi = 0usize; for p in projects { let db_path = index_db_path(&p.slug); if !db_path.exists() { @@ -721,9 +739,10 @@ fn spawn_stats(projects: Vec) -> mpsc::Receiver { continue; }; for row in &rows { - match source_is_codex(&row.content) { - Some(true) => codex += 1, - Some(false) => claude += 1, + match transcript_source(&row.content) { + Some(TranscriptSource::ClaudeCode) => claude += 1, + Some(TranscriptSource::Codex) => codex += 1, + Some(TranscriptSource::OhMyPi) => oh_my_pi += 1, None => {} } } @@ -735,6 +754,7 @@ fn spawn_stats(projects: Vec) -> mpsc::Receiver { total, claude, codex, + oh_my_pi, by_project, }); }); @@ -750,20 +770,26 @@ mod tests { } #[test] - fn classifies_codex_vs_claude() { + fn classifies_transcript_sources() { assert_eq!( - source_is_codex(&anchored( + transcript_source(&anchored( "/Users/x/.codex/sessions/2026/07/rollout-2026-abc.jsonl" )), - Some(true) + Some(TranscriptSource::Codex) + ); + assert_eq!( + transcript_source(&anchored( + "/Users/x/.omp/agent/sessions/-Users-x-repo/2026-07-14_session.jsonl" + )), + Some(TranscriptSource::OhMyPi) ); assert_eq!( - source_is_codex(&anchored( + transcript_source(&anchored( "/Users/x/.claude/projects/-Users-x-repo/6f0e.jsonl" )), - Some(false) + Some(TranscriptSource::ClaudeCode) ); - assert_eq!(source_is_codex("no anchor here"), None); + assert_eq!(transcript_source("no anchor here"), None); } #[test] diff --git a/crates/lethe-tui/src/ui.rs b/crates/lethe-tui/src/ui.rs index a3b660f..ba07432 100644 --- a/crates/lethe-tui/src/ui.rs +++ b/crates/lethe-tui/src/ui.rs @@ -196,7 +196,7 @@ fn draw_body(frame: &mut Frame<'_>, area: Rect, app: &mut App) { .direction(Direction::Vertical) .constraints([ Constraint::Min(5), - Constraint::Length(4), + Constraint::Length(5), Constraint::Length(8), ]) .split(chunks[0]); @@ -351,6 +351,7 @@ fn draw_sources(frame: &mut Frame<'_>, area: Rect, app: &App) { Some(s) => vec![ source_line("Claude Code", s.claude), source_line("Codex", s.codex), + source_line("Oh My Pi", s.oh_my_pi), ], }; let para = Paragraph::new(lines).block(block);