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.
Files changed (53) hide show
  1. checksums.yaml +7 -0
  2. data/.envrc +3 -0
  3. data/.gitignore +8 -0
  4. data/.travis.yml +7 -0
  5. data/CHANGELOG.md +100 -0
  6. data/COMMITS.md +196 -0
  7. data/Gemfile +6 -0
  8. data/Gemfile.lock +71 -0
  9. data/README.md +303 -0
  10. data/Rakefile +10 -0
  11. data/bin/console +14 -0
  12. data/bin/setup +8 -0
  13. data/docs/README.md +52 -0
  14. data/docs/architecture.md +370 -0
  15. data/docs/dispatcher.md +593 -0
  16. data/docs/examples.md +808 -0
  17. data/docs/getting-started.md +235 -0
  18. data/docs/ideas_to_think_about.md +329 -0
  19. data/docs/serializers.md +575 -0
  20. data/docs/transports.md +501 -0
  21. data/docs/troubleshooting.md +582 -0
  22. data/examples/01_point_to_point_orders.rb +200 -0
  23. data/examples/02_publish_subscribe_events.rb +364 -0
  24. data/examples/03_many_to_many_chat.rb +608 -0
  25. data/examples/README.md +335 -0
  26. data/examples/tmux_chat/README.md +283 -0
  27. data/examples/tmux_chat/bot_agent.rb +272 -0
  28. data/examples/tmux_chat/human_agent.rb +197 -0
  29. data/examples/tmux_chat/room_monitor.rb +158 -0
  30. data/examples/tmux_chat/shared_chat_system.rb +295 -0
  31. data/examples/tmux_chat/start_chat_demo.sh +190 -0
  32. data/examples/tmux_chat/stop_chat_demo.sh +22 -0
  33. data/lib/simple_stats.rb +57 -0
  34. data/lib/smart_message/base.rb +284 -0
  35. data/lib/smart_message/dispatcher/.keep +0 -0
  36. data/lib/smart_message/dispatcher.rb +146 -0
  37. data/lib/smart_message/errors.rb +29 -0
  38. data/lib/smart_message/header.rb +20 -0
  39. data/lib/smart_message/logger/base.rb +8 -0
  40. data/lib/smart_message/logger.rb +7 -0
  41. data/lib/smart_message/serializer/base.rb +23 -0
  42. data/lib/smart_message/serializer/json.rb +22 -0
  43. data/lib/smart_message/serializer.rb +10 -0
  44. data/lib/smart_message/transport/base.rb +85 -0
  45. data/lib/smart_message/transport/memory_transport.rb +69 -0
  46. data/lib/smart_message/transport/registry.rb +59 -0
  47. data/lib/smart_message/transport/stdout_transport.rb +62 -0
  48. data/lib/smart_message/transport.rb +41 -0
  49. data/lib/smart_message/version.rb +7 -0
  50. data/lib/smart_message/wrapper.rb +43 -0
  51. data/lib/smart_message.rb +54 -0
  52. data/smart_message.gemspec +53 -0
  53. metadata +252 -0
