conversations

package
v0.74.1 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Feb 19, 2026 License: MIT Imports: 37 Imported by: 0

Documentation

Overview

Package conversations provides the content search functionality for cross-conversation search (searching message content across sessions).

Package conversations provides content search execution for cross-conversation search.

Package conversations provides content search keyboard handling for cross-conversation search (td-6ac70a).

Package conversations provides the content search modal UI for cross-conversation search (searching message content across sessions).

Package conversations implements a multi-pane session and message viewer with search, filtering, pagination, content export, and per-session analytics.

Index

Constants

View Source
const (

	// Hybrid content display thresholds
	ShortMessageCharLimit = 500 // Messages shorter than this display inline
	ShortMessageLineLimit = 13  // Messages with fewer lines display inline
	CollapsedPreviewChars = 300 // Preview length for collapsed messages

)

Variables

This section is empty.

Functions

func CopyToClipboard

func CopyToClipboard(content string) error

CopyToClipboard copies content to the system clipboard.

func ExportSessionAsMarkdown

func ExportSessionAsMarkdown(session *adapter.Session, messages []adapter.Message) string

ExportSessionAsMarkdown converts a session and its messages to markdown format.

func ExportSessionToFile

func ExportSessionToFile(session *adapter.Session, messages []adapter.Message, workDir string) (string, error)

ExportSessionToFile writes a session to a markdown file.

func RunContentSearch

func RunContentSearch(query string, sessions []adapter.Session,
	adapters map[string]adapter.Adapter, opts adapter.SearchOptions, epoch uint64) tea.Cmd

RunContentSearch executes search across all sessions using their adapters. Returns a tea.Cmd that produces ContentSearchResultsMsg when complete. Search runs in parallel with concurrency limit, timeout, and match cap. Performance optimizations (td-80cbe1):

  • Concurrency scales with CPU count
  • Sessions sorted by UpdatedAt (recent first for early results)
  • Empty sessions skipped

func UpdateSessionSummary

func UpdateSessionSummary(summary *SessionSummary, newMessages []adapter.Message, modelCounts map[string]int, fileSet map[string]bool)

UpdateSessionSummary incrementally updates an existing summary with new messages. It avoids re-scanning all messages by only processing the new ones. Note: primary model may change if new messages use a different model more frequently.

Types

type AdapterBatchMsg

type AdapterBatchMsg struct {
	Epoch         uint64
	Sessions      []adapter.Session
	Final         bool // true when all adapters are done
	WorktreePaths []string
	WorktreeNames map[string]string
}

AdapterBatchMsg delivers sessions from a single adapter incrementally (td-7198a5).

func (AdapterBatchMsg) GetEpoch

func (m AdapterBatchMsg) GetEpoch() uint64

GetEpoch implements plugin.EpochMessage.

type CoalescedRefreshMsg

type CoalescedRefreshMsg struct {
	Epoch      uint64   // Epoch when request was issued (for stale detection)
	SessionIDs []string // Specific sessions to refresh (if not RefreshAll)
	RefreshAll bool     // If true, ignore SessionIDs and do full refresh
}

CoalescedRefreshMsg is sent when the coalesce window closes.

func (CoalescedRefreshMsg) GetEpoch

func (m CoalescedRefreshMsg) GetEpoch() uint64

GetEpoch implements plugin.EpochMessage.

type ContentSearchDebounceMsg

type ContentSearchDebounceMsg struct {
	Version int    // Must match DebounceVersion to be valid
	Query   string // Query to search for
}

ContentSearchDebounceMsg is sent after debounce delay to trigger search.

type ContentSearchResultsMsg

type ContentSearchResultsMsg struct {
	Epoch        uint64                // Epoch when request was issued (for stale detection)
	Results      []SessionSearchResult // Search results by session
	Error        error                 // Error if search failed
	Query        string                // The query these results are for (td-5b9928)
	TotalMatches int                   // Total matches found before truncation (td-8e1a2b)
	Truncated    bool                  // True if results were truncated (td-8e1a2b)
}

ContentSearchResultsMsg carries search results back to the plugin.

func (ContentSearchResultsMsg) GetEpoch

func (m ContentSearchResultsMsg) GetEpoch() uint64

GetEpoch implements plugin.EpochMessage.

type ContentSearchState

