Repository navigation
feat(chat): search a pane's conversation over messages and answers (#2018) - #2891
Conversation
…2018) Search conversation, from the pane's menu and the command palette, finds a word in the user's messages and the agent's answers, earlier pages included, and lists the matches newest first as snippets; a pick opens the chat at that message, reading the earlier pages up to it. The bridge searches the same parsed pages the chat reads, a few pages per answer with a cursor to go on, so only snippets cross the network. A new additive route GET /api/pane/conversation/search and ServerFeature "conversation-search"; a PC whose bridge does not list it offers no search. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Caution Review failedThe pull request is closed.
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. |
Deploying ovis with
|
| Latest commit: |
9b3db91
|
| Status: | ✅ Deploy successful! |
| Preview URL: | https://76727f25.ovis.pages.dev |
| Branch Preview URL: | https://feat-conversation-search.ovis.pages.dev |
Merge of origin/main and review of the conflict resolutionorigin/main (1dd4f3f) is merged into this branch as one merge commit. Four conflicts, each resolved by keeping both sides:
Codex (gpt-6.1-terra, xhigh, adversarial) reviewed the resolution. It found no HIGH issues and nothing in the
Validation at the merge head d1c1e84: |
# Conflicts: # src/components/ChatView.tsx # src/components/PaneCanvas.tsx
A search whose conversation became unreadable after a page of it was searched (in the same answer, or on a `before` request) answered no matches and no cursor, so the dialog said it had searched the whole conversation and kept only the matches before it. Such a read is now HistoryChanged (409 history_changed), as for a replaced transcript; a pane with no conversation at all still answers no matches. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Merge of origin/main, a review of the feature at the new head, and one fix roundorigin/main (58432f8) is merged into this branch as one merge commit, 74bdd45. The only textual conflicts were imports, resolved by keeping both sides: CodeRabbit did not review this PR (review limit). Codex (gpt-6.1-terra, xhigh, adversarial) reviewed the whole feature at the merge head, covering the merge's auto-merged hunks and the search itself. It found no HIGH issues, three MEDIUM and one LOW. Here is how each was resolved:
The two MEDIUM findings from the earlier merge review ( Earlier unresolved review threads: none. Validation at 8490724: |
# Conflicts: # src/components/PaneCanvas.tsx
Another merge of origin/main (9b3db91)origin/main (2da018f) is merged into this branch as one merge commit. The one conflict was the pane menu's item list ( Validation at 9b3db91: |
|
Local merge gate (owner-approved): origin/main merged, |
…arched (#2988) Fixes #2958 ## What changes A search pick looked for the row named exactly as its turn was when searched (`conversationTurnAnchor`). An answer that went on, or whose first tool's output arrived after the search, has another name by then, so the pick read every earlier page and said "This message is no longer in the conversation". Now the pick reads earlier pages until a turn with that name is in, or back to the beginning, as before, and then settles on the rows shown at that moment (a refresh may have changed them meanwhile): it opens the first turn with the match's name, as main does, unless a turn of its time before it changed into the match; otherwise the **one** turn that changed since (turns without a time go by name only) (`pickedTurn` and `searchedTurnChanged` in `shared/conversation-search.ts`). A changed turn has the match's role and time, goes on from how the searched turn began (its first text grew, or its first tool is the same call up to its output), and still holds the match after the same words (a Markdown mark that streamed in and closed, such as a link, is ignored). When two turns qualify (Codex dates answers the user steered between by one time; an answer that grew while a later one took its old name), none opens and the line says the message is missing: never the wrong answer, which is why #2891's prefix fallback was reverted. Design reference: the Codex and Claude apps jump to a search hit by the message's own id; our transcripts have none for every agent, so this keeps the content name and adds a strict, unambiguous fallback instead of a per-agent id. The pick reads no more pages than before. ## Review round Codex (Terra, xhigh) on the first head: a refresh during the page reads could make the counted candidate differ from the row opened (HIGH), an exact name could name a later answer after the searched one grew (HIGH, pre-existing), and a closed Markdown link changed the words before the match (MEDIUM). All three are fixed here: the decision moved to the rows shown at the end, an exact name no longer wins over a second qualifying turn, and the comparison ignores link and emphasis marks. omo: CLEAR on the first head. Second round (omo + Codex on dfc5be6): two answers of one time that both begin with the match counted each other, so neither opened (fixed: a changed turn must go on from the searched turn's opening; test added); a pick whose named row was renamed before it was drawn could stay loading (fixed: the pick settles on the first draw after its pages are in, since `loadOlder` lays a page before it answers). Codex also reproduced a pre-existing case (main does the same): when the grown answer sits on an older page not loaded yet and a later answer of its time took its old name, the named one opens. Tracked in #2993. Third round (omo on 0a24b61): turns sharing a name (an untimed prompt sent twice) both counted and neither opened, where main opened the first (fixed: the first named turn opens unless a turn changed since also qualifies; test added). Also fixed: a changed turn's words after the match must go on as they did (a stale view with two same-opening tool answers no longer opens the other), and a pick with no list to show it in fails instead of waiting. Left as LOW: an answer that itself begins with "…" is read as a cut snippet and, once grown, is reported missing. Fourth round (omo on 70523f8): an unchanged answer followed by a longer answer of its time that begins with it was reported missing (fixed: only a changed turn before the named one blocks; test added); an image link's `!` is ignored like a link's. Fifth round (omo on b747b5f): answers after the named turn (a task steered twice) and untimed turns that begin with the match blocked an unchanged message main opens (fixed: the named turn decides; only a changed turn of its time before it blocks; a missing time tells no turn apart); a link mark after the match is ignored too. Codex's last MEDIUM (a Codex image prompt searched between its inline and event records reads as missing, as on main) is noted on #2993. ## Validation - `bun test shared/conversation-search.test.ts server/conversation-search.test.ts`: 22 pass (with the steered-twice and untimed cases). New tests use the Codex transcript parser (tool output arriving after the search; two steered answers of one time), a grown answer, a closed link, and `pickedTurn`'s none/one/several cases. - `bun scripts/conversation-search-demo-regression.ts` (1280 and 390): pass. New case: the demo serves every match under its old (changed) name, and the pick still opens the answer. Without the fix this case times out with the "no longer in the conversation" line. - `bun run check fast` on the final head with origin/main merged (6193eec, unit 12150 pass, 0 fail): pass (typecheck, build, unit tests, generated files ok). - No visual change, so no screenshots. No changelog fragment: the search itself is unreleased (`changelog.d/chat-conversation-search.md`).
Fixes #2018. Proposed by @vibewriter-hand, who scoped it to the smaller version the closing notes of #180 describe; thank you for the careful write-up.
Change
Search conversation finds a word in a pane's conversation, older pages included, and lists the matches as snippets; picking one opens the chat at that message.
/newor a rewind replaced the transcript).conversation-searchfeature in itsfeaturesframe, so a remote PC on an older bridge bundle offers none; a 404 from such a bridge reads as "This PC's bridge cannot search conversations yet". This PR does not raiseREMOTE_BUNDLE_VERSION; remote PCs get it with the next bundle the release publishes.Design reference
It follows the Codex app's Find in chat in what it reads (user messages and final answers, older pages included, searched on the server, as #180's notes describe) and differs in what it shows: a list of matches rather than a match count with next/previous and highlights in the chat. The DESIGN.md entry says the same.
How the search runs, and the measurement the issue asked for
The bridge walks the same parsed pages the chat reads (
paneConversation, so every agent's transcript is searched exactly as its chat shows it), 50 prompts a page, newest first. I measured a whole-history walk on synthetic Claude sessions before deciding how much to search (5 tool calls, 1.2 KB of thinking and a 1.5 KB answer per prompt, tool output at the 4,000-character page cut), in-process on this desktop:The same walk from the browser would move every page's full JSON (24 MB for the 1,000-prompt session), which is why the search runs on the PC and answers with snippets only. One answer reads at most 20 pages, or 1 s, or until it has 50 matches, then names a cursor; the sheet goes on from it by itself while it is open, so the whole conversation is searched, up to the newest 200 matches. A changed query or closing the sheet aborts the read in flight.
Contract (additive only)
GET /api/pane/conversation/search?pane_id=&q=[&before=&history_id=]→ConversationSearchResponse { history_id?, matches[], cursor }(shared/conversation-search.ts, documented inshared/protocol.ts).qis required, at most 200 characters. A changed history answers 409history_changed, a pending Copilot read 503conversation_read_pending, a pane with no readable store no matches. A watching device may use it, as it may read the conversation.ServerFeature"conversation-search"in thefeaturesframe and snapshot.site/demo/transport.ts).Screenshots (demo fixtures, 1280 and 390)
The pane's menu, before → after:
The palette's actions for
>search, before → after:The search (new), and the chat after picking the last match:
Docs: a Features row in
docs/guide.md, aConversation searchentry in DESIGN.md, the two new browser scripts indocs/development.md, andchangelog.d/chat-conversation-search.md.Validation
server/conversation-search.test.tsandshared/conversation-search.test.tsfail on origin/main (Cannot find module ../shared/conversation-search.ts, 0 pass / 2 fail) and pass here (11 tests). They cover: user messages and answers found on every page while a tool's output and thinking are not; the turn identity a match carries equals the one the chat gives its rows at another page size; the page, match and time budgets and the cursor that goes on; a page from another history refused; the route's 400 / 409 / 503 / no-store-no-match answers; whitespace and case, every regex character literal, Markdown marks dropped, snippet cuts at words and never inside a surrogate pair.bun run check run bun test --timeout 15000 ./server/api.contract.test.ts ./server/ping.contract.test.ts -t "conversation/search|agent-only feature": 2 pass. A real Claude pane's 120-prompt transcript is searched across its three pages through the cursor (the user's words on the first page, the answer on the last, the tool output that holds the words left out), another history's cursor answers 409, a blank query 400; and a remote PC's bridge listsconversation-searchto the browser through the relay.bun scripts/conversation-search-browser-qa.ts(real ChatView over controlled HTTP, 200 prompts, at 1280 and 390): passes. Matches listed newest first with the tool output left out; a pick four pages of 50 back is read (exactly fourbeforereads atprompts=50) and held at the top of the view; Enter opens an already loaded match without a read; no match; a match the conversation no longer holds; Escape.bun scripts/conversation-search-demo-regression.ts(the app on demo fixtures, at 1280 and 390): passes. The pane's menu and the palette open it, a pick opens the chat at its message, and a PC whosefeaturesleave outconversation-searchoffers it in neither place.scripts/ci-browser.sh) andtsconfig.tools.json.bun run check fast: passed (workflow syntax, generated types and themes, typecheck, build, 11544 unit tests pass, 0 fail).bun run check fulllocally (the browser lane runs both new scripts in CI); no real herdr session or remote PC was used.🤖 Generated with Claude Code