smart_message 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.envrc +3 -0
- data/.gitignore +8 -0
- data/.travis.yml +7 -0
- data/CHANGELOG.md +100 -0
- data/COMMITS.md +196 -0
- data/Gemfile +6 -0
- data/Gemfile.lock +71 -0
- data/README.md +303 -0
- data/Rakefile +10 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/docs/README.md +52 -0
- data/docs/architecture.md +370 -0
- data/docs/dispatcher.md +593 -0
- data/docs/examples.md +808 -0
- data/docs/getting-started.md +235 -0
- data/docs/ideas_to_think_about.md +329 -0
- data/docs/serializers.md +575 -0
- data/docs/transports.md +501 -0
- data/docs/troubleshooting.md +582 -0
- data/examples/01_point_to_point_orders.rb +200 -0
- data/examples/02_publish_subscribe_events.rb +364 -0
- data/examples/03_many_to_many_chat.rb +608 -0
- data/examples/README.md +335 -0
- data/examples/tmux_chat/README.md +283 -0
- data/examples/tmux_chat/bot_agent.rb +272 -0
- data/examples/tmux_chat/human_agent.rb +197 -0
- data/examples/tmux_chat/room_monitor.rb +158 -0
- data/examples/tmux_chat/shared_chat_system.rb +295 -0
- data/examples/tmux_chat/start_chat_demo.sh +190 -0
- data/examples/tmux_chat/stop_chat_demo.sh +22 -0
- data/lib/simple_stats.rb +57 -0
- data/lib/smart_message/base.rb +284 -0
- data/lib/smart_message/dispatcher/.keep +0 -0
- data/lib/smart_message/dispatcher.rb +146 -0
- data/lib/smart_message/errors.rb +29 -0
- data/lib/smart_message/header.rb +20 -0
- data/lib/smart_message/logger/base.rb +8 -0
- data/lib/smart_message/logger.rb +7 -0
- data/lib/smart_message/serializer/base.rb +23 -0
- data/lib/smart_message/serializer/json.rb +22 -0
- data/lib/smart_message/serializer.rb +10 -0
- data/lib/smart_message/transport/base.rb +85 -0
- data/lib/smart_message/transport/memory_transport.rb +69 -0
- data/lib/smart_message/transport/registry.rb +59 -0
- data/lib/smart_message/transport/stdout_transport.rb +62 -0
- data/lib/smart_message/transport.rb +41 -0
- data/lib/smart_message/version.rb +7 -0
- data/lib/smart_message/wrapper.rb +43 -0
- data/lib/smart_message.rb +54 -0
- data/smart_message.gemspec +53 -0
- metadata +252 -0
@@ -0,0 +1,272 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# examples/tmux_chat/bot_agent.rb
|
3
|
+
#
|
4
|
+
# Bot agent for tmux chat visualization
|
5
|
+
|
6
|
+
require_relative 'shared_chat_system'
|
7
|
+
|
8
|
+
class BotChatAgent < BaseAgent
|
9
|
+
def initialize(bot_id:, name:, capabilities: [])
|
10
|
+
@capabilities = capabilities
|
11
|
+
@command_count = 0
|
12
|
+
super(agent_id: bot_id, name: name, agent_type: 'bot')
|
13
|
+
|
14
|
+
log_display("🤖 Bot #{@name} online!")
|
15
|
+
log_display("🔧 Capabilities: #{@capabilities.join(', ')}")
|
16
|
+
log_display("⚡ Listening for commands and messages...")
|
17
|
+
log_display("")
|
18
|
+
end
|
19
|
+
|
20
|
+
def setup_subscriptions
|
21
|
+
# Subscribe to bot commands and chat messages
|
22
|
+
BotCommandMessage.subscribe("BotChatAgent.handle_bot_command_#{@agent_id}")
|
23
|
+
ChatMessage.subscribe("BotChatAgent.handle_chat_message_#{@agent_id}")
|
24
|
+
|
25
|
+
# Register this instance
|
26
|
+
@@bots ||= {}
|
27
|
+
@@bots[@agent_id] = self
|
28
|
+
end
|
29
|
+
|
30
|
+
def run
|
31
|
+
# Start processing messages and wait
|
32
|
+
begin
|
33
|
+
while true
|
34
|
+
sleep(1)
|
35
|
+
end
|
36
|
+
rescue Interrupt
|
37
|
+
shutdown
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
# Class method routing for SmartMessage
|
42
|
+
def self.method_missing(method_name, *args)
|
43
|
+
if method_name.to_s.start_with?('handle_bot_command_')
|
44
|
+
bot_id = method_name.to_s.split('_').last
|
45
|
+
bot = (@@bots ||= {})[bot_id]
|
46
|
+
bot&.handle_bot_command(*args)
|
47
|
+
elsif method_name.to_s.start_with?('handle_chat_message_')
|
48
|
+
bot_id = method_name.to_s.split('_').last
|
49
|
+
bot = (@@bots ||= {})[bot_id]
|
50
|
+
bot&.handle_chat_message(*args)
|
51
|
+
else
|
52
|
+
super
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
def handle_bot_command(message_header, message_payload)
|
57
|
+
command_data = JSON.parse(message_payload)
|
58
|
+
|
59
|
+
# Only handle commands in rooms we're in and commands we can handle
|
60
|
+
return unless @active_rooms.include?(command_data['room_id'])
|
61
|
+
return unless can_handle_command?(command_data['command'])
|
62
|
+
|
63
|
+
@command_count += 1
|
64
|
+
log_display("⚡ Processing command: /#{command_data['command']} (#{@command_count})")
|
65
|
+
|
66
|
+
process_command(command_data)
|
67
|
+
end
|
68
|
+
|
69
|
+
def handle_chat_message(message_header, message_payload)
|
70
|
+
chat_data = JSON.parse(message_payload)
|
71
|
+
|
72
|
+
# Only process messages from rooms we're in and not our own messages
|
73
|
+
return unless @active_rooms.include?(chat_data['room_id'])
|
74
|
+
return if chat_data['sender_id'] == @agent_id
|
75
|
+
|
76
|
+
# Log the message
|
77
|
+
log_display("👁️ [#{chat_data['room_id']}] #{chat_data['sender_name']}: #{chat_data['content']}")
|
78
|
+
|
79
|
+
# Check if it's a bot command
|
80
|
+
if chat_data['content'].start_with?('/')
|
81
|
+
handle_inline_command(chat_data)
|
82
|
+
else
|
83
|
+
# Respond to certain keywords
|
84
|
+
respond_to_keywords(chat_data)
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
def can_handle_command?(command)
|
89
|
+
@capabilities.include?(command)
|
90
|
+
end
|
91
|
+
|
92
|
+
private
|
93
|
+
|
94
|
+
def handle_inline_command(chat_data)
|
95
|
+
content = chat_data['content']
|
96
|
+
command_parts = content[1..-1].split(' ')
|
97
|
+
command = command_parts.first
|
98
|
+
parameters = command_parts[1..-1]
|
99
|
+
|
100
|
+
return unless can_handle_command?(command)
|
101
|
+
|
102
|
+
# Create bot command message
|
103
|
+
bot_command = BotCommandMessage.new(
|
104
|
+
command_id: "CMD-#{@agent_id}-#{Time.now.to_i}-#{rand(1000)}",
|
105
|
+
room_id: chat_data['room_id'],
|
106
|
+
user_id: chat_data['sender_id'],
|
107
|
+
user_name: chat_data['sender_name'],
|
108
|
+
command: command,
|
109
|
+
parameters: parameters,
|
110
|
+
timestamp: Time.now.iso8601
|
111
|
+
)
|
112
|
+
|
113
|
+
bot_command.publish
|
114
|
+
end
|
115
|
+
|
116
|
+
def process_command(command_data)
|
117
|
+
case command_data['command']
|
118
|
+
when 'weather'
|
119
|
+
handle_weather_command(command_data)
|
120
|
+
when 'joke'
|
121
|
+
handle_joke_command(command_data)
|
122
|
+
when 'help'
|
123
|
+
handle_help_command(command_data)
|
124
|
+
when 'stats'
|
125
|
+
handle_stats_command(command_data)
|
126
|
+
when 'time'
|
127
|
+
handle_time_command(command_data)
|
128
|
+
when 'echo'
|
129
|
+
handle_echo_command(command_data)
|
130
|
+
else
|
131
|
+
send_bot_response(
|
132
|
+
room_id: command_data['room_id'],
|
133
|
+
content: "🤷♂️ Sorry, I don't know how to handle /#{command_data['command']}"
|
134
|
+
)
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
138
|
+
def respond_to_keywords(chat_data)
|
139
|
+
content = chat_data['content'].downcase
|
140
|
+
|
141
|
+
if content.include?('hello') || content.include?('hi')
|
142
|
+
send_bot_response(
|
143
|
+
room_id: chat_data['room_id'],
|
144
|
+
content: "Hello #{chat_data['sender_name']}! 👋"
|
145
|
+
)
|
146
|
+
elsif content.include?('help') && !content.start_with?('/')
|
147
|
+
send_bot_response(
|
148
|
+
room_id: chat_data['room_id'],
|
149
|
+
content: "Type /help to see my commands! 🤖"
|
150
|
+
)
|
151
|
+
elsif content.include?('thank')
|
152
|
+
send_bot_response(
|
153
|
+
room_id: chat_data['room_id'],
|
154
|
+
content: "You're welcome! 😊"
|
155
|
+
)
|
156
|
+
end
|
157
|
+
end
|
158
|
+
|
159
|
+
def handle_weather_command(command_data)
|
160
|
+
location = command_data['parameters'].first || 'your location'
|
161
|
+
weather_responses = [
|
162
|
+
"☀️ It's sunny and 72°F in #{location}!",
|
163
|
+
"🌧️ Looks like rain and 65°F in #{location}",
|
164
|
+
"❄️ Snow expected, 32°F in #{location}",
|
165
|
+
"⛅ Partly cloudy, 68°F in #{location}",
|
166
|
+
"🌪️ Tornado warning in #{location}! (Just kidding, it's nice)"
|
167
|
+
]
|
168
|
+
|
169
|
+
# Simulate API delay
|
170
|
+
Thread.new do
|
171
|
+
sleep(0.5)
|
172
|
+
send_bot_response(
|
173
|
+
room_id: command_data['room_id'],
|
174
|
+
content: weather_responses.sample
|
175
|
+
)
|
176
|
+
end
|
177
|
+
end
|
178
|
+
|
179
|
+
def handle_joke_command(command_data)
|
180
|
+
jokes = [
|
181
|
+
"Why don't scientists trust atoms? Because they make up everything! 😄",
|
182
|
+
"Why did the scarecrow win an award? He was outstanding in his field! 🌾",
|
183
|
+
"What do you call a fake noodle? An impasta! 🍝",
|
184
|
+
"Why don't eggs tell jokes? They'd crack each other up! 🥚",
|
185
|
+
"What do you call a sleeping bull? A bulldozer! 😴",
|
186
|
+
"Why don't robots ever panic? They have enough bytes! 🤖"
|
187
|
+
]
|
188
|
+
|
189
|
+
send_bot_response(
|
190
|
+
room_id: command_data['room_id'],
|
191
|
+
content: jokes.sample
|
192
|
+
)
|
193
|
+
end
|
194
|
+
|
195
|
+
def handle_help_command(command_data)
|
196
|
+
help_text = @capabilities.map do |cmd|
|
197
|
+
" /#{cmd} - #{get_command_description(cmd)}"
|
198
|
+
end.join("\n")
|
199
|
+
|
200
|
+
send_bot_response(
|
201
|
+
room_id: command_data['room_id'],
|
202
|
+
content: "🤖 #{@name} Commands:\n#{help_text}"
|
203
|
+
)
|
204
|
+
end
|
205
|
+
|
206
|
+
def handle_stats_command(command_data)
|
207
|
+
send_bot_response(
|
208
|
+
room_id: command_data['room_id'],
|
209
|
+
content: "📊 Bot Stats:\n" +
|
210
|
+
" • Active rooms: #{@active_rooms.length}\n" +
|
211
|
+
" • Commands processed: #{@command_count}\n" +
|
212
|
+
" • Capabilities: #{@capabilities.length}\n" +
|
213
|
+
" • Uptime: #{Time.now.strftime('%H:%M:%S')}"
|
214
|
+
)
|
215
|
+
end
|
216
|
+
|
217
|
+
def handle_time_command(command_data)
|
218
|
+
timezone = command_data['parameters'].first || 'local'
|
219
|
+
current_time = Time.now.strftime('%Y-%m-%d %H:%M:%S')
|
220
|
+
|
221
|
+
send_bot_response(
|
222
|
+
room_id: command_data['room_id'],
|
223
|
+
content: "🕒 Current time (#{timezone}): #{current_time}"
|
224
|
+
)
|
225
|
+
end
|
226
|
+
|
227
|
+
def handle_echo_command(command_data)
|
228
|
+
message = command_data['parameters'].join(' ')
|
229
|
+
if message.empty?
|
230
|
+
message = "Echo! Echo! Echo! 📢"
|
231
|
+
end
|
232
|
+
|
233
|
+
send_bot_response(
|
234
|
+
room_id: command_data['room_id'],
|
235
|
+
content: "🔊 Echo: #{message}"
|
236
|
+
)
|
237
|
+
end
|
238
|
+
|
239
|
+
def get_command_description(command)
|
240
|
+
descriptions = {
|
241
|
+
'weather' => 'Get weather information',
|
242
|
+
'joke' => 'Tell a random joke',
|
243
|
+
'help' => 'Show this help message',
|
244
|
+
'stats' => 'Show bot statistics',
|
245
|
+
'time' => 'Show current time',
|
246
|
+
'echo' => 'Echo your message'
|
247
|
+
}
|
248
|
+
|
249
|
+
descriptions[command] || 'No description available'
|
250
|
+
end
|
251
|
+
|
252
|
+
def send_bot_response(room_id:, content:)
|
253
|
+
send_message(room_id: room_id, content: content, message_type: 'bot')
|
254
|
+
log_display("💬 Replied to [#{room_id}]: #{content}")
|
255
|
+
end
|
256
|
+
end
|
257
|
+
|
258
|
+
# Main execution
|
259
|
+
if __FILE__ == $0
|
260
|
+
if ARGV.length < 2
|
261
|
+
puts "Usage: #{$0} <bot_id> <name> [capability1,capability2,...]"
|
262
|
+
puts "Example: #{$0} helpbot HelpBot help,stats,time"
|
263
|
+
exit 1
|
264
|
+
end
|
265
|
+
|
266
|
+
bot_id = ARGV[0]
|
267
|
+
name = ARGV[1]
|
268
|
+
capabilities = ARGV[2] ? ARGV[2].split(',') : ['help']
|
269
|
+
|
270
|
+
bot = BotChatAgent.new(bot_id: bot_id, name: name, capabilities: capabilities)
|
271
|
+
bot.run
|
272
|
+
end
|
@@ -0,0 +1,197 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# examples/tmux_chat/human_agent.rb
|
3
|
+
#
|
4
|
+
# Human chat agent for tmux visualization
|
5
|
+
|
6
|
+
require_relative 'shared_chat_system'
|
7
|
+
|
8
|
+
begin
|
9
|
+
require 'io/console'
|
10
|
+
rescue LoadError
|
11
|
+
# Console methods may not be available, handled gracefully
|
12
|
+
end
|
13
|
+
|
14
|
+
class HumanChatAgent < BaseAgent
|
15
|
+
def initialize(user_id:, name:)
|
16
|
+
@message_counter = 0
|
17
|
+
super(agent_id: user_id, name: name, agent_type: 'human')
|
18
|
+
|
19
|
+
log_display("👤 Human agent #{@name} ready!")
|
20
|
+
log_display("Commands: /join <room>, /leave <room>, /list, /quit")
|
21
|
+
log_display("Type messages to send to current rooms")
|
22
|
+
log_display("")
|
23
|
+
end
|
24
|
+
|
25
|
+
def setup_subscriptions
|
26
|
+
# Subscribe to chat messages
|
27
|
+
ChatMessage.subscribe("HumanChatAgent.handle_chat_message_#{@agent_id}")
|
28
|
+
SystemNotificationMessage.subscribe("HumanChatAgent.handle_system_notification_#{@agent_id}")
|
29
|
+
|
30
|
+
# Register this instance for method dispatch
|
31
|
+
@@agents ||= {}
|
32
|
+
@@agents[@agent_id] = self
|
33
|
+
end
|
34
|
+
|
35
|
+
def start_interactive_session
|
36
|
+
while true
|
37
|
+
begin
|
38
|
+
print "> "
|
39
|
+
input = STDIN.gets&.chomp
|
40
|
+
break if input.nil?
|
41
|
+
|
42
|
+
next if input.strip.empty?
|
43
|
+
|
44
|
+
if input.start_with?('/')
|
45
|
+
handle_command(input)
|
46
|
+
else
|
47
|
+
send_to_active_rooms(input)
|
48
|
+
end
|
49
|
+
rescue Interrupt
|
50
|
+
break
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
shutdown
|
55
|
+
end
|
56
|
+
|
57
|
+
# Class method routing for SmartMessage
|
58
|
+
def self.method_missing(method_name, *args)
|
59
|
+
if method_name.to_s.start_with?('handle_chat_message_')
|
60
|
+
user_id = method_name.to_s.split('_').last
|
61
|
+
agent = (@@agents ||= {})[user_id]
|
62
|
+
agent&.handle_chat_message(*args)
|
63
|
+
elsif method_name.to_s.start_with?('handle_system_notification_')
|
64
|
+
user_id = method_name.to_s.split('_').last
|
65
|
+
agent = (@@agents ||= {})[user_id]
|
66
|
+
agent&.handle_system_notification(*args)
|
67
|
+
else
|
68
|
+
super
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
def handle_chat_message(message_header, message_payload)
|
73
|
+
chat_data = JSON.parse(message_payload)
|
74
|
+
|
75
|
+
# Only process messages from rooms we're in and not our own messages
|
76
|
+
return unless @active_rooms.include?(chat_data['room_id'])
|
77
|
+
return if chat_data['sender_id'] == @agent_id
|
78
|
+
|
79
|
+
sender_emoji = case chat_data['message_type']
|
80
|
+
when 'bot' then '🤖'
|
81
|
+
when 'system' then '🔔'
|
82
|
+
else '👤'
|
83
|
+
end
|
84
|
+
|
85
|
+
log_display("#{sender_emoji} [#{chat_data['room_id']}] #{chat_data['sender_name']}: #{chat_data['content']}")
|
86
|
+
|
87
|
+
# Auto-respond if mentioned
|
88
|
+
if chat_data['mentions']&.include?(@name.downcase) || chat_data['content'].include?("@#{@name}")
|
89
|
+
respond_to_mention(chat_data)
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
def handle_system_notification(message_header, message_payload)
|
94
|
+
notif_data = JSON.parse(message_payload)
|
95
|
+
|
96
|
+
# Only process notifications from rooms we're in
|
97
|
+
return unless @active_rooms.include?(notif_data['room_id'])
|
98
|
+
|
99
|
+
log_display("🔔 [#{notif_data['room_id']}] #{notif_data['content']}")
|
100
|
+
end
|
101
|
+
|
102
|
+
private
|
103
|
+
|
104
|
+
def handle_command(input)
|
105
|
+
parts = input[1..-1].split(' ')
|
106
|
+
command = parts[0]
|
107
|
+
args = parts[1..-1]
|
108
|
+
|
109
|
+
case command
|
110
|
+
when 'join'
|
111
|
+
if args.empty?
|
112
|
+
log_display("❌ Usage: /join <room_id>")
|
113
|
+
else
|
114
|
+
join_room(args[0])
|
115
|
+
update_display_header
|
116
|
+
end
|
117
|
+
when 'leave'
|
118
|
+
if args.empty?
|
119
|
+
log_display("❌ Usage: /leave <room_id>")
|
120
|
+
else
|
121
|
+
leave_room(args[0])
|
122
|
+
update_display_header
|
123
|
+
end
|
124
|
+
when 'list'
|
125
|
+
log_display("📋 Active rooms: #{@active_rooms.empty? ? 'none' : @active_rooms.join(', ')}")
|
126
|
+
when 'quit', 'exit'
|
127
|
+
log_display("👋 Goodbye!")
|
128
|
+
exit(0)
|
129
|
+
when 'help'
|
130
|
+
log_display("📖 Commands:")
|
131
|
+
log_display(" /join <room> - Join a chat room")
|
132
|
+
log_display(" /leave <room> - Leave a chat room")
|
133
|
+
log_display(" /list - List active rooms")
|
134
|
+
log_display(" /quit - Exit the chat")
|
135
|
+
else
|
136
|
+
log_display("❌ Unknown command: /#{command}. Type /help for help.")
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
def send_to_active_rooms(message)
|
141
|
+
if @active_rooms.empty?
|
142
|
+
log_display("❌ You're not in any rooms. Use /join <room> to join a room.")
|
143
|
+
return
|
144
|
+
end
|
145
|
+
|
146
|
+
@active_rooms.each do |room_id|
|
147
|
+
send_message(room_id: room_id, content: message)
|
148
|
+
end
|
149
|
+
end
|
150
|
+
|
151
|
+
def respond_to_mention(chat_data)
|
152
|
+
responses = [
|
153
|
+
"Thanks for mentioning me!",
|
154
|
+
"I'm here, what's up?",
|
155
|
+
"How can I help?",
|
156
|
+
"Yes, I saw that!",
|
157
|
+
"Interesting point!"
|
158
|
+
]
|
159
|
+
|
160
|
+
# Delay response slightly to make it feel natural
|
161
|
+
Thread.new do
|
162
|
+
sleep(0.5 + rand)
|
163
|
+
send_message(
|
164
|
+
room_id: chat_data['room_id'],
|
165
|
+
content: responses.sample
|
166
|
+
)
|
167
|
+
end
|
168
|
+
end
|
169
|
+
|
170
|
+
def update_display_header
|
171
|
+
# Move cursor to update rooms line
|
172
|
+
print "\033[4;9H" # Move to line 4, column 9
|
173
|
+
print "#{@active_rooms.join(', ').ljust(42)}"
|
174
|
+
# Move cursor to bottom (default to 40 lines if console unavailable)
|
175
|
+
max_lines = begin
|
176
|
+
IO.console&.winsize&.first || 40
|
177
|
+
rescue
|
178
|
+
40
|
179
|
+
end
|
180
|
+
print "\033[#{max_lines};1H"
|
181
|
+
end
|
182
|
+
end
|
183
|
+
|
184
|
+
# Main execution
|
185
|
+
if __FILE__ == $0
|
186
|
+
if ARGV.length < 2
|
187
|
+
puts "Usage: #{$0} <user_id> <name>"
|
188
|
+
puts "Example: #{$0} alice Alice"
|
189
|
+
exit 1
|
190
|
+
end
|
191
|
+
|
192
|
+
user_id = ARGV[0]
|
193
|
+
name = ARGV[1]
|
194
|
+
|
195
|
+
agent = HumanChatAgent.new(user_id: user_id, name: name)
|
196
|
+
agent.start_interactive_session
|
197
|
+
end
|
@@ -0,0 +1,158 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# examples/tmux_chat/room_monitor.rb
|
3
|
+
#
|
4
|
+
# Room activity monitor for tmux chat visualization
|
5
|
+
|
6
|
+
require_relative 'shared_chat_system'
|
7
|
+
|
8
|
+
begin
|
9
|
+
require 'io/console'
|
10
|
+
rescue LoadError
|
11
|
+
# Console methods may not be available, handled gracefully
|
12
|
+
end
|
13
|
+
|
14
|
+
class RoomMonitor < BaseAgent
|
15
|
+
def initialize(room_id)
|
16
|
+
@room_id = room_id
|
17
|
+
@message_count = 0
|
18
|
+
@user_count = 0
|
19
|
+
@last_activity = Time.now
|
20
|
+
@participants = Set.new
|
21
|
+
|
22
|
+
super(agent_id: "monitor-#{room_id}", name: "Room Monitor", agent_type: 'monitor')
|
23
|
+
|
24
|
+
join_room(@room_id)
|
25
|
+
log_display("🏠 Monitoring room: #{@room_id}")
|
26
|
+
log_display("📊 Waiting for activity...")
|
27
|
+
log_display("")
|
28
|
+
end
|
29
|
+
|
30
|
+
def setup_subscriptions
|
31
|
+
ChatMessage.subscribe("RoomMonitor.handle_chat_message_#{@agent_id}")
|
32
|
+
SystemNotificationMessage.subscribe("RoomMonitor.handle_system_notification_#{@agent_id}")
|
33
|
+
|
34
|
+
@@monitors ||= {}
|
35
|
+
@@monitors[@agent_id] = self
|
36
|
+
end
|
37
|
+
|
38
|
+
def run
|
39
|
+
# Update display every few seconds
|
40
|
+
begin
|
41
|
+
while true
|
42
|
+
sleep(2)
|
43
|
+
update_stats_display
|
44
|
+
end
|
45
|
+
rescue Interrupt
|
46
|
+
shutdown
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
# Class method routing
|
51
|
+
def self.method_missing(method_name, *args)
|
52
|
+
if method_name.to_s.start_with?('handle_chat_message_')
|
53
|
+
monitor_id = method_name.to_s.split('_', 4).last
|
54
|
+
monitor = (@@monitors ||= {})[monitor_id]
|
55
|
+
monitor&.handle_chat_message(*args)
|
56
|
+
elsif method_name.to_s.start_with?('handle_system_notification_')
|
57
|
+
monitor_id = method_name.to_s.split('_', 4).last
|
58
|
+
monitor = (@@monitors ||= {})[monitor_id]
|
59
|
+
monitor&.handle_system_notification(*args)
|
60
|
+
else
|
61
|
+
super
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
def handle_chat_message(message_header, message_payload)
|
66
|
+
chat_data = JSON.parse(message_payload)
|
67
|
+
|
68
|
+
return unless chat_data['room_id'] == @room_id
|
69
|
+
return if chat_data['sender_id'] == @agent_id
|
70
|
+
|
71
|
+
@message_count += 1
|
72
|
+
@last_activity = Time.now
|
73
|
+
@participants.add(chat_data['sender_name'])
|
74
|
+
|
75
|
+
sender_emoji = case chat_data['message_type']
|
76
|
+
when 'bot' then '🤖'
|
77
|
+
when 'system' then '🔔'
|
78
|
+
else '👤'
|
79
|
+
end
|
80
|
+
|
81
|
+
# Show the message with metadata
|
82
|
+
timestamp = Time.now.strftime("%H:%M:%S")
|
83
|
+
log_display("#{sender_emoji} [#{timestamp}] #{chat_data['sender_name']}: #{chat_data['content']}")
|
84
|
+
|
85
|
+
# Check for commands
|
86
|
+
if chat_data['content'].start_with?('/')
|
87
|
+
log_display("⚡ Command detected: #{chat_data['content']}")
|
88
|
+
end
|
89
|
+
|
90
|
+
# Check for mentions
|
91
|
+
if chat_data['mentions'] && !chat_data['mentions'].empty?
|
92
|
+
log_display("🏷️ Mentions: #{chat_data['mentions'].join(', ')}")
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
def handle_system_notification(message_header, message_payload)
|
97
|
+
notif_data = JSON.parse(message_payload)
|
98
|
+
|
99
|
+
return unless notif_data['room_id'] == @room_id
|
100
|
+
|
101
|
+
case notif_data['notification_type']
|
102
|
+
when 'user_joined'
|
103
|
+
@user_count += 1
|
104
|
+
log_display("📥 #{notif_data['content']}")
|
105
|
+
when 'user_left'
|
106
|
+
@user_count = [@user_count - 1, 0].max
|
107
|
+
log_display("📤 #{notif_data['content']}")
|
108
|
+
else
|
109
|
+
log_display("🔔 #{notif_data['content']}")
|
110
|
+
end
|
111
|
+
|
112
|
+
@last_activity = Time.now
|
113
|
+
end
|
114
|
+
|
115
|
+
private
|
116
|
+
|
117
|
+
def update_stats_display
|
118
|
+
# Move cursor to update stats
|
119
|
+
time_since_activity = Time.now - @last_activity
|
120
|
+
activity_status = if time_since_activity < 10
|
121
|
+
"🟢 Active"
|
122
|
+
elsif time_since_activity < 60
|
123
|
+
"🟡 Quiet (#{time_since_activity.to_i}s ago)"
|
124
|
+
else
|
125
|
+
"🔴 Inactive (#{(time_since_activity / 60).to_i}m ago)"
|
126
|
+
end
|
127
|
+
|
128
|
+
# Update the header area with stats
|
129
|
+
print "\033[2;2H" # Move to line 2
|
130
|
+
room_info = " ROOM: #{@room_id} | #{activity_status} "
|
131
|
+
print room_info.center(50)
|
132
|
+
|
133
|
+
print "\033[4;2H" # Move to line 4
|
134
|
+
stats_info = " Messages: #{@message_count} | Participants: #{@participants.size} "
|
135
|
+
print stats_info.center(50)
|
136
|
+
|
137
|
+
# Move cursor back to bottom (default to 40 lines if console unavailable)
|
138
|
+
max_lines = begin
|
139
|
+
IO.console&.winsize&.first || 40
|
140
|
+
rescue
|
141
|
+
40
|
142
|
+
end
|
143
|
+
print "\033[#{max_lines};1H"
|
144
|
+
end
|
145
|
+
end
|
146
|
+
|
147
|
+
# Main execution
|
148
|
+
if __FILE__ == $0
|
149
|
+
if ARGV.empty?
|
150
|
+
puts "Usage: #{$0} <room_id>"
|
151
|
+
puts "Example: #{$0} general"
|
152
|
+
exit 1
|
153
|
+
end
|
154
|
+
|
155
|
+
room_id = ARGV[0]
|
156
|
+
monitor = RoomMonitor.new(room_id)
|
157
|
+
monitor.run
|
158
|
+
end
|