type ContentSearchState struct {
	Query           string                // Current search query
	Results         []SessionSearchResult // Sessions with matching messages
	UseRegex        bool                  // Treat query as regex
	CaseSensitive   bool                  // Case-sensitive matching
	Cursor          int                   // Flat index into hierarchical results
	ScrollOffset    int                   // Scroll offset for viewport
	IsSearching     bool                  // True while search is in progress
	Error           string                // Error message if search failed
	DebounceVersion int                   // For debouncing search requests
	TotalFound      int                   // Total matches found before truncation (td-8e1a2b)
	Truncated       bool                  // True if results were truncated (td-8e1a2b)
	Skeleton        ui.Skeleton           // Animated skeleton loader for search in progress (td-e740e4)
}

ContentSearchState holds cross-conversation search state.

func NewContentSearchState

func NewContentSearchState() *ContentSearchState

NewContentSearchState creates a new content search state with defaults.

func (*ContentSearchState) ClampCursor

func (s *ContentSearchState) ClampCursor()

ClampCursor ensures cursor is within valid bounds.

func (*ContentSearchState) CollapseAll

func (s *ContentSearchState) CollapseAll()

CollapseAll collapses all sessions.

func (*ContentSearchState) EnsureCursorVisible

func (s *ContentSearchState) EnsureCursorVisible(viewportHeight int)

EnsureCursorVisible adjusts ScrollOffset to ensure the cursor is visible. viewportHeight is the number of visible rows.

func (*ContentSearchState) ExpandAll

func (s *ContentSearchState) ExpandAll()

ExpandAll expands all sessions.

func (*ContentSearchState) FirstMatchInSession

func (s *ContentSearchState) FirstMatchInSession(sessionIdx int) int

FirstMatchInSession returns the flat index of the first match in the given session. Returns -1 if session is collapsed or has no matches.

func (*ContentSearchState) FlatItem

func (s *ContentSearchState) FlatItem(idx int) (sessionIdx, msgIdx, matchIdx int, isSession, isMessage bool)

FlatItem maps a flat index to the hierarchical position. Returns (sessionIdx, msgIdx, matchIdx, isSession, isMessage). - If isSession is true, this is a session row (msgIdx and matchIdx are -1). - If isMessage is true, this is a message row (matchIdx is -1). - Otherwise, this is a match row. Returns (-1, -1, -1, false, false) if idx is out of range.

func (*ContentSearchState) FlatLen

func (s *ContentSearchState) FlatLen() int

FlatLen returns the total number of items in the flattened view. The hierarchy is: Session -> Message -> Match When a session is collapsed, its messages and matches are hidden.

func (*ContentSearchState) GetSelectedResult

GetSelectedResult returns the currently selected item based on cursor position. Returns (session, messageMatch, contentMatch) where some may be nil depending on selection type: - Session row: session is set, others are nil - Message row: session and messageMatch are set, contentMatch is nil - Match row: all three are set Returns (nil, nil, nil) if cursor is out of range.

func (*ContentSearchState) MoveToSession

func (s *ContentSearchState) MoveToSession()

MoveToSession moves the cursor to the session containing the current selection. Useful for navigating "up" in the hierarchy.

func (*ContentSearchState) NextMatchIndex

func (s *ContentSearchState) NextMatchIndex(from int) int

NextMatchIndex finds the next actual match (not session or message row) from the given index. Returns the flat index of the next match, or -1 if none found.

func (*ContentSearchState) PrevMatchIndex

func (s *ContentSearchState) PrevMatchIndex(from int) int

PrevMatchIndex finds the previous actual match (not session or message row) from the given index. Returns the flat index of the previous match, or -1 if none found.

func (*ContentSearchState) Reset

func (s *ContentSearchState) Reset()

Reset clears the search state.

func (*ContentSearchState) SessionCount

func (s *ContentSearchState) SessionCount() int

SessionCount returns the number of sessions with matches.

func (*ContentSearchState) ToggleCollapse

func (s *ContentSearchState) ToggleCollapse() bool

ToggleCollapse toggles the collapsed state of the session at the current cursor. Returns true if toggle was performed (cursor was on a session row).

func (*ContentSearchState) TotalMatches

func (s *ContentSearchState) TotalMatches() int

TotalMatches returns the total count of content matches across all results.

