- Oh My Pi (
omp) session support (#56, by @Michelh91) — discover, preview, resume (omp --resume <id>), and delete Oh My Pi sessions under~/.omp/agent/sessions/, reusing the pi JSONL parser and excluding nested subagent transcripts. - Yolop session support (#55, by @chaliy) — discover, preview, resume, and delete sessions from Yolop's platform-native session store. Current metadata supplies session titles, canonical repository names, timestamps, and concise worktree labels; older sessions fall back to first prompts and repository-name recovery.
- Contributor guide for adding an agent —
docs/adding-an-agent.mddocuments the full wiring checklist, including the threeVecregistrations the compiler can't enforce.
- Bulk delete could delete the wrong sessions (#58) — multi-select stored
sessionsVec indices captured at toggle time, but a background scan landing mid-selection reorders that Vec, so confirming a bulk delete removed different sessions than the ones checked. Selections are now keyed by(agent, session_id)and resolved at delete time. session_idis shell-escaped in resume commands (#58) — the id was wrapped in raw single quotes, so an id containing'broke the eval'd command and a crafted transcript filename could inject shell. It now goes through the shell-aware quoter (POSIX and PowerShell).- Cursor and pi sorted by creation time, not last activity (#58) — a session created long ago but used today sorted as old and sank below stale ones. Both now use the transcript's last-modified time (pi: the max of its header timestamp and mtime), matching the other agents. Oh My Pi inherits the pi fix.
- Grouped view ordered projects by the first session, not the newest (#58) — correct only under Time sort; grouped view now keys on each group's max session timestamp.
- Codex timestamp overflow on corrupt data (#58) —
updated_at * 1000now saturates instead of wrapping to a garbage sort key. - Streaming startup keeps the initial cursor at the top (#57, by @MilkClouds) — when a fast scanner (commonly OpenCode) returned before a slower one (commonly Claude Code), the later merge pushed the cursor down the newly sorted list. The top row now follows incoming results until the user moves away from it, while explicit selections remain anchored.
- Faster cold scan on large
~/.claude/projectstrees (#58) — the Claude metadata scan read whole transcript bodies (up to 272 KB each) just to reach the tail for the off-by-default recap. The tail window is now 32 KB; measured read I/O dropped 62 MB → 29 MB (−53%) on a 714-file tree, with the recap unchanged.
- Dropped
panic = "abort"(#58) — restoresscan_all's per-thread panic isolation so one malformed session file can't abort the whole listing.
- Grouped view:
Ctrl+Lpreviews the selected session (#50, by @soomtong) — the first grouped→preview path; child rows jump straight to the session detail pane. j/knavigation in the action menu,Ctrl+P/Ctrl+Nin the help screen (#50, by @soomtong) — consistent with the other modes; the footer advertisesTab/jk.
- List scrolling keeps a 3-row margin below the cursor (#50, by @soomtong) — browse, grouped browse, and
agf watchall scroll before the cursor hits the bottom edge. Review fixups hardened the arithmetic (see Fixed) and added the missing end-of-list clamps so the margin can't overscroll into blank rows. - Summary cycling
[/]now wraps around (#50, by @soomtong) — cycling past the last summary returns to the first; the help screen reads[ or ]to avoid/-key confusion. - Deletes only disappear from the UI when the delete actually succeeded — both delete paths previously did
let _ = delete_session(...)and removed the row unconditionally, so a permissions failure looked like success until the session reappeared on next launch. Failed deletes now stay visible. - Rust edition 2024,
rust-version = "1.88"declared (#52) — the floor is set by let-chains; the code already used 1.87 APIs (usize::is_multiple_of), whichclippy::incompatible_msrvsurfaced the moment an MSRV was declared. - rusqlite 0.32 → 0.40, toml 0.8 → 1 (#52) — both verified zero-code-change bumps; the bundled SQLite moves to libsqlite3-sys 0.38. sha2 0.11 (breaks
{:x}digest formatting) and superlighttui 0.21 (adds 29#[must_use]call sites) were evaluated and deliberately deferred.
- Windows without the shell wrapper: bare
agfno longer crashes withprogram not found(#49, by @MilkClouds) — withAGF_SHELLunset the shell defaulted to POSIX on every OS, so native Windows exec'dcd '…' && agentvia a nonexistentsh. The default is now OS-aware via a pure, unit-testeddefault_shell(native Windows → PowerShell;MSYSTEM/unix-styleSHELL→ POSIX). POSIX hosts are bit-for-bit unaffected. - Upgrades no longer serve stale per-session cache data (#51, closes #37) — mtime freshness can't see "the data didn't change but its interpretation did" when scanner logic changes within one
CACHE_VERSION. The cache now stamps the writing binary's version and any mismatch forces a one-time rescan;AGF_DEBUG=1logscache built by X, current Y → rescanning. The write-side carry-over path is gated the same way, and per-releaseCACHE_VERSIONbumps are no longer part of the release checklist. - Gemini scanner: UTF-8 char-boundary panic in
extract_summary_partial(#52) — a multi-byte (e.g. CJK) character straddling the 1 KiB partial-read window made the byte slice panic and silently killed the entire Gemini scan. The window now backs up to a char boundary; CJK regression test added. agf resume --list 0 <query>no longer panics (#52) —--list 0underflowedtop_n.len() - 1; it now shows the top result instead.- Cursor scanner:
hex_decodepanic on non-ASCII store.db metadata (#52) — a corruptmetablob could index mid-char; anis_asciiguard rejects it and the scan continues. - Scroll-margin arithmetic underflow (review fixups on #50) — the margin branch can fire while
selected < visible, whereselected - visible + 1 + marginunderflows usize: debug builds panicked on routine down-navigation (release builds wrapped to the right value by accident). All three sites now use saturating arithmetic; regression tests pin no-underflow, margin, and end-clamp behavior. agf watch: selection clamped when a refresh shrinks the list (#52) — a background refresh that removed sessions could leave the cursor past the end, blanking the viewport.- Ctrl+H no longer leaks into the fuzzy-search textarea (#50, by @soomtong).
- Audit-driven modernization (#52) — 31 verified findings applied: three shared scanner helpers (
collapse_whitespace,project_name_from_path,push_concat_titles) replacing 5–6 duplicated sites each;Agentderives serde and the hand-rolled string maps are deleted (on-disk cache format pinned byte-identical by a round-trip test); let-else/let-chain flattening across scanners, delete, and TUI dispatch; per-frame allocation cuts (render_chunkstakes its chunks by value,update_filter/apply_sortstop cloning,detect_editorcaches config for the process lifetime); claudehistory.jsonlpre-filters orphaned sessions the way codex has since v0.11.4;read_first_linegets the same 512 KiB byte budget as the other bounded readers. Tests grew 53 → 75. - CI hardening (#52) — clippy runs
--all-targets(tests were unlinted in CI) including a new Windows lint job that caught a real platform gap on its first run;Swatinem/rust-cache(Test job 35s → 17s); per-ref concurrency cancellation;--lockedeverywhere cargo resolves. The release workflow gains a tag↔Cargo.tomlversion guard, loud checksum failures, and a graceful publish skip whenCARGO_REGISTRY_TOKENis unset instead of a red run on every release.
scanner/pi: a single invalid-UTF-8 line no longer drops the rest of the file (or the whole session) —parse_sessionusedreader.lines().map_while(Result::ok), which stops at the firstErrand silently truncates every prompt summary that follows. Worse, when the bad line lands before the session header (a real failure mode for crash-truncated or rotation-racing writes), the header is never captured and the entire session is dropped fromagf list. This is the same bug class as theextract_first_prompt.ok()?regression fixed in v0.11.3 for the Cursor scanner. Replaced withlet Ok(line) = line_result else { continue; };so each bad line is skipped individually. Regression test:parse_session_skips_invalid_utf8_lines.scanner/codex:~/.codex/history.jsonlno longer scales with the user's lifetime codex usage —read_history_summariesstreamed the entire file, parsed every line into aHistoryEntry, and accumulated(f64, String)tuples for every session_id ever seen — including thousands of sessions that no longer have a rollout JSONL on disk. For power users who run codex daily, the file reaches tens of MB; v0.11.3'sCACHE_VERSIONbump to 6 forces a cold rescan on every upgrader, which would otherwise pay this cost on first launch. The function now takes the samelive_session_idsset already collected bycollect_live_session_idsand short-circuits any line whosesession_idis not in it. Whenlive_session_idsisNone(transient I/O on the sessions tree), the legacy "keep everything" behavior is preserved, mirroringscan_sqlite's same-condition fallback. Regression tests:read_history_summaries_pre_filters_against_live_session_idsandread_history_summaries_keeps_all_when_live_set_is_none.
- README: Kiro row now surfaces "no per-session resume — always opens the latest session for the cwd" —
kiro-cliignoressession_id, so selecting a specific older Kiro entry in the TUI silently launches a different session. The caveat was previously only inAgent::Kiro::resume_cmd's inline comment; it now appears in the top agents table where users actually read. - README: Hermes row now surfaces "cwd-independent — resumes in your current shell directory" — documented in the expanded
Full session storage pathssection but missing from the discoverable top table.
.gitignore:/.claude/added — every contributor running Claude Code locally was seeing~/.claude/show up as untracked ingit status, with the latent risk of an accidentalgit add .committing a personal agent state directory.
- Cursor scanner: walk both legacy
.txtand current Composer 2+.jsonllayouts (#45, by @rooty0 / Stan) — current Cursor stores transcripts at~/.cursor/projects/*/agent-transcripts/<uuid>/<uuid>.jsonl(depth 4) rather than the legacy~/.cursor/projects/*/agent-transcripts/<uuid>.txt(depth 3). The scanner walked depth 3 with a.txt-only filter, so on current Cursor it returned zero sessions. Verified against live data:~/.cursor/projectshad 2 JSONL transcripts at depth 4 and 0 TXT, andagf list --agent cursor-agentreturnedNo sessions found.before this release. Closes #35. - Cursor scanner: read chat metadata from the right table (#45, by @rooty0) — the previous code queried
SELECT value FROM cursorDiskKV WHERE key = 'composerData', which is the IDE'sstate.vscdbschema, not the CLI'sstore.db. Cursor CLI'sstore.dbactually exposes ameta(key TEXT PRIMARY KEY, value TEXT)table with a singlekey = '0'row whose value is a hex-encoded JSON containingagentId,name,createdAt,mode, andlastUsedModel. Verified viasqlite3against a real store.db on disk. - Cursor scanner: skip JSONL transcripts whose
store.dbis missing (#45, by @rooty0) —cursor-agent --resumeonly surfaces sessions that have BOTH a transcript and a~/.cursor/chats/<workspace>/<session_id>/store.dbentry; reporting orphaned transcripts that the CLI itself refuses to resume just confuses the listing. Legacy.txtsessions are unaffected (they predate thechats/directory). - Cursor scanner: fall back to the first user prompt when
store.dbhas no usable metadata (#45, by @rooty0) — the JSONL is parsed for the firstrole: usertext part, with<user_info>system injections skipped and<user_query>wrappers stripped. extract_first_promptno longer panics on inverted<user_query>tags —str::findreturns the FIRST occurrence of each substring independently, so a text part where</user_query>byte-precedes<user_query>(e.g. a pasted log or AI-generated code sample) gavestart > endandtext[s+12..e]panicked withbegin > end. Confirmed via a standalone rustc reproducer. The closing tag is now searched after the opening one. Regression test:extract_first_prompt_does_not_panic_on_inverted_tags.extract_first_promptno longer aborts on the first malformed or non-UTF-8 line — both the per-line IO read andserde_json::from_strused.ok()?, which propagatesNoneout of the whole function on the first error instead of skipping the bad line. A single corrupted/truncated/non-UTF-8 line at the top of the JSONL silently disabled the blank-summary fallback for the rest of the file. Replaced withlet Ok(...) else { continue; };(matchingscanner/pi.rs). Regression tests:extract_first_prompt_skips_malformed_json_linesandextract_first_prompt_skips_invalid_utf8_lines.extract_first_promptnow bounded by a 512 KiB byte budget — pi.rs added this safeguard in v0.11.2 after large Claude logs stalled the TUI; Cursor transcripts can carry multi-MB tool-result blobs, and theCACHE_VERSIONbump in this release forces a cold rescan for every upgrader, so the same precaution applies.- Cursor delete: legacy
.txttranscripts now actually get removed —delete_cursor_agent_sessioncalledremove_dirs_matching_name(&projects_dir, &session.session_id), but that helper filters onpath.is_dir()ANDfile_name == name. Legacy sessions live atagent-transcripts/<uuid>.txt(a file named<uuid>.txt), so it never matched. Delete returnedOk(()), the orphan file persisted on disk, and the next scan resurrected it. A new sibling helperremove_files_matching_nameremoves the file form alongside the directory form. Regression test:delete_cursor_agent_removes_legacy_txt_transcript. - Cursor scanner: enforce stem == parent UUID invariant on the
.jsonlarm — the previous check only required the grandparent to be namedagent-transcripts. A strayagent-transcripts/<uuidA>/<uuidB>.jsonlwould producesession_id = uuidB, which mismatches both the store.db lookup and whatcursor-agent --resumeexpects. Real Cursor always writes them equal, but the invariant is now explicit. Regression test:scan_from_rejects_jsonl_with_stem_mismatched_to_parent. decode_dash_pathtest coverage for hyphenated project segments — addeddecode_dash_path_resolves_hyphenated_segmentswhich placesagent,agent-tui, andagent-tui-finderas sibling directories and asserts the backtracking decoder resolves to the longest existing match. This is the load-bearing case for this very repo's path.
CACHE_VERSIONbumped to 6 — the new orphan-skip rule fires only on fresh scans; cached0.11.xcursor entries persist with their old summaries until each transcript's mtime changes. Bumping the version forces a one-time rescan on upgrade so the "35 orphans → 0" effect actually lands for upgraders.
- README: Cursor CLI doc link + transcript paths updated (#45, by @rooty0) —
docs.cursor.com/agentno longer resolves; switched tocursor.com/docs/cli/overview. Storage column now lists both the current JSONL layout and the legacy.txtform.
- pi: resume the selected session by id (#43, by @shellus) — pi sessions were resumed with a bare
pi --resume, which opens an interactive picker / the latest session for the cwd and ignored the session the user actually selected in agf. The command is nowpi --session '<id>'. Verified against the pi-mono source (resolveSessionPathinpackages/coding-agent/src/main.ts): an argument with no/,\, or.jsonlis treated as a session-id prefix and matched against the session store; because agf wraps resume ascd '<project_path>' && pi --session '<id>', pi resolves it in the session's own project directory and resumes directly without a fork prompt. (Confirmed on pi 0.53.0.) - pi: keep every session from a project selectable (#43, by @shellus) — the scanner deduped to the most recent session per project directory, a workaround for the old "only resumes latest" assumption. Now that resume-by-id works, all sessions are listed and individually resumable.
- pi: show prompt summaries and full history (#43, by @shellus) — the scanner now extracts user-message text from each session's JSONL (collapsing whitespace, capped at 120 chars per line) so the listing shows what a session was about, and the Preview
History:pane lists every prompt — parity with the other agents instead of bareproject (model)rows.
- cache:
CACHE_VERSIONbumped to 5 (#43, by @shellus) — the pi payload now carries prompt summaries, so 0.11.x cache entries written before this release would otherwise keep rendering only the project name until each session's source mtime happened to change. The bump forces a one-time rescan on upgrade.
- pi scanner: bound the per-file read with a 512 KiB budget — collecting every prompt requires reading the whole JSONL, but pi transcripts can grow to several MB and the
CACHE_VERSIONbump forces a cold rescan for everyone on upgrade. The read loop now stops after 512 KiB (the header is the first line, so it is always captured), mirroring theread_head_tailcap added in v0.10.1 after large Claude logs stalled the TUI. - model: document Kiro's resume-latest behavior — restored the note (dropped in #43) that
kiro-cli chat --resumehas no per-session flag and ignoressession_id, now placed on theAgent::Kiroarm ofresume_cmdwhere the command is defined.
- TUI default-to-cwd search query (from #43) — the merged PR also pre-filled the search box with
$PWDwhenagfwas launched without a query. That changed the no-arg behavior for every agent (empty list in non-project directories) and re-introduced the cwd special-casing deliberately removed in v0.11.0 (the cwd-match sort boost made time-sort look broken). Reverted to keep this release scoped to pi; a cwd default can land later as its own opt-in setting.
superlighttuibumped 0.17 → 0.20.1 — picks up 8 patch/minor releases of the underlying TUI library since v0.10.0 was tagged. Drop-in: every public API agf uses (Context,Color::Rgb,KeyCode,KeyModifiers,RunConfig,TextareaState, container/col/row builders,help_colored,separator_colored,consume_key,mouse_down,quit) is unchanged; agf does not touch any of the v0.20 breaking APIs (gauge/line_gauge/breadcrumbchainable builders,scrollable_with_gutterGutterOpts,ConstraintsWidthSpec/HeightSpecredesign,f32 → f64ratio unification onSplitPane). Notable improvements inherited for free:- 3–5× flush-path speedup on redraw-heavy frames (SLT 0.18.2 #62) —
flush_buffer_diffnow coalesces consecutive same-style cells in a row into a singlePrint(run)instead of per-cell, droppingqueue!calls roughly 12000 → 2000 on a 200×60 redraw. - ~1000× speedup on static frames (SLT 0.20.0 #171) — per-row hash skip in
flush_buffer_diffshort-circuits cell iteration on rows whose contents and style didn't change. The agf list view, which is mostly static while the user reads it, gets the full benefit. rgb_to_ansi256u8 overflow fix atr=g=b=248(SLT 0.19.1 #104) — agf's color palette (Rgb(229, 229, 229),Rgb(245, 158, 11), etc.) sits below the threshold so the user-visible bug never fired in agf, but downstream installs on color-limited terminals now route grayscale tones to the correct ANSI 256 cell instead of silently mapping toIndexed(0)(Black).stdoutBufWriter (SLT 0.19.1 #172) — every frame now batches dozens ofwrite_allANSI commands behind a 64 KiBBufWriter, ending with a singleflush(). Reduces syscalls per frame on every TUI mode without any agf-side change.- Bordered title CJK truncation + overdraw fixes, image command count drop, treemap/textarea panic fixes (SLT 0.19.x), textarea undo/redo, modal
tab_trapopt-in,Anchorenum +modal_at(SLT 0.20.0) — not used by agf today; available for future TUI work.
- 3–5× flush-path speedup on redraw-heavy frames (SLT 0.18.2 #62) —
- DeleteConfirm: arrow Up/Down (and
j/k,Ctrl-j/Ctrl-k) now toggle Yes/No (#38) — previously only Left/Right worked, which was surprising on the v0.11.0 release because every other modal-style picker in agf accepts both axes. The toggle is the same direction-agnostic flip (Yes ↔ No) regardless of which arrow key fires. - ActionSelect / AgentSelect / PermissionSelect / ResumeSelect: arrow keys cycle through the menu instead of stopping at edges (#39) —
Upon the first item now jumps to the last;Downon the last wraps to the first. Tab/BackTab already wrapped — Up/Down/Ctrl-p/Ctrl-n/Ctrl-j/Ctrl-know match. - Footer shows
agf v<version>on the leftmost cell of every mode's status bar (#40) — version is read fromCARGO_PKG_VERSIONat compile time so it stays in sync with the published crate. The 10 per-mode status bars are now routed through a singlerender_footerhelper to keep layout uniform.
- Hermes Agent support (#34, by @SHL0MS) — adds Hermes Agent (Nous Research's self-improving agent) as a first-class scanner. Reads top-level sessions from
~/.hermes/state.db(SQLite); aggregates child-session titles as additional summaries (same pattern as OpenCode subagents); cascade-deletes messages → child sessions → parent → on-disk JSON dumps under~/.hermes/sessions/session_<id>.json. Resume viahermes --resume <session_id>.
- scanner/hermes: pull first user message as preview when title is NULL — Hermes lazily auto-generates titles after the first exchange, so short sessions stay un-titled and the listing fell back to bare
api_server session (model) — N msgs. The scan query now selects the earliestmessages.content WHERE role='user', collapses whitespace, strips<user_query>wrapper tags, and caps to 160 chars so the detail pane shows what the conversation was actually about. - scanner/hermes: empty project_path so resume stays in the user's cwd — Hermes is cwd-independent; the original PR set
project_path = ~/.hermes, which madeagfemitcd ~/.hermes && hermes --resume <id>and yanked the shell out of whatever project the user was working in.shell::cd_andnow skips thecdwhen the quoted path is empty (""/''/\"\"), and the Hermes scanner leavesproject_pathempty.display_path()renders empty paths as—so the TUI doesn't show a blank cell. - delete/hermes: wrap cascade in a single SQLite transaction — the original four DELETEs ran independently; a mid-cascade failure (disk full, DB locked, etc.) could leave orphan messages whose
sessionsrow was already gone, surfacing as ghost rows on the next scan. The four DELETEs are nowBEGIN/COMMIT-wrapped viaConnection::transaction. - TUI: time sort not applied on first frame when
config.sort_byis unset (regression visible since the cache was grouped per-agent) —main.rsonly calledapp.apply_sort()inside anif let Some(sort_by) = config.sort_by, so the default-sort path silently rendered the cache's per-agent grouping order. A session from 11 minutes ago could land below sessions from a month ago on the first paint.apply_sort()is now always called;sort_modefalls through toSortMode::Timewhenconfig.sort_byisNone. - TUI: cwd-match boost removed — the secondary sort that pushed sessions whose
project_path == $PWDabove everything else made the time-sorted listing look broken whenagfwas launched from inside a project: 11 sessions from "this project" surfaced first, then suddenly a 2-minute-old session from another project, then a 31-minute-old Hermes session, and so on. The boost was implicit (no on-screen indicator), so users read it as "time sort is wrong." Pinning is still honored — it's an explicit user action — but cwd is no longer special-cased. - cache: bump
CACHE_VERSIONto 3 — Hermes Agent gained a new entry in the per-agent cache map, and the Hermes session payload now carries first-user-message previews instead of bare source/model fallbacks. Without bumping, 0.10.x cache files would surface as stale "cli session (...)" summaries on first 0.11.0 launch until the underlying DB mtime happened to change. The bump forces a one-time rescan on upgrade.
- Cursor CLI scanner regression tracked separately in #35 — recent cursor-agent installs write transcripts as
<id>/<id>.jsonl(depth 4) instead of the legacy<id>.txt(depth 3) the scanner expects, soagf list --agent cursor-agentreturns 0 sessions on those installs. Out of scope for this release.
- scanner/claude: skip sessions whose per-session JSONL is missing (#27, #29, by @Mert-coderoid) —
~/.claude/history.jsonlaccumulates session IDs forever; Claude Code never trims it when the per-session JSONL under~/.claude/projects/*/<id>.jsonlis deleted. Those orphan IDs surfaced in the listing and resume failed withNo conversation found.scanner::claude::scannow filters its output through the set of session IDs that have a JSONL on disk; the directory walk is shared withscan_session_metadataso the tree is still read once per scan. - delete/codex: also remove the SQLite
threadsrow (#28, #30, by @Mert-coderoid) — since the Codex scanner moved tostate_*.sqliteas primary source, deletes that only removed the rollout JSONL andhistory.jsonlentry came back on the next scan.delete_codex_sessionnow walks everystate_*.sqlitein~/.codex/and runsDELETE FROM threads WHERE id = ?1; older-schema dbs withoutthreadsare skipped silently. - scanner/codex: prune orphan
threadsrows on scan (#31, #32, by @Mert-coderoid) —state_*.sqliterows whose rollout JSONL no longer exists kept dominatingagf list/agf stats(e.g. 324 ghost rows for an empty~/.codex/sessions/) and could not be resumed.scan_sqlitenow builds the live session-id set from~/.codex/sessions/, excludes orphans from the listing, andDELETEs them from everystate_*.sqlite. A walker error with no live IDs collected falls back to the legacy "surface every row" behavior so a transient I/O failure cannot wipe the table. Note: Codex scans are no longer strictly read-only — they will hard-delete unrecoverable orphan rows.
- Cache write race on early TUI exit (regression in 0.10.1) — v0.10.1 introduced streaming background scans; if the user exited the TUI before a worker finished,
cache::write_cachepersisted that agent as(empty session list, fresh mtime), hiding its sessions on the next launch until file mtime changed again.write_cachenow takes the still-scanning agent set and carries the prior cache entry verbatim for those agents — only completed scans replace cache state.
- TUI hangs / never opens on heavy Claude logs —
scanner::claude::scan_session_metadataline-iterated every per-session JSONL to EOF to find the latestaway_summaryrecap. On a directory with multi-MB session files (10+ MB jsonl is common with long Claude Code sessions), rayon would parse hundreds of MB in parallel and the TUI never showed up. Fixed by capping per-file I/O to 16 KB head + 256 KB tail via a newscanner::read_head_tailhelper:cwdandaiTitleare extracted from the head, the latestaway_summaryfrom the tail, and small files (≤ 272 KB) still read in full. Cold scan on a ~1.6 GB Claude log directory dropped from 48 s to 0.7 s in local testing.
- Background scan + streaming TUI ingest —
cache::start_stale_scanreturns anmpsc::Receiver<ScanResult>and the TUI now drains it on every render frame. With a warm cache, the TUI opens instantly on cached sessions and the scanning agents stream results in as they finish (footer shows• scanning N…until every worker reports). With a cold cache, the TUI still opens immediately on the first agent that completes instead of blocking on the slowest one. Final cache write happens at TUI exit. - Per-agent scan timing under
AGF_DEBUG=1so users can locate the slow agent on their machine.
- Windows / PowerShell support (#22, by @MilkClouds) —
agf init powershell(aliaspwsh) emits a wrapper compatible with Windows PowerShell 5.1 and PowerShell 7+;agf setupauto-detects PowerShell on Windows and writes to$PROFILE.CurrentUserAllHosts. A newCommandShell(Posix / PowerShell), selected via theAGF_SHELLenv var the wrapper sets, routesaction::*and the TUI new-session preview through shell-specific quoting (''vs'\'') andcd_and(Set-Location ...; if ($?) { ... }vscd ... && ...). POSIX behavior is unchanged whenAGF_SHELLis unset. - Windows agent detection —
is_agent_installednow matches%PATHEXT%-aware stems on Windows, soclaude.exe/claude.cmd/claude.ps1resolve correctly. Previously every agent read as "not installed" on Windows and the TUI showed "No agent sessions found" even when sessions existed on disk. - UTF-8 round-trip in PowerShell wrapper — wrapper reads
AGF_CMD_FILEwith-Encoding UTF8, fixing CP949/CP1252 mojibake on Windows PowerShell 5.1 (Korean Windows etc.) so non-ASCII project pathsSet-Locationcorrectly. - CI runs on Windows —
windows-latestjob added so PATHEXT stemming, PowerShell command synthesis, and the wrapper UTF-8 contract cannot silently regress. - Release ships Windows x86_64 binary —
release.ymlmatrix gainsx86_64-pc-windows-msvc; tagged releases now includeagf-x86_64-pc-windows-msvc.zipalongside the existing macOS/Linux tarballs.
CommandShell::from_envcached viaOnceLock— the wrapper setsAGF_SHELLonce before exec'ingagf, so the value is immutable for the process lifetime; caching collapses repeated env lookups on the TUI render path and at everyaction::*call.
- UTF-8 panic on non-ASCII paths —
gemini.rshashed-dir slicing,codex.rstitle truncation,list.rs/stats.rs/watch.rscolumn truncation used byte-based slicing and crashed on Korean/emoji/CJK input. All paths now char-safe via sharedscanner::char_prefix+ local char-basedtruncatehelpers. - Scanner panics silently dropped —
scanner::scan_allreplacedunwrap_or_defaultonJoinHandle::joinwithAGF_DEBUG=1-gated stderr logging. Crashes are still non-fatal but now diagnosable. - Cache staleness broken for nested sources —
cache::get_max_mtimenow recurses viawalkdir(max depth 4). Previously, file writes inside~/.codex/sessions/<date>/,~/.gemini/tmp/<dir>/chats/, and~/.cursor/{chats,projects}/*did not bump the top-level dir mtime, leaving the cache permanently stale for Codex/Gemini/Cursor. - Cache/config write corruption — atomic write-then-rename for
sessions.jsonandconfig.tomlprevents truncation on concurrentagfinvocations or^C. - Gemini 64KB UTF-8 cut —
String::from_utf8_lossyover the hard-capped buffer could slice a multi-byte char. Buffer is now trimmed to the last valid UTF-8 boundary before decode. - Selection lost on sort —
apply_sortnow snapshots the selectedsession_idand restores cursor position after reorder. - Resume bypassed PermissionSelect via number key —
ActionSelectnumber keys1-9(and mouse click) now routeResumethrough thePermissionSelect/ResumeSelectflow, matching the Enter path. - DeleteConfirm Up/Down toggled Yes/No — only Left/Right,
h/l, and Ctrl-h/l now toggle the horizontal choice. - Preview dismissed on any key — only Esc and Left dismiss; Up/Down (and Ctrl-p/n/k/j) now cycle to prev/next session without leaving preview; other keys are no-ops.
watchthread leak — refresh thread now gated by anAtomicBool; slow scans no longer accumulate threads every interval.agf setuploose detection — precise sentinel# agf - AI Agent Session Finderreplacescontains("agf init"), avoiding false positives from user comments.agf setup <unknown-shell>exit code — now returns non-zero instead of silentlyOk(()).- Silent config parse failure —
Settings::loadnow prints[agf] config parse error at <path>: <err> — using defaultsinstead of discarding pins silently. - Cache version/parse failures —
AGF_DEBUG=1now logs why cache was discarded (version mismatch or parse error).
- Startup flash eliminated —
main.rsenters the alt-screen (and hides the cursor) via a RAII guard before cache load and scan. Previously, cold-cache first-runs showed the shell prompt for 200ms–3s while scanning; now the terminal switches immediately and the scan runs under the TUI surface. whichfork storm removed —is_agent_installedreplaces 7 per-launch subprocess calls with a single cached$PATHdirectory walk viaOnceLock. ~50ms saved on every startup.- No
Sessionclone per keystroke —FuzzyMatcher::filtersignature is now(&[Session], &[usize], query, ...); the TUI passes the agent-filtered indices directly instead of cloningSessionvalues into a subset vec. name_col_widthcached — computed once inupdate_filterand invalidated on sort/delete, not recomputed per-frame.agents_with_sessionsusesagent_counts— avoids an O(N) walk through every cycle of the agent filter.- Cache + scan honor
installed_agents()— uninstalled agents no longer burn a thread, syscalls, or cache slot on every launch. Filter applied uniformly at cache, scanner, and TUI layers. scan_stale_agentsdispatch — directmatch agent → scanner::*::scan()instead of iteratingplugin::all_plugins()inside each spawned thread.- Stats labels —
Today / This week / This month→Last 24h / Last 7d / Last 30dto match the actual rolling-window semantics. - Stats comment drift —
"most common agent for color"corrected to reflect the first-seen behavior it actually implements. watchprocess detection —pgrep -f→pgrep -xso running agents are matched by exact binary name, not by any cmdline containing the string (editors/greps no longer false-positive).- Redundant
Settings::load—App::newnow accepts aSettingsparameter instead of re-reading the config file. Settings::save_editable+ cache writes — both use atomic tmpfile + rename.
- Shared scanner helpers in
src/scanner/mod.rs:char_prefix,read_first_line,first_line_truncated. Removed duplicated file-reading and truncation logic fromcodex.rs/pi.rs/others. AltScreenGuard(RAII) inmain.rs— ensures the alt-screen is left and cursor restored even on early-exit paths.decrement_agent_count()helper in TUI — keepsagent_countsconsistent after single/bulk delete, which fixes the agent filter showing deleted agents.debug_assert!indelete_session— defense in depth againstsession_idvalues containing/or...
- Per-project git-branch thread + 100ms timeout —
claude::read_git_branchnow justfs::read_to_strings the ~30-byte.git/HEAD. The timeout-per-project was overhead, not safety. any_key_pressedhelper — preview no longer dismisses on arbitrary keys; helper deleted.
- SLT upgrade: v0.6 → v0.15 — major TUI library upgrade bringing 9 minor versions of improvements.
- Rounded borders — filter bar and bulk-delete header now use
Border::Roundedwith colored borders for a modern look. - Native separators — replaced manual
"─".repeat()with SLT'sseparator_colored(). - Native help bars — all footer keybinding hints now use SLT's
help()widget for consistent styling. - Responsive breakpoints — compact layout now uses
ui.breakpoint()instead of manual width checks. - Inline text with
line()— preview details, headers, and info rows useline()for proper inline text rendering.
- Agent filter badges — agent filter indicator now uses
badge_colored()/badge()widgets. - Empty state — shows a friendly "No sessions found" message when search/filter returns no results.
- Section dividers in Help — help screen uses
divider_text()for section headers. - Key hints in Help — keybindings displayed with
key_hint()widget for visual distinction. - Terminal title — window title set to "agf" via
RunConfig::default().title().
- Dead legacy files — deleted unused
tui/input.rsandtui/render.rs(ratatui/crossterm remnants).
- 49
#[must_use]warnings — all unusedResponsereturns from SLT 0.11+ properly handled. - Clippy clean — resolved 17 clippy suggestions (collapsible if-statements, redundant imports).
- TUI engine: ratatui → SuperLightTUI (SLT) — complete rewrite of the rendering layer from ratatui's retained-mode to SLT's immediate-mode architecture. Same look, fewer dependencies, simpler code.
- Shell wrapper updated — the wrapper now uses a temp file instead of stdout capture. Run
agf setupagain after upgrading, or restart your shell. - Keybinding change: summary cycling changed from
Shift+↑/Shift+↓to[/].
- Scanner hang on unreadable
.git/HEAD—read_git_branch()now has a 100ms timeout per path, preventing infinite blocking when a git directory is on an unresponsive filesystem.
ratatuiandcrosstermdependencies removed; replaced withsuperlighttuiv0.6.- TUI source consolidated from 3 files (~2,350 lines) into a single
tui/mod.rs(~1,850 lines). - Shell wrappers (zsh, bash, fish) use
AGF_CMD_FILEtemp file for command passing instead of stdout capture.
- Resume mode picker with
Tabon the action menu. - Parallelize worktree scanning for faster startup.