feat(protocol): thread/list, thread/fork and thread/archive - #231
Merged
Conversation
Finding F15 in docs/THREE_WAY_REVIEW.md. The protocol could start, read and resume a thread but not enumerate one, so every client that wanted a thread picker read the session directory itself — the desktop through a Tauri command, which is a second reader of the same data with its own idea of what a row is. Adds the three methods behind a `threadManagement` capability. ThreadStore gains optional list/archive; a store without them leaves the capability off and the runtime rejects the calls rather than letting an empty list pass for the truth. The canonical store lists snapshots first and then projects any legacy session that has no snapshot yet, so a listing shows everything a user has, not just what the app-server has touched since 0.2.0. Fork copies a thread into a new one and leaves the original untouched. An in-progress turn is copied as `interrupted`: nothing is executing the fork, and carrying `in_progress` across would leave it permanently unable to start a turn. The desktop sidebar now lists through the protocol, falling back to the Tauri reader only when the sidecar is too old to serve it. Co-Authored-By: Claude Opus 5 <[email protected]>
This was referenced Aug 3, 2026
oratis
added a commit
that referenced
this pull request
Aug 9, 2026
The sidebar archived and deleted session files through Tauri while the app-server served the same threads. That is not only a second reader — the app-server is the single owner and writer of thread storage, so a renderer deleting files behind it can pull the ground out from under an open writer or leave the index pointing at something gone. Three call sites, not the two THREE_WAY_REVIEW recorded. `window.deepcode. sessions.list()` has preferred the protocol since #231, but `Sidebar.tsx` bypassed the shim and called `listSessions()` directly — so the list was a second reader too, with its own row shape and its own sort. `thread/delete` is new. The protocol could list, fork and archive but not delete, which is why delete had nowhere to go. It sits under the existing `threadManagement` capability and mirrors `archive`: 404 on a thread that does not exist rather than silently succeeding, and a store that cannot delete says so instead of quietly archiving instead — being helpful about a destructive verb by doing a different one is the worst available answer. Deleting removes both representations. The protocol snapshot and the canonical session projection share an id and are two views of one thing, and the composite `list` reads both; removing one left the row reappearing on the next refresh as an empty session that could not be opened. `SessionManager.delete` takes the stream, the legacy stream, the meta sidecar, the writer lock and the per-session directory — the listing reads the sidecar, so leaving it behind is not a tidy half-delete. The Tauri commands stay as the fallback for a sidecar too old to serve the methods, matching what #231 established for `list`. Co-Authored-By: Claude Opus 5 <[email protected]>
oratis
added a commit
that referenced
this pull request
Aug 9, 2026
The sidebar archived and deleted session files through Tauri while the app-server served the same threads. That is not only a second reader — the app-server is the single owner and writer of thread storage, so a renderer deleting files behind it can pull the ground out from under an open writer or leave the index pointing at something gone. Three call sites, not the two THREE_WAY_REVIEW recorded. `window.deepcode. sessions.list()` has preferred the protocol since #231, but `Sidebar.tsx` bypassed the shim and called `listSessions()` directly — so the list was a second reader too, with its own row shape and its own sort. `thread/delete` is new. The protocol could list, fork and archive but not delete, which is why delete had nowhere to go. It sits under the existing `threadManagement` capability and mirrors `archive`: 404 on a thread that does not exist rather than silently succeeding, and a store that cannot delete says so instead of quietly archiving instead — being helpful about a destructive verb by doing a different one is the worst available answer. Deleting removes both representations. The protocol snapshot and the canonical session projection share an id and are two views of one thing, and the composite `list` reads both; removing one left the row reappearing on the next refresh as an empty session that could not be opened. `SessionManager.delete` takes the stream, the legacy stream, the meta sidecar, the writer lock and the per-session directory — the listing reads the sidecar, so leaving it behind is not a tidy half-delete. The Tauri commands stay as the fallback for a sidecar too old to serve the methods, matching what #231 established for `list`. Co-Authored-By: Claude Opus 5 <[email protected]>
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.
Finding F15 in
docs/THREE_WAY_REVIEW.md— the last open item from the review.The protocol could start, read and resume a thread but not enumerate one. So every client that wanted a thread picker read the session directory itself: the desktop through a Tauri command, with its own row shape, its own ordering and its own idea of a title. Two readers of the same data.
The three methods
thread/listthread/forkthread/archiveBehind a
threadManagementcapability.ThreadStoregains optionallist/archive: a store without them leaves the capability off and the runtime rejects the calls, rather than letting an empty list pass for the truth.listThreads()returnsnullto the desktop client in that case, so "the server can't tell me" and "there are no threads" stay distinguishable.The canonical store lists snapshots first, then projects any legacy session that has no snapshot yet — a listing shows everything a user has, not just what the app-server has touched since 0.2.0. Legacy rows are projected lazily and not written back.
Fork copies an in-progress turn as
interrupted. Nothing is executing the fork, andstartTurnrefuses a thread that already has an in-progress turn — so carrying the status across would produce a fork that could never be used. There's a test that forks mid-turn and then starts a turn on the result.Wired, not just declared
The desktop sidebar lists through the protocol now, falling back to the Tauri reader only when the sidecar is too old. A capability nothing consumes is the failure mode this whole review batch has been about.
Verification
7 new runtime tests (ordering, title derivation, no title for an empty thread, archive removing a row, archiving a nonexistent thread, fork isolation, in-progress fork, and a store that can't do any of it). The preview fixture serves
thread/listandthread/archive, so the existing Playwright journey — which picks sessions by title out of the sidebar — is now exercising the protocol-backed listing end to end.pnpm typecheck·lint·format:checkclean; protocol 31 · core 750/16 skipped · desktop 101 · server 41 · cli 208 · vscode 12 · lsp 13 · scripts 21;playwright test→ 7 passed.Follow-up: archive and delete in the sidebar still go through Tauri;
thread/archiveis served but not yet the path the button takes. That's the remaining half of removing the second reader.🤖 Generated with Claude Code