Class: TUI::MessageStore

Inherits:
Object
  • Object
show all
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

Constructor Details

#initializeMessageStore

Returns a new instance of MessageStore.



31
32
33
34
35
# File 'lib/tui/message_store.rb', line 31

def initialize
  @entries = []
  @entries_by_id = {}
  @mutex = Mutex.new
end

Instance Method Details

#clearvoid

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.



76
77
78
79
80
81
# File 'lib/tui/message_store.rb', line 76

def clear
  @mutex.synchronize do
    @entries = []
    @entries_by_id = {}
  end
end

#last_pending_user_messageHash?

Returns the last pending user message for recall editing. Walks entries backwards and returns the first pending user_message found.

Returns:

  • (Hash, nil)

    ‘Integer, content: String` or nil if none pending



87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
# File 'lib/tui/message_store.rb', line 87

def last_pending_user_message
  @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

#messagesArray<Hash>

Returns thread-safe copy of stored entries.

Returns:

  • (Array<Hash>)

    thread-safe copy of stored entries



38
39
40
# File 'lib/tui/message_store.rb', line 38

def messages
  @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.

Parameters:

  • event_data (Hash)

    Action Cable event payload with “type”, “content”, and optionally “rendered” (hash of mode => lines), “id”, “action”

Returns:

  • (Boolean)

    true if the event type was recognized and handled



52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
# File 'lib/tui/message_store.rb', line 52

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 record_message(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.

Parameters:

  • event_id (Integer)

    database ID of the event to remove

Returns:

  • (Boolean)

    true if the entry was found and removed



108
109
110
111
112
113
114
115
116
# File 'lib/tui/message_store.rb', line 108

def remove_by_id(event_id)
  @mutex.synchronize do
    entry = @entries_by_id.delete(event_id)
    return false unless entry

    @entries.delete(entry)
    true
  end
end