Repository navigation
feat(conversations): rebuild the conversation picker on provider-native indexes - #899
Merged
Merged
Conversation
Stage decomposition for replacing the resume picker, prompt search, path picker list and in-session prompt lists with one catalog fed by the providers' native indexes (Codex state SQLite, Claude project dirs plus ai-title records and history.jsonl, OpenCode's database). Measured on the author's machine on 2026-09-11: a hard cap of 20 rows, worktree transcripts never visited, orchestration children indistinguishable from real sessions (16 of 57 here, 66 of 98 in bringdown), Codex rows labelled with the injected AGENTS.md message, and 1.5-2.9 s per Codex picker open where the index answers in 50 ms. Records the user's decisions so no later session re-litigates them. Refs #96, #151, #718, #735, #739, #762, #773 Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Ukb9oTvJaojxUxoHfYEUby
Twenty-three tasks over the five approved stages of docs/decomposition/conversations.md, each with its failing test written first against the recorded corpus, the implementation, the exact commands and the commit. Real signatures throughout: the family resolver runs the same `git worktree list` upstream Claude Code uses, the Codex adapter reads the native sqlite index read-only with a column probe and a scan fallback, the ledger is projected from workspace saves through a new store observer, and the live suite is the only place the timing budget is measured. Refs #874 Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Ukb9oTvJaojxUxoHfYEUby
The single publication gate for the recorded conversation corpus: a closed allowlist of structural keys stays verbatim, paths rewrite to /fixture/*, branches other than main hash, and the wrapper prefixes the catalog keys on survive so a fixture can still prove the unwrapping rule. Refs #874 Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Ukb9oTvJaojxUxoHfYEUby
Records this machine's Claude project directories (159 family transcripts across 44 dirs, 105 of them orchestration children), Codex's state_5.sqlite index (1,125 family threads plus a 25-row control) with sampled rollouts, and OpenCode's database (94 family sessions) into testing/fixtures/ conversations, redacted through the policy, with an unredacted git-ignored mirror for the live suite. The family root is the first `git worktree list` entry, not the checkout the script runs from, or a worktree run would file the main repository as "another project". The policy grew three rules the first recording proved necessary: object keys that are paths, relative-path keys inside trackedFileBackups, and file names under this repo's own Claude project directory are content, not structure. Verification re-runs redaction over the committed corpus and requires a no-op, so every rule is idempotent. node:sqlite is typed by a minimal ambient declaration because @types/node is pinned to 20 while Electron 43 bundles Node 24. Refs #874 Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Ukb9oTvJaojxUxoHfYEUby
…r's expectations The installer materialises the committed corpus as a real ~/.claude, ~/.codex and ~/.local/share/opencode under a temp root so adapters run with no test-only branches. The expectations file is the user's answer, reviewed on 2026-09-11, to "which of these 1,377 conversations are mine": 357 user sessions, 355 orchestration children, 278 native subagents, 381 exec runs, 3 projected, 3 empty placeholders. The draft script that produced it deliberately shares no code with the catalog. Refs #874 Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Ukb9oTvJaojxUxoHfYEUby
…lution The family is the unit the picker lists: the main checkout plus every `git worktree list` path plus anything under them, matched case- insensitively on darwin. Roots and candidates are compared in both literal and realpath form (git prints /private/var while providers record /var), and a candidate that no longer exists resolves through its nearest existing ancestor so a pruned worktree's transcripts still belong here. Verified on a real repository with a real `git worktree add`. Refs #874 Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Ukb9oTvJaojxUxoHfYEUby
…lumn probe Refs #874 Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Ukb9oTvJaojxUxoHfYEUby
…ssionIndex The byte-range prompt reader (#735) becomes src/main/conversations/prompts/ promptFolder.ts so the catalog can list, search and show a conversation's prompts without the discovery and search code around it; its twelve tests move with it. One deliberate change: wrapper-prefixed texts are no longer dropped here, because `<stt …>` is a prompt the user dictated and `<orchestration-handoff>` carries the child's task. The legacy index keeps its old behaviour by filtering at its two call sites until it is deleted. Refs #874 Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Ukb9oTvJaojxUxoHfYEUby
Refs #874 Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Ukb9oTvJaojxUxoHfYEUby
…oject dirs Candidate directories come from the family's sanitized roots plus their `-` continuations (worktrees, subdirectories, pruned worktrees); membership is then decided by each transcript's recorded cwd. The head read is record- bounded because modern transcripts open with several kilobytes of hook and environment attachments before the first user record; the tail read finds ai-title, customTitle and the newest user timestamp. Activity and prompt counts come from history.jsonl. Refs #874 Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Ukb9oTvJaojxUxoHfYEUby
…with scan fallback The index is Codex's own resume source: cwd, the real first prompt as title, recency, source, subagent provenance and spawn edges. Rows whose rollout is gone stay listed as unavailable; rollouts the index never saw are unioned from one cached walk through a local head reader; a missing or drifted index downgrades to that reader with a recorded reason. Refs #874 Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Ukb9oTvJaojxUxoHfYEUby
Reads the session table with the family predicate in SQL and the first user text parts per session, skipping synthetic compaction continuations. The app's own placeholder title for a fresh OpenCode Terminal session is reported as no title so an unprompted shell classifies as empty. The corpus policy keeps that app-authored constant and the identifier keys (message_id, parent_id, project_id, workspace_id) verbatim, and the corpus was re-recorded so the fixture exercises both. Refs #773, #874 Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Ukb9oTvJaojxUxoHfYEUby
Refs #874 Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Ukb9oTvJaojxUxoHfYEUby
…r activity Refs #874 Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Ukb9oTvJaojxUxoHfYEUby
…source The catalog's one entry point: sources in, rows out with kind, label provenance and activity, hidden children counted, cursor paging, and a search that reads prompts only when asked. The listing test replays the recorded corpus against the user-authored expectations, and making the two agree exposed four corpus-fidelity gaps rather than catalog bugs: empty strings were hashed into a placeholder the catalog read as a first prompt, a cwd-less bridge-session stub from another project was filed into the family root, Codex rollouts carried no recorded mtime so an unindexed synthesized rollout sorted first by extraction time, and the draft never enumerated unindexed rollouts. The policy keeps emptiness, the extractor files stubs by their original directory and records rollout mtimes, the installer applies every recorded mtime, and the draft restates the last label rung. Refs #874 Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Ukb9oTvJaojxUxoHfYEUby
…n a ledger Once a pane closes the workspace file forgets its title, spoken name and orchestration role, and the only way left to tell a review child from a real session is a prompt sniff. The ledger is a projection of every window's sessions keyed by provider-native id, appended on each workspace commit through a store observer and compacted on open, so the catalog can join durable identity against any transcript on disk. Agent names are read from the registry file without allocating: resolve() spends an address on a miss, and a projection must never do that. Refs #874 Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Ukb9oTvJaojxUxoHfYEUby
…m one cached service The service is the catalog's only consumer and the only owner of caches: discovery per family for a short freshness window so a picker keystroke never walks the Claude project dirs twice, and prompt texts per row keyed by activity so a second search keystroke costs nothing. Claude search reads history.jsonl; Codex and OpenCode search is bounded to the newest two hundred rows because their prompts live in transcripts. The worktree lister is injected rather than imported because the git IPC module pulls electron in on its first line and the system tests run under plain Node. Refs #874 Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Ukb9oTvJaojxUxoHfYEUby
…C surface Three channels replace the per-lister channels that each returned a different row shape. A listing failure rejects instead of returning an empty response, because an empty list means the stores were read and held nothing; collapsing the two erased the only distinction #718 needed. Each listing leaves a breadcrumb correlated by a per-process cwd fingerprint that never retains the cwd. Refs #874 Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Ukb9oTvJaojxUxoHfYEUby
…earch Conversations Both commands now open the same surface: every conversation of the focused repository's worktree family across all three providers, ordered by the user's last activity, with orchestration children behind a toggle, scope and provider filters, search by title, name or prompt, a live preview and Enter to resume. Resume uses the row's own cwd and provider, never the focused pane's, because the picker lists other worktrees and providers on purpose. The preview header shows the catalog's whole-conversation prompt count rather than the tail window's floor. Refs #874 Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Ukb9oTvJaojxUxoHfYEUby
…r through the catalog The path picker's resume rows are now catalog rows rendered by the same component as the Conversations picker, so a session looks the same in both places and naming, provenance and order are decided in main. Scope is the typed directory alone, because a worktree's sessions listed under the main checkout would resume in the wrong tree. The resume-target coherence contract is unchanged: an accepted row disappears the moment the provider toggle changes, and a listing failure is shown as a failure. Refs #874 Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Ukb9oTvJaojxUxoHfYEUby
…d Rewind View Prompts read a fifteen-prompt tail of the in-memory feed and Rewind a thirty-prompt page from main; on any long session the prompt the user was looking for was outside both windows. Both now list every prompt of the transcript, newest first, with "3h ago" as the primary label and the absolute time on hover, through one shared list component. The rewind lister caps only when a caller asks for a bounded page. Refs #874 Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Ukb9oTvJaojxUxoHfYEUby
External operators now read the same rows the in-app picker shows: every provider including OpenCode, every worktree of a cwd's repository, ordered by the user's last activity, with the coverage block reporting what is actually true instead of the old 400-candidate budget. Output schemas are unchanged so existing operators keep parsing. Refs #874 Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Ukb9oTvJaojxUxoHfYEUby
…s not know The first live run against the author's stores took 8.5 s to list and never finished a search. Three causes, each now bounded and recorded in the corpus: the Codex union keyed "unindexed" off the family-filtered query, so nine hundred rollouts of other projects had their heads read on every cold discovery; a rollout head with no user event was streamed to its end; and a search folded whole rollouts (1.2 GB for the newest two hundred) while OpenCode prompts spawned the CLI per session. The union is now keyed by every indexed thread, head reads stop at the record limit, search folds carry a byte budget that a later full read continues from, and OpenCode prompts come from its database. The corpus now holds rollouts of indexed control threads so the union regression is covered. Refs #874 Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Ukb9oTvJaojxUxoHfYEUby
The catalog's folder already returns prompts newest first; reversing them again showed the oldest prompt at the top. Refs #874 Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Ukb9oTvJaojxUxoHfYEUby
Opt-in suite that lists and searches this repository's conversations through the production wiring and asserts the timing budget from the decomposition. It is the only place the budget is measured against real stores, and it found the three discovery and search costs the corpus could not show. Refs #874 Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Ukb9oTvJaojxUxoHfYEUby
…pts through a pool The 64 KB head block rarely holds six user texts, so nearly every Claude transcript fell through to a stream that parsed up to two hundred records of any size, one file after another. The head now reads at most a second 256 KB block for the four texts classification needs, and transcripts are summarised eight at a time. Search folds are trimmed to 150 rows at 128 KB because 50 MB of rollout tails per keystroke measured seconds under load. Live Claude discovery on the author's store: 605 ms to 304 ms. Refs #874 Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Ukb9oTvJaojxUxoHfYEUby
…hree old pickers Every reader of past conversations now goes through the conversation catalog, so the Claude session lister, the prompt index, the sessions IPC and preload surface, the palette's resume mode, the prompt search modal and the per-provider listing members of the registry are gone. The provider feature matrix loses savedSessionListing because listing is no longer something a provider can lack: the catalog reads each native index itself, OpenCode included. The new Conversations feature carries its own control reference so external operators learn where the commands went. Refs #874 Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Ukb9oTvJaojxUxoHfYEUby
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Ukb9oTvJaojxUxoHfYEUby
Resolves the three conflicts from Root Agent Code Management (#907): the control host is now built before IPC registration, so the conversation service is constructed right after the ledger and passed there; the registry keeps main's transcript-path resolver import without the deleted listers; both the TLDR and the Conversations feature references stay. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Ukb9oTvJaojxUxoHfYEUby
…search budget and resumability A natively moved Claude session (main's #883 appends a `relocated` record and renames the transcript into the worktree's project dir) now lists and resumes under the worktree it moved to, and a redirect stub never outranks the transcript: the largest file per session id wins. Codex and OpenCode bind lowercased roots so `lower(cwd) = ?` holds on case-sensitive file systems too. Prompt search spends its row budget only on rows the request can show, so hidden children and excluded providers no longer push a visible row out of it. A row whose transcript file is gone is listed for the record and refused as a resume target in both pickers, a head read that hit its byte bound no longer classifies as empty, the picker asks for a pane instead of claiming an empty repository, project directories are matched with the family's case folding, and the footer counts matches when a query is set. Refs #874 Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Ukb9oTvJaojxUxoHfYEUby
…ters Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Ukb9oTvJaojxUxoHfYEUby
Owner
Author
Review resolutionOne Claude orchestration agent reviewed the branch read-only against a written brief (verdict: FIX FIRST). Everything it rated should-fix is resolved on this branch; the review itself is kept out of the repository. Adopted
Not adopted
🤖 Generated with Claude Code |
Juliusolsson05
added a commit
that referenced
this pull request
Sep 12, 2026
…atalog #899 landed the conversation-picker rebuild while this branch was in review, and it supersedes this branch's feature D. Session listing is no longer a provider-registry concern at all: `src/main/conversations` reads each provider's native index directly and serves the pickers AND the external nativeHistory capabilities from one place — including its own OpenCode source, which closes #773 the same way this branch intended and cites it by number. So the registry's `listSessions`/`listAllSessions`, the shared row mapper, and the registry listing system test are all removed here rather than fought for. Two implementations of "list OpenCode sessions" would be worse than either alone, and the catalog's is the one the app now routes through. What this branch keeps, because it is genuinely its own and main has no version of it: the provider-owned history source (`loadHistoryChunk`), the non-file transcript identity (`transcriptLocator` / `parseTranscriptLocator` / `transcriptLastModifiedAt`), and everything downstream of them. Taking main's `providerConfig.ts` wholesale dropped those on the first pass; they are restored on top of it, and `listSessions` deliberately is not. Also accepts main's deletion of `sessionIndex.ts` and the old resume picker hook, both retired by the same PR. The duplication that remains — the package's `OpencodeStore` and the catalog's own SQLite reader both reading `opencode.db` — is real and is noted as a follow-up on #910 rather than papered over. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Juliusolsson05
added a commit
that referenced
this pull request
Sep 12, 2026
Written hours before #899 landed a conversation catalog that reads the database directly. The line claimed the Resume picker uses this listing; it does not, because that picker no longer exists. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Juliusolsson05
added a commit
that referenced
this pull request
Sep 12, 2026
Textually clean, and the typecheck on the merged tree is clean. That is not taken as proof of anything: the previous merge (#899) resolved cleanly in places and still broke this branch's contract. The overlaps worth naming: - #911 stops a stale session list from severing a mounted transcript view in the remote feed. This branch pins, in `forwarder.test.ts`, that the remote feed's plain-terminal filter does not drop an OpenCode Terminal pane (whose kind is the provider and only its runtime is terminal). Eviction is a second way a pane can leave the phone's view, so the two are checked together. - #912 stops agent clones inheriting root control and resuming provisional ids, and re-applies MCP preferences on reload. This branch added `providerRuntime` to orchestrated agent creation and spawns the OpenCode TUI with built-in MCP launch config, so both paths are in scope for review. Both are handed to an independent pre-merge review rather than asserted here. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VXcU2JMay6xCk8NKJ6cTgj
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
The resume picker capped at 20 rows, never visited worktree project dirs, could not tell orchestration children from real sessions, labelled every Codex row with the injected AGENTS.md text, ordered by file mtime, and the prompt search folded whole transcripts per keystroke. View Prompts and Rewind capped at 15 and 30. Measured evidence and the staged decomposition:
docs/decomposition/conversations.md.What this does
One Conversations picker behind
Resume Session…andSearch Conversations…, fed by one catalog over the providers' native indexes: Codexstate_N.sqlite(read-only throughnode:sqlite), Claude project dirs merged acrossgit worktree listplusai-titlerecords andhistory.jsonl, and OpenCodeopencode.db. Rows carry a label with provenance, the Agent Code spoken name and title from a new durable ledger, worktree, relative time and prompt count. Orchestration children, native subagents andcodex execruns are hidden behind a counted toggle; ordering is the user's last activity. The path picker, View Prompts, Rewind and the externalnativeHistory.*capabilities read the same catalog. The old listers, index, IPC and modals are deleted.Behaviour changes
this folderandeverywhereare one click.nativeHistory.listandnativeHistory.searchnow cover OpenCode and report honest coverage.Verification
testing/fixtures/conversations/, 1,393 conversations, redacted by a checked policy and verified bycheck:conversation-fixtures); expectations drafted from the unredacted mirror and reviewed with the user. Making catalog and expectations agree surfaced four corpus-fidelity gaps, each fixed in the recorder rather than in the rules.npm run test:live:conversations), measured while other agent sessions kept the load average near 20: cold list 762 ms (8.5 s before the Codex union and head bounds; Codex alone 7.0 s to 0.33 s, Claude 605 ms to 304 ms), warm 45 ms, first search 1.4 s, second search under 100 ms. The 500 ms cold and 750 ms first-search budgets from the decomposition are not met on that loaded machine; the suite keeps asserting them so the next measurement on a quiet machine is honest.npm run typecheck(both projects),npm run check:keybindings,npm run test:contract,npm test: everything green except the two known local failures (the image attachment corpus cites a deleted local session; the remote feed integration test is the intermittent test(remote): investigate intermittent live transcript bootstrap failure #847).Known limitations
history.jsonl; OpenCode search reads its database.node:sqliteprints one experimental warning per process in dev logs.Fixes #874
Fixes #96
Fixes #151
Fixes #718
Fixes #773
Refs #735, #739, #762
🤖 Generated with Claude Code
https://claude.ai/code/session_01Ukb9oTvJaojxUxoHfYEUby