#19722: feat(chat): add search functionality for chat history
app: web-ui
gateway
size: M
## Summary
Closes #19725
Add text search across chat session transcripts:
- **New `chat.search` gateway method** — search within a session's message history with case-insensitive matching, snippet extraction, and configurable result limits
- **Extended `sessions.list` search** — new optional `searchContent` boolean parameter to scan transcript content in addition to session metadata (displayName, label, subject, key)
- **UI controller wiring** — `searchChatHistory()` function in chat controller, `sessionsSearch` state in sessions controller
## Changes
### Backend
- `ChatSearchParamsSchema` (sessionKey, query, limit) in protocol schema
- `searchSessionTranscript()` helper in `session-utils.fs.ts` — reads JSONL transcripts, extracts text from string/array content, returns matches with snippets
- `chat.search` handler in chat server methods, registered in gateway method list
- `searchContent` param in `SessionsListParamsSchema` — when true, `sessions.list` also scans transcript content for the search term
### Frontend
- `ChatSearchResult`, `ChatSearchMatch` types and `searchChatHistory()` in chat controller
- `sessionsSearch` state property added to sessions controller, app state, and view state
- `loadSessions()` now passes `search` and `searchContent` params
### Tests
- 10 tests across `session-utils.fs.test.ts` and `session-utils.test.ts` covering: string/array content matching, empty queries, missing sessions, limit, case-insensitivity, message indexing, malformed JSON, and cross-session content filtering integration
## Existing limitation (unchanged)
The `searchContent` option in `sessions.list` scans transcript files synchronously within the existing session filter loop. This is O(N) in session count, consistent with the existing synchronous I/O patterns used by `readSessionMessages`, `readSessionTitleFieldsFromTranscript`, etc. For large deployments with many sessions, a future improvement could add an inverted index or async streaming to avoid blocking the event loop. For now, using `limit` alongside `searchContent` is recommended.
## Test plan
- [x] `pnpm test -- --run src/gateway/session-utils.fs.test.ts` — 51 tests pass
- [x] `pnpm test -- --run src/gateway/session-utils.test.ts` — 37 tests pass
- [x] `npx tsc --noEmit` — clean type check
- [ ] CI checks on upstream
## AI-assisted
This PR was developed with AI assistance (Claude Opus 4.6). All changes have been reviewed and tested.
---
🤖 [Tackled](https://github.com/aleiby/claude-skills/tree/main/tackle) with [Claude Code](https://claude.com/claude-code)
<!-- greptile_comment -->
<h3>Greptile Summary</h3>
Adds text search functionality for chat session transcripts via a new `chat.search` gateway method and a `searchContent` option on `sessions.list`. The backend reads JSONL transcript files, extracts text from string/array content formats, and returns matches with context snippets. The frontend adds corresponding types and controller functions. Good test coverage with 10 new tests.
Two issues found:
- **Message index mismatch**: `searchSessionTranscript` counts only `parsed?.message` lines for its `messageIndex`, but `readSessionMessages` (used by `chat.history`) also includes compaction entries in its output array. This causes the search result `index` to be off when a session has compaction entries, which would cause the UI to scroll to or highlight the wrong message.
- **Missing transcript resolution params**: The `searchContent` path in `listSessionsFromStore` calls `searchSessionTranscriptFs` without passing `sessionFile` or `agentId`, unlike the comparable `readSessionTitleFieldsFromTranscript` call. This means transcript content search will silently fail for sessions stored in agent-specific directories.
<h3>Confidence Score: 3/5</h3>
- Two logic bugs should be fixed before merging: index mismatch with compaction entries and missing transcript resolution parameters for agent sessions.
- The overall structure and patterns are solid and well-tested, but two logic bugs would cause incorrect behavior in production: (1) search result indices won't match chat history array positions when compaction entries exist, and (2) searchContent in sessions.list will silently miss transcripts for agent-specific sessions due to missing sessionFile/agentId parameters.
- `src/gateway/session-utils.fs.ts` (messageIndex drift with compaction entries), `src/gateway/session-utils.ts` (missing sessionFile/agentId in searchContent call)
<sub>Last reviewed commit: 9d3d8f8</sub>
<!-- greptile_other_comments_section -->
<sub>(3/5) Reply to the agent's comments like "Can you suggest a fix for this @greptileai?" or ask follow-up questions!</sub>
<!-- /greptile_comment -->
Most Similar PRs
#20211: feat(webchat): add search UI for chat history
by aleiby · 2026-02-18
81.7%
#14309: fix(ui): resolve chat event session key mismatch
by justonlyforyou · 2026-02-11
78.6%
#21693: feat(gateway): expose session entry id on chat history messages
by tlxue · 2026-02-20
78.2%
#22798: feat(webchat): ChatGPT-style multi-chat threads with generated titles
by opnsec · 2026-02-21
77.5%
#19754: feat(webchat): add cursor-based pagination to chat.history
by aleiby · 2026-02-18
76.0%
#13548: feat(control-ui): Add quote reply and send message to session features
by Annaxiebot · 2026-02-10
75.7%
#13104: fix: persist user command message in chat transcript
by mcaxtr · 2026-02-10
75.6%
#11889: fix(chat): filter HEARTBEAT_OK messages in chat.history when showOk...
by bendyclaw · 2026-02-08
75.6%
#3721: fix(ui): webchat not displaying chat responses
by maxmaxrouge-rgb · 2026-01-29
74.8%
#19343: Refactor chat state management: reset chat messages and queue on se...
by saurav470 · 2026-02-17
74.5%