@@ -0,0 +1,608 @@
1
+ #!/usr/bin/env ruby
2
+ # examples/03_many_to_many_chat.rb
3
+ #
4
+ # Many-to-Many Messaging Example: Distributed Chat System
5
+ #
6
+ # This example demonstrates many-to-many messaging where multiple chat agents
7
+ # can send messages to multiple chat rooms, and other agents receive and respond
8
+ # to messages based on their interests and capabilities.
9
+
10
+ require_relative '../lib/smart_message'
11
+
12
+ puts "=== SmartMessage Example: Many-to-Many Distributed Chat ==="
13
+ puts
14
+
15
+ # Define the Chat Message
16
+ class ChatMessage < SmartMessage::Base
17
+ property :message_id
18
+ property :room_id
19
+ property :sender_id
20
+ property :sender_name
21
+ property :content
22
+ property :message_type # 'user', 'bot', 'system'
23
+ property :timestamp
24
+ property :mentions # Array of user IDs mentioned
25
+ property :metadata
26
+
27
+ config do
28
+ transport SmartMessage::Transport::StdoutTransport.new(loopback: true)
29
+ serializer SmartMessage::Serializer::JSON.new
30
+ end
31
+
32
+ def self.process(message_header, message_payload)
33
+ chat_data = JSON.parse(message_payload)
34
+ puts "šŸ’¬ Chat message in #{chat_data['room_id']}: #{chat_data['content']}"
35
+ end
36
+ end
37
+
38
+ # Define Bot Command Message
39
+ class BotCommandMessage < SmartMessage::Base
40
+ property :command_id
41
+ property :room_id
42
+ property :user_id
43
+ property :command
44
+ property :parameters
45
+ property :timestamp
46
+
47
+ config do
48
+ transport SmartMessage::Transport::StdoutTransport.new(loopback: true)
49
+ serializer SmartMessage::Serializer::JSON.new
50
+ end
51
+
52
+ def self.process(message_header, message_payload)
53
+ command_data = JSON.parse(message_payload)
54
+ puts "šŸ¤– Bot command: #{command_data['command']} in #{command_data['room_id']}"
55
+ end
56
+ end
57
+
58
+ # Define System Notification Message
59
+ class SystemNotificationMessage < SmartMessage::Base
60
+ property :notification_id
61
+ property :room_id
62
+ property :notification_type # 'user_joined', 'user_left', 'room_created'
63
+ property :content
64
+ property :timestamp
65
+ property :metadata
66
+
67
+ config do
68
+ transport SmartMessage::Transport::StdoutTransport.new(loopback: true)
69
+ serializer SmartMessage::Serializer::JSON.new
70
+ end
71
+
72
+ def self.process(message_header, message_payload)
73
+ notif_data = JSON.parse(message_payload)
74
+ puts "šŸ”” System: #{notif_data['content']} in #{notif_data['room_id']}"
75
+ end
76
+ end
77
+
78
+ # Human Chat Agent
79
+ class HumanChatAgent
80
+ attr_reader :user_id, :name, :active_rooms
81
+
82
+ def initialize(user_id:, name:)
83
+ @user_id = user_id
84
+ @name = name
85
+ @active_rooms = []
86
+ @message_counter = 0
87
+
88
+ puts "šŸ‘¤ #{@name} (#{@user_id}): Joining chat system..."
89
+
90
+ # Subscribe to all message types this agent cares about
91
+ ChatMessage.subscribe("HumanChatAgent.handle_chat_message_#{@user_id}")
92
+ SystemNotificationMessage.subscribe("HumanChatAgent.handle_system_notification_#{@user_id}")
93
+ end
94
+
95
+ def join_room(room_id)
96
+ return if @active_rooms.include?(room_id)
97
+
98
+ @active_rooms << room_id
99
+ puts "šŸ‘¤ #{@name}: Joining room #{room_id}"
100
+
101
+ # Send system notification
102
+ send_system_notification(
103
+ room_id: room_id,
104
+ notification_type: 'user_joined',
105
+ content: "#{@name} joined the room"
106
+ )
107
+ end
108
+
109
+ def leave_room(room_id)
110
+ return unless @active_rooms.include?(room_id)
111
+
112
+ @active_rooms.delete(room_id)
113
+ puts "šŸ‘¤ #{@name}: Leaving room #{room_id}"
114
+
115
+ send_system_notification(
116
+ room_id: room_id,
117
+ notification_type: 'user_left',
118
+ content: "#{@name} left the room"
119
+ )
120
+ end
121
+
122
+ def send_message(room_id:, content:, mentions: [])
123
+ return unless @active_rooms.include?(room_id)
124
+
125
+ puts "šŸ‘¤ #{@name}: Sending message to #{room_id}: '#{content}'"
126
+
127
+ # Check if this is a bot command
128
+ if content.start_with?('/')
129
+ send_bot_command(room_id, content)
130
+ else
131
+ message = ChatMessage.new(
132
+ message_id: generate_message_id,
133
+ room_id: room_id,
134
+ sender_id: @user_id,
135
+ sender_name: @name,
136
+ content: content,
137
+ message_type: 'user',
138
+ timestamp: Time.now.iso8601,
139
+ mentions: mentions,
140
+ metadata: { client: 'human_agent' }
141
+ )
142
+
143
+ message.publish
144
+ end
145
+ end
146
+
147
+ # Class method for message handling (required by SmartMessage)
148
+ def self.method_missing(method_name, *args)
149
+ if method_name.to_s.start_with?('handle_chat_message_')
150
+ user_id = method_name.to_s.split('_').last
151
+ agent = @@agents[user_id]
152
+ agent&.handle_chat_message(*args)
153
+ elsif method_name.to_s.start_with?('handle_system_notification_')
154
+ user_id = method_name.to_s.split('_').last
155
+ agent = @@agents[user_id]
156
+ agent&.handle_system_notification(*args)
157
+ else
158
+ super
159
+ end
160
+ end
161
+
162
+ def self.register_agent(agent)
163
+ @@agents ||= {}
164
+ @@agents[agent.user_id] = agent
165
+ end
166
+
167
+ def handle_chat_message(message_header, message_payload)
168
+ chat_data = JSON.parse(message_payload)
169
+
170
+ # Only process messages from rooms we're in and not our own messages
171
+ return unless @active_rooms.include?(chat_data['room_id'])
172
+ return if chat_data['sender_id'] == @user_id
173
+
174
+ puts "šŸ‘¤ #{@name}: Received message in #{chat_data['room_id']}"
175
+
176
+ # Respond if mentioned
177
+ if chat_data['mentions']&.include?(@user_id)
178
+ respond_to_mention(chat_data)
179
+ end
180
+ end
181
+
182
+ def handle_system_notification(message_header, message_payload)
183
+ notif_data = JSON.parse(message_payload)
184
+
185
+ # Only process notifications from rooms we're in
186
+ return unless @active_rooms.include?(notif_data['room_id'])
187
+
188
+ puts "šŸ‘¤ #{@name}: Received system notification in #{notif_data['room_id']}"
189
+ end
190
+
191
+ private
192
+
193
+ def send_bot_command(room_id, content)
194
+ command_parts = content[1..-1].split(' ')
195
+ command = command_parts.first
196
+ parameters = command_parts[1..-1]
197
+
198
+ bot_command = BotCommandMessage.new(
199
+ command_id: "CMD-#{Time.now.to_i}-#{rand(1000)}",
200
+ room_id: room_id,
201
+ user_id: @user_id,
202
+ command: command,
203
+ parameters: parameters,
204
+ timestamp: Time.now.iso8601
205
+ )
206
+
207
+ bot_command.publish
208
+ end
209
+
210
+ def respond_to_mention(chat_data)
211
+ # Simulate thinking time
212
+ sleep(0.2)
213
+
214
+ responses = [
215
+ "Thanks for mentioning me!",
216
+ "I'm here, what's up?",
217
+ "How can I help?",
218
+ "Yes, I saw that!",
219
+ "Interesting point!"
220
+ ]
221
+
222
+ send_message(
223
+ room_id: chat_data['room_id'],
224
+ content: responses.sample
225
+ )
226
+ end
227
+
228
+ def send_system_notification(room_id:, notification_type:, content:)
229
+ notification = SystemNotificationMessage.new(
230
+ notification_id: "NOTIF-#{Time.now.to_i}-#{rand(1000)}",
231
+ room_id: room_id,
232
+ notification_type: notification_type,
233
+ content: content,
234
+ timestamp: Time.now.iso8601,
235
+ metadata: { triggered_by: @user_id }
236
+ )
237
+
238
+ notification.publish
239
+ end
240
+
241
+ def generate_message_id
242
+ @message_counter += 1
243
+ "MSG-#{@user_id}-#{@message_counter}"
244
+ end
245
+ end
246
+
247
+ # Bot Agent - Responds to commands and provides services
248
+ class BotAgent
249
+ def initialize(bot_id:, name:, capabilities: [])
250
+ @bot_id = bot_id
251
+ @name = name
252
+ @capabilities = capabilities
253
+ @active_rooms = []
254
+
255
+ puts "šŸ¤– #{@name} (#{@bot_id}): Starting bot with capabilities: #{@capabilities.join(', ')}"
256
+
257
+ # Subscribe to bot commands and chat messages
258
+ BotCommandMessage.subscribe('BotAgent.handle_bot_command')
259
+ ChatMessage.subscribe('BotAgent.handle_chat_message')
260
+ end
261
+
262
+ def join_room(room_id)
263
+ return if @active_rooms.include?(room_id)
264
+
265
+ @active_rooms << room_id
266
+ puts "šŸ¤– #{@name}: Joining room #{room_id}"
267
+
268
+ # Announce capabilities
269
+ send_chat_message(
270
+ room_id: room_id,
271
+ content: "šŸ¤– Hello! I'm #{@name}. Available commands: #{@capabilities.map { |c| "/#{c}" }.join(', ')}"
272
+ )
273
+ end
274
+
275
+ def self.handle_bot_command(message_header, message_payload)
276
+ command_data = JSON.parse(message_payload)
277
+ @@bots ||= []
278
+
279
+ # Find bot that can handle this command and is in the room
280
+ capable_bot = @@bots.find do |bot|
281
+ bot.can_handle_command?(command_data['command']) &&
282
+ bot.in_room?(command_data['room_id'])
283
+ end
284
+
285
+ capable_bot&.process_command(command_data)
286
+ end
287
+
288
+ def self.handle_chat_message(message_header, message_payload)
289
+ chat_data = JSON.parse(message_payload)
290
+ @@bots ||= []
291
+
292
+ # Let all bots in the room process the message
293
+ @@bots.each do |bot|
294
+ bot.process_chat_message(chat_data) if bot.in_room?(chat_data['room_id'])
295
+ end
296
+ end
297
+
298
+ def self.register_bot(bot)
299
+ @@bots ||= []
300
+ @@bots << bot
301
+ end
302
+
303
+ def can_handle_command?(command)
304
+ @capabilities.include?(command)
305
+ end
306
+
307
+ def in_room?(room_id)
308
+ @active_rooms.include?(room_id)
309
+ end
310
+
311
+ def process_command(command_data)
312
+ puts "šŸ¤– #{@name}: Processing command /#{command_data['command']}"
313
+
314
+ case command_data['command']
315
+ when 'weather'
316
+ handle_weather_command(command_data)
317
+ when 'joke'
318
+ handle_joke_command(command_data)
319
+ when 'help'
320
+ handle_help_command(command_data)
321
+ when 'stats'
322
+ handle_stats_command(command_data)
323
+ else
324
+ send_chat_message(
325
+ room_id: command_data['room_id'],
326
+ content: "Sorry, I don't know how to handle /#{command_data['command']}"
327
+ )
328
+ end
329
+ end
330
+
331
+ def process_chat_message(chat_data)
332
+ # Bot can respond to certain keywords or patterns
333
+ content = chat_data['content'].downcase
334
+
335
+ if content.include?('hello') || content.include?('hi')
336
+ send_chat_message(
337
+ room_id: chat_data['room_id'],
338
+ content: "Hello #{chat_data['sender_name']}! šŸ‘‹"
339
+ )
340
+ elsif content.include?('help') && !content.start_with?('/')
341
+ send_chat_message(
342
+ room_id: chat_data['room_id'],
343
+ content: "Type /help to see available commands!"
344
+ )
345
+ end
346
+ end
347
+
348
+ private
349
+
350
+ def handle_weather_command(command_data)
351
+ location = command_data['parameters'].first || 'your location'
352
+ weather_responses = [
353
+ "ā˜€ļø It's sunny in #{location}!",
354
+ "šŸŒ§ļø Looks like rain in #{location}",
355
+ "ā„ļø Snow expected in #{location}",
356
+ "ā›… Partly cloudy in #{location}"
357
+ ]
358
+
359
+ send_chat_message(
360
+ room_id: command_data['room_id'],
361
+ content: weather_responses.sample
362
+ )
363
+ end
364
+
365
+ def handle_joke_command(command_data)
366
+ jokes = [
367
+ "Why don't scientists trust atoms? Because they make up everything!",
368
+ "Why did the scarecrow win an award? He was outstanding in his field!",
369
+ "What do you call a fake noodle? An impasta!",
370
+ "Why don't eggs tell jokes? They'd crack each other up!"
371
+ ]
372
+
373
+ send_chat_message(
374
+ room_id: command_data['room_id'],
375
+ content: "šŸ˜„ #{jokes.sample}"
376
+ )
377
+ end
378
+
379
+ def handle_help_command(command_data)
380
+ help_text = @capabilities.map { |cmd| "/#{cmd} - #{get_command_description(cmd)}" }.join("\n")
381
+
382
+ send_chat_message(
383
+ room_id: command_data['room_id'],
384
+ content: "šŸ¤– Available commands:\n#{help_text}"
385
+ )
386
+ end
387
+
388
+ def handle_stats_command(command_data)
389
+ send_chat_message(
390
+ room_id: command_data['room_id'],
391
+ content: "šŸ“Š Bot Stats: Active in #{@active_rooms.length} rooms, #{@capabilities.length} capabilities"
392
+ )
393
+ end
394
+
395
+ def get_command_description(command)
396
+ descriptions = {
397
+ 'weather' => 'Get weather information',
398
+ 'joke' => 'Tell a random joke',
399
+ 'help' => 'Show this help message',
400
+ 'stats' => 'Show bot statistics'
401
+ }
402
+
403
+ descriptions[command] || 'No description available'
404
+ end
405
+
406
+ def send_chat_message(room_id:, content:)
407
+ message = ChatMessage.new(
408
+ message_id: "BOT-MSG-#{Time.now.to_i}-#{rand(1000)}",
409
+ room_id: room_id,
410
+ sender_id: @bot_id,
411
+ sender_name: @name,
412
+ content: content,
413
+ message_type: 'bot',
414
+ timestamp: Time.now.iso8601,
415
+ mentions: [],
416
+ metadata: { bot_type: 'service_bot' }
417
+ )
418
+
419
+ message.publish
420
+ end
421
+ end
422
+
423
+ # Room Manager - Manages chat rooms and routing
424
+ class RoomManager
425
+ def initialize
426
+ puts "šŸ¢ RoomManager: Starting up..."
427
+ @rooms = {}
428
+
429
+ # Subscribe to system notifications to track room membership
430
+ SystemNotificationMessage.subscribe('RoomManager.handle_system_notification')
431
+ end
432
+
433
+ def create_room(room_id:, name:, description: '')
434
+ return if @rooms.key?(room_id)
435
+
436
+ @rooms[room_id] = {
437
+ name: name,
438
+ description: description,
439
+ created_at: Time.now,
440
+ members: []
441
+ }
442
+
443
+ puts "šŸ¢ RoomManager: Created room #{room_id} (#{name})"
444
+
445
+ # Send system notification
446
+ notification = SystemNotificationMessage.new(
447
+ notification_id: "ROOM-#{Time.now.to_i}",
448
+ room_id: room_id,
449
+ notification_type: 'room_created',
450
+ content: "Room '#{name}' was created",
451
+ timestamp: Time.now.iso8601,
452
+ metadata: { description: description }
453
+ )
454
+
455
+ notification.publish
456
+ end
457
+
458
+ def self.handle_system_notification(message_header, message_payload)
459
+ @@instance ||= new
460
+ @@instance.process_system_notification(message_header, message_payload)
461
+ end
462
+
463
+ def process_system_notification(message_header, message_payload)
464
+ notif_data = JSON.parse(message_payload)
465
+ room_id = notif_data['room_id']
466
+
467
+ return unless @rooms.key?(room_id)
468
+
469
+ case notif_data['notification_type']
470
+ when 'user_joined'
471
+ # Could track membership if needed
472
+ puts "šŸ¢ RoomManager: Noted user joined #{room_id}"
473
+ when 'user_left'
474
+ puts "šŸ¢ RoomManager: Noted user left #{room_id}"
475
+ end
476
+ end
477
+
478
+ def list_rooms
479
+ @rooms
480
+ end
481
+ end
482
+
483
+ # Demo Runner
484
+ class DistributedChatDemo
485
+ def run
486
+ puts "šŸš€ Starting Distributed Chat Demo\n"
487
+
488
+ # Start room manager
489
+ room_manager = RoomManager.new
490
+
491
+ # Create some chat rooms
492
+ room_manager.create_room(room_id: 'general', name: 'General Chat', description: 'Main discussion room')
493
+ room_manager.create_room(room_id: 'tech', name: 'Tech Talk', description: 'Technology discussions')
494
+ room_manager.create_room(room_id: 'random', name: 'Random', description: 'Off-topic conversations')
495
+
496
+ sleep(0.5)
497
+
498
+ # Create human agents
499
+ alice = HumanChatAgent.new(user_id: 'user-001', name: 'Alice')
500
+ bob = HumanChatAgent.new(user_id: 'user-002', name: 'Bob')
501
+ carol = HumanChatAgent.new(user_id: 'user-003', name: 'Carol')
502
+
503
+ # Register agents for message routing
504
+ HumanChatAgent.register_agent(alice)
505
+ HumanChatAgent.register_agent(bob)
506
+ HumanChatAgent.register_agent(carol)
507
+
508
+ # Create bot agents
509
+ helper_bot = BotAgent.new(
510
+ bot_id: 'bot-001',
511
+ name: 'HelperBot',
512
+ capabilities: ['help', 'stats']
513
+ )
514
+
515
+ fun_bot = BotAgent.new(
516
+ bot_id: 'bot-002',
517
+ name: 'FunBot',
518
+ capabilities: ['joke', 'weather']
519
+ )
520
+
521
+ # Register bots
522
+ BotAgent.register_bot(helper_bot)
523
+ BotAgent.register_bot(fun_bot)
524
+
525
+ sleep(0.5)
526
+
527
+ puts "\n" + "="*80
528
+ puts "Chat Simulation Starting"
529
+ puts "="*80
530
+
531
+ # Users join rooms
532
+ alice.join_room('general')
533
+ alice.join_room('tech')
534
+ sleep(0.3)
535
+
536
+ bob.join_room('general')
537
+ bob.join_room('random')
538
+ sleep(0.3)
539
+
540
+ carol.join_room('tech')
541
+ carol.join_room('random')
542
+ sleep(0.3)
543
+
544
+ # Bots join rooms
545
+ helper_bot.join_room('general')
546
+ helper_bot.join_room('tech')
547
+ sleep(0.3)
548
+
549
+ fun_bot.join_room('general')
550
+ fun_bot.join_room('random')
551
+ sleep(0.5)
552
+
553
+ # Simulate conversation
554
+ puts "\n--- Conversation in General ---"
555
+ alice.send_message(room_id: 'general', content: 'Hello everyone!')
556
+ sleep(0.5)
557
+
558
+ bob.send_message(room_id: 'general', content: 'Hi Alice! How are you?')
559
+ sleep(0.5)
560
+
561
+ alice.send_message(room_id: 'general', content: '/help')
562
+ sleep(0.8)
563
+
564
+ bob.send_message(room_id: 'general', content: '/joke')
565
+ sleep(0.8)
566
+
567
+ puts "\n--- Conversation in Tech ---"
568
+ alice.send_message(room_id: 'tech', content: 'Anyone working on Ruby today?')
569
+ sleep(0.5)
570
+
571
+ carol.send_message(room_id: 'tech', content: 'Yes! Working on messaging systems')
572
+ sleep(0.5)
573
+
574
+ alice.send_message(room_id: 'tech', content: 'Cool! @user-003 what kind of messaging?', mentions: ['user-003'])
575
+ sleep(0.8)
576
+
577
+ puts "\n--- Conversation in Random ---"
578
+ bob.send_message(room_id: 'random', content: '/weather New York')
579
+ sleep(0.8)
580
+
581
+ carol.send_message(room_id: 'random', content: 'Hello fun bot!')
582
+ sleep(0.5)
583
+
584
+ # Some users leave
585
+ puts "\n--- Users Leaving ---"
586
+ alice.leave_room('tech')
587
+ sleep(0.3)
588
+
589
+ bob.leave_room('random')
590
+ sleep(0.5)
591
+
592
+ puts "\n✨ Demo completed!"
593
+ puts "\nThis example demonstrated:"
594
+ puts "• Many-to-many messaging between multiple agents"
595
+ puts "• Different types of agents (humans, bots) in the same system"
596
+ puts "• Room-based message routing and filtering"
597
+ puts "• Multiple message types (chat, commands, notifications)"
598
+ puts "• Dynamic subscription and unsubscription"
599
+ puts "• Event-driven responses and interactions"
600
+ puts "• Service discovery and capability advertisement"
601
+ end
602
+ end
603
+
604
+ # Run the demo if this file is executed directly
605
+ if __FILE__ == $0
606
+ demo = DistributedChatDemo.new
607
+ demo.run
608
+ end