type DateRange

type DateRange struct {
	Preset string    // "today", "yesterday", "week", "month", "all"
	Start  time.Time // For custom range
	End    time.Time
}

DateRange represents a date range filter.

type ErrorMsg

type ErrorMsg struct{ Err error }

type EventCoalescer

type EventCoalescer struct {
	// contains filtered or unexported fields
}

EventCoalescer batches rapid watch events into single refreshes. When events arrive faster than the coalesce window, they are accumulated and a single refresh is triggered after the window closes. td-190095: Uses dynamic window based on largest pending session's size.

func NewEventCoalescer

func NewEventCoalescer(window time.Duration, msgChan chan<- CoalescedRefreshMsg) *EventCoalescer

NewEventCoalescer creates a coalescer with the given window duration. msgChan receives CoalescedRefreshMsg when the coalesce window closes.

func (*EventCoalescer) Add

func (c *EventCoalescer) Add(sessionID string, epoch uint64)

Add queues a sessionID for refresh. Empty string triggers full refresh. Uses dynamic window based on largest pending session (td-190095). The epoch parameter tracks the project context for stale detection.

func (*EventCoalescer) ShouldAutoReload

func (c *EventCoalescer) ShouldAutoReload(sessionID string) bool

ShouldAutoReload returns whether auto-reload is enabled for a session. Sessions larger than HugeSessionThreshold (500MB) disable auto-reload.

func (*EventCoalescer) Stop

func (c *EventCoalescer) Stop()

Stop cancels any pending flush. Call when plugin is shutting down.

func (*EventCoalescer) UpdateSessionSize

func (c *EventCoalescer) UpdateSessionSize(sessionID string, size int64)

UpdateSessionSize records the file size for a session (td-190095). Call this after loading sessions to enable dynamic debounce.

func (*EventCoalescer) UpdateSessionSizes

func (c *EventCoalescer) UpdateSessionSizes(sessions []adapter.Session)

UpdateSessionSizes updates sizes for multiple sessions at once.

type FocusPane

type FocusPane int

FocusPane represents which pane is active in two-pane mode.

const (
	PaneSidebar FocusPane = iota
	PaneMessages
)

type GlamourRenderer

type GlamourRenderer = markdown.Renderer

GlamourRenderer is an alias to the shared markdown renderer.

func NewGlamourRenderer

func NewGlamourRenderer() (*GlamourRenderer, error)

NewGlamourRenderer creates a new renderer instance.

type GroupSummary

type GroupSummary struct {
	SessionCount int
	TotalTokens  int
	TotalCost    float64
}

GroupSummary holds aggregate stats for a session group.

type LoadSettledMsg

type LoadSettledMsg struct {
	Token int // Must match loadSettleToken to be valid
}

LoadSettledMsg signals that session loading has settled (no new arrivals).

type LoadingStartedMsg

type LoadingStartedMsg struct {
	Epoch uint64
}

LoadingStartedMsg signals that adapter goroutines have been launched (td-7198a5).

func (LoadingStartedMsg) GetEpoch

func (m LoadingStartedMsg) GetEpoch() uint64

GetEpoch implements plugin.EpochMessage.

type MessageReloadMsg

type MessageReloadMsg struct {
	Epoch     uint64 // Epoch when request was issued (for stale detection)
	Token     int
	SessionID string
}

func (MessageReloadMsg) GetEpoch

func (m MessageReloadMsg) GetEpoch() uint64

GetEpoch implements plugin.EpochMessage.

type MessagesLoadedMsg

type MessagesLoadedMsg struct {
	Epoch      uint64 // Epoch when request was issued (for stale detection)
	SessionID  string
	Messages   []adapter.Message
	TotalCount int // Total message count before truncation (td-313ea851)
	Offset     int // Offset into the message list (td-313ea851)
}

func (MessagesLoadedMsg) GetEpoch

func (m MessagesLoadedMsg) GetEpoch() uint64

GetEpoch implements plugin.EpochMessage.

type Plugin

type Plugin struct {
	// contains filtered or unexported fields
}

Plugin implements the conversations plugin.

func New

func New() *Plugin

New creates a new conversations plugin.

func (*Plugin) Commands

func (p *Plugin) Commands() []plugin.Command

Commands returns the available commands.

func (*Plugin) ConsumesTextInput

