Class: TUI::MessageStore
- Inherits:
-
Object
- Object
- TUI::MessageStore
- Defined in:
- lib/tui/message_store.rb
Overview
Thread-safe in-memory store for chat entries displayed in the TUI. Replaces Events::Subscribers::MessageCollector in the WebSocket-based TUI, with no dependency on Rails or the Events module.
Accepts Action Cable event payloads and stores typed entries:
-
‘:rendered, data:, event_type:, id:` for events with structured decorator output
-
‘:message, role:, content:, id:` for user/agent messages (fallback)
-
‘:tool_counter, calls:, responses:` for tool activity
Structured data takes priority when available. Events with nil rendered content fall back to existing behavior: tool events aggregate into counters, messages store role and content.
Tool counters aggregate per agent turn: a new counter starts when a tool_call arrives after a message entry. Consecutive tool events increment the same counter until the next message breaks the chain.
When an event arrives with ‘“action” => “update”` and a known `“id”`, the existing entry is replaced in-place, preserving display order.
Constant Summary collapse
- MESSAGE_TYPES =
%w[user_message agent_message].freeze
- ROLE_MAP =
{ "user_message" => "user", "agent_message" => "assistant" }.freeze
Instance Method Summary collapse
-
#clear ⇒ void
Removes all entries.
-
#initialize ⇒ MessageStore
constructor
A new instance of MessageStore.
-
#last_pending_user_message ⇒ Hash?
Returns the last pending user message for recall editing.
-
#messages ⇒ Array<Hash>
Thread-safe copy of stored entries.
-
#process_event(event_data) ⇒ Boolean
Processes a raw event payload from the WebSocket channel.
-
#remove_by_id(event_id) ⇒ Boolean
Removes an entry by its event ID.
-
#remove_by_ids(event_ids) ⇒ Integer
Removes entries by their event IDs.
-
#size ⇒ Integer
Number of stored entries (no array copy).
-
#version ⇒ Integer
Monotonically increasing counter that bumps on every mutation.
Constructor Details
#initialize ⇒ MessageStore
Returns a new instance of MessageStore.
31 32 33 34 35 36 |
# File 'lib/tui/message_store.rb', line 31 def initialize @entries = [] @entries_by_id = {} @mutex = Mutex.new @version = 0 end |
Instance Method Details
#clear ⇒ void
This method returns an undefined value.
Removes all entries. Called on view mode change and session switch to prepare for re-decorated viewport events from the server.
90 91 92 93 94 95 96 |
# File 'lib/tui/message_store.rb', line 90 def clear @mutex.synchronize do @entries = [] @entries_by_id = {} @version += 1 end end |
#last_pending_user_message ⇒ Hash?
Returns the last pending user message for recall editing. Walks entries backwards and returns the first pending user_message found.
102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 |
# File 'lib/tui/message_store.rb', line 102 def @mutex.synchronize do @entries.reverse_each do |entry| next unless entry[:event_type] == "user_message" if entry[:type] == :rendered && entry.dig(:data, "status") == "pending" return {id: entry[:id], content: entry.dig(:data, "content")} end # Only check the most recent user message break end nil end end |
#messages ⇒ Array<Hash>
Returns thread-safe copy of stored entries.
47 48 49 |
# File 'lib/tui/message_store.rb', line 47 def @mutex.synchronize { @entries.dup } end |
#process_event(event_data) ⇒ Boolean
Processes a raw event payload from the WebSocket channel. Uses structured decorator data when available; falls back to role/content extraction for messages and tool counter aggregation.
Events with ‘“action” => “update”` and a matching `“id”` replace the existing entry’s data in-place rather than appending.
66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 |
# File 'lib/tui/message_store.rb', line 66 def process_event(event_data) event_id = event_data["id"] if event_data["action"] == "update" && event_id return update_existing(event_data, event_id) end rendered = extract_rendered(event_data) if rendered record_rendered(rendered, event_type: event_data["type"], id: event_id) else case event_data["type"] when "tool_call" then record_tool_call when "tool_response" then record_tool_response when *MESSAGE_TYPES then (event_data) else false end end end |
#remove_by_id(event_id) ⇒ Boolean
Removes an entry by its event ID. Used when a pending message is recalled for editing or deleted by another client.
123 124 125 126 127 128 129 130 131 132 |
# File 'lib/tui/message_store.rb', line 123 def remove_by_id(event_id) @mutex.synchronize do entry = @entries_by_id.delete(event_id) return false unless entry @entries.delete(entry) @version += 1 true end end |
#remove_by_ids(event_ids) ⇒ Integer
Removes entries by their event IDs. Used when the brain reports that events have left the LLM’s viewport (context window eviction). Acquires the mutex once for the entire batch.
140 141 142 143 144 145 146 147 148 149 150 151 152 153 |
# File 'lib/tui/message_store.rb', line 140 def remove_by_ids(event_ids) @mutex.synchronize do removed = 0 event_ids.each do |event_id| entry = @entries_by_id.delete(event_id) next unless entry @entries.delete(entry) removed += 1 end @version += 1 if removed > 0 removed end end |
#size ⇒ Integer
Returns number of stored entries (no array copy).
52 53 54 |
# File 'lib/tui/message_store.rb', line 52 def size @mutex.synchronize { @entries.size } end |
#version ⇒ Integer
Monotonically increasing counter that bumps on every mutation. Consumers compare this to a cached value to detect changes without copying the full entries array on every frame.
42 43 44 |
# File 'lib/tui/message_store.rb', line 42 def version @mutex.synchronize { @version } end |