func (p *Plugin) ConsumesTextInput() bool

ConsumesTextInput reports whether conversation UI currently has a focused text-entry flow where app shortcuts should not intercept characters.

func (*Plugin) Diagnostics

func (p *Plugin) Diagnostics() []plugin.Diagnostic

Diagnostics returns plugin health info.

func (*Plugin) FocusContext

func (p *Plugin) FocusContext() string

FocusContext returns the current focus context.

func (*Plugin) ID

func (p *Plugin) ID() string

ID returns the plugin identifier.

func (*Plugin) Icon

func (p *Plugin) Icon() string

Icon returns the plugin icon character.

func (*Plugin) Init

func (p *Plugin) Init(ctx *plugin.Context) error

Init initializes the plugin with context.

func (*Plugin) IsFocused

func (p *Plugin) IsFocused() bool

IsFocused returns whether the plugin is focused.

func (*Plugin) Name

func (p *Plugin) Name() string

Name returns the plugin display name.

func (*Plugin) SetFocused

func (p *Plugin) SetFocused(f bool)

SetFocused sets the focus state.

func (*Plugin) Start

func (p *Plugin) Start() tea.Cmd

Start begins plugin operation.

func (*Plugin) Stop

func (p *Plugin) Stop()

Stop cleans up plugin resources.

func (*Plugin) Update

func (p *Plugin) Update(msg tea.Msg) (plugin.Plugin, tea.Cmd)

Update handles messages.

func (*Plugin) View

func (p *Plugin) View(width, height int) string

View renders the plugin.

type PreviewLoadMsg

type PreviewLoadMsg struct {
	Epoch     uint64 // Epoch when request was issued (for stale detection)
	Token     int
	SessionID string
}

func (PreviewLoadMsg) GetEpoch

func (m PreviewLoadMsg) GetEpoch() uint64

GetEpoch implements plugin.EpochMessage.

type SearchFilters

type SearchFilters struct {
	Query      string    // Text search
	Adapters   []string  // ["claude-code", "codex"]
	Models     []string  // ["opus", "sonnet", "haiku"]
	Categories []string  // ["interactive", "cron", "system"]
	DateRange  DateRange // today, week, custom
	MinTokens  int       // Sessions with > N tokens
	MaxTokens  int       // Sessions with < N tokens
	ActiveOnly bool      // Only currently active
	HasFiles   []string  // Sessions that touched these files
}

SearchFilters holds multi-dimensional filter criteria.

func (*SearchFilters) HasAdapter

func (f *SearchFilters) HasAdapter(adapterID string) bool

HasAdapter returns true if the adapter is in the filter list.

func (*SearchFilters) HasCategory

func (f *SearchFilters) HasCategory(cat string) bool

HasCategory returns true if the category is in the filter list.

func (*SearchFilters) HasModel

func (f *SearchFilters) HasModel(model string) bool

HasModel returns true if the model is in the filter list.

func (*SearchFilters) IsActive

func (f *SearchFilters) IsActive() bool

IsActive returns true if any filter is active.

func (*SearchFilters) Matches

func (f *SearchFilters) Matches(session adapter.Session) bool

Matches checks if a session matches all filter criteria.

func (*SearchFilters) SetDateRange

func (f *SearchFilters) SetDateRange(preset string)

SetDateRange sets the date range preset.

func (*SearchFilters) String

func (f *SearchFilters) String() string

String formats active filters for display.

func (*SearchFilters) ToggleAdapter

func (f *SearchFilters) ToggleAdapter(adapterID string)

ToggleAdapter toggles an adapter in the filter list.

func (*SearchFilters) ToggleCategory

func (f *SearchFilters) ToggleCategory(cat string)

ToggleCategory toggles a category in the filter list.

func (*SearchFilters) ToggleModel

func (f *SearchFilters) ToggleModel(model string)

ToggleModel toggles a model in the filter list.

type SessionGroup

type SessionGroup struct {
	Label    string            // "Today", "Yesterday", "This Week", "Older"
	Sessions []adapter.Session // Sessions in this group
	Summary  GroupSummary      // Aggregate stats
}

SessionGroup represents a group of sessions by time period.

func GroupSessionsByTime

func GroupSessionsByTime(sessions []adapter.Session) []SessionGroup

GroupSessionsByTime organizes sessions into time-based groups.

type SessionSearchResult

type SessionSearchResult struct {
	Session   adapter.Session        // The session containing matches
	Messages  []adapter.MessageMatch // Messages with matches (from adapter search)
	Collapsed bool                   // True if session is collapsed in view
}

SessionSearchResult represents a session with matching messages.

type SessionSummary

type SessionSummary struct {
	FilesTouched    []string       // Unique files from tool uses
	FileCount       int            // Number of unique files
	TotalTokensIn   int            // Sum of input tokens
	TotalTokensOut  int            // Sum of output tokens
	TotalCacheRead  int            // Sum of cache read tokens
	TotalCacheWrite int            // Sum of cache write tokens
	TotalCost       float64        // Estimated cost in dollars
	Duration        time.Duration  // Session duration
	PrimaryModel    string         // Most used model
	MessageCount    int            // Total messages
	ToolCounts      map[string]int // Tool name -> count
}

SessionSummary holds aggregated statistics for a session.

func ComputeSessionSummary

func ComputeSessionSummary(messages []adapter.Message, duration time.Duration) SessionSummary

ComputeSessionSummary aggregates statistics from messages.

type SessionsLoadedMsg

type SessionsLoadedMsg struct {
	Epoch    uint64 // Epoch when request was issued (for stale detection)
	Sessions []adapter.Session
	// Worktree cache data (td-0e43c080: computed in cmd, stored in Update)
	WorktreePaths []string
	WorktreeNames map[string]string
}

Message types

func (SessionsLoadedMsg) GetEpoch

func (m SessionsLoadedMsg) GetEpoch() uint64

GetEpoch implements plugin.EpochMessage.

type SessionsRefreshedMsg

type SessionsRefreshedMsg struct {
	Epoch     uint64
	Refreshed []adapter.Session // Only the sessions that were successfully refreshed
}

SessionsRefreshedMsg carries only refreshed sessions as a delta update. Unlike SessionsLoadedMsg which replaces the entire session list, this merges into the current list to avoid overwriting concurrent updates from loadSessions.

func (SessionsRefreshedMsg) GetEpoch

func (m SessionsRefreshedMsg) GetEpoch() uint64

GetEpoch implements plugin.EpochMessage.

type Turn

type Turn struct {
	Role           string            // "user" or "assistant"
	Messages       []adapter.Message // All messages in this turn
	StartIndex     int               // Index of first message in original slice
	TotalTokensIn  int               // Sum of input tokens
	TotalTokensOut int               // Sum of output tokens
	ThinkingTokens int               // Sum of thinking block tokens
	ToolCount      int               // Number of tool uses
}

Turn represents a sequence of consecutive messages from the same role. In Claude Code, a single "turn" may contain multiple JSONL messages: - User turn: system reminders, command output, actual user text - Assistant turn: thinking blocks, tool calls, text responses

func AppendMessagesToTurns

func AppendMessagesToTurns(turns []Turn, newMessages []adapter.Message, startIndex int) []Turn

AppendMessagesToTurns incrementally adds new messages to existing turns. It handles the case where the first new message may extend the last turn. Returns the updated turns slice (may modify the last element in place).

func GroupMessagesIntoTurns

func GroupMessagesIntoTurns(messages []adapter.Message) []Turn

GroupMessagesIntoTurns groups consecutive messages by role into turns.

func (*Turn) FirstTimestamp

func (t *Turn) FirstTimestamp() string

FirstTimestamp returns the timestamp of the first message in the turn.

func (*Turn) Preview

func (t *Turn) Preview(maxLen int) string

Preview returns a content preview from the first message with meaningful content.

type View

type View int

View represents the current view mode.

const (
	ViewSessions View = iota
	ViewMessages
	ViewAnalytics
	ViewMessageDetail
)

type WatchEventMsg

type WatchEventMsg struct {
	Epoch     uint64 // Epoch when request was issued (for stale detection)
	SessionID string // ID of the session that changed (empty for periodic refresh)
}

func (WatchEventMsg) GetEpoch

func (m WatchEventMsg) GetEpoch() uint64

GetEpoch implements plugin.EpochMessage.

type WatchStartedMsg

type WatchStartedMsg struct {
	Channel <-chan adapter.Event
	Closers []io.Closer
}

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL