smart_message 0.0.10 → 0.0.13
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 +4 -4
- data/.github/workflows/deploy-github-pages.yml +38 -0
- data/.gitignore +5 -0
- data/CHANGELOG.md +64 -0
- data/Gemfile.lock +35 -4
- data/README.md +169 -71
- data/Rakefile +29 -4
- data/docs/assets/images/ddq_architecture.svg +130 -0
- data/docs/assets/images/dlq_architecture.svg +115 -0
- data/docs/assets/images/enhanced-dual-publishing.svg +136 -0
- data/docs/assets/images/enhanced-fluent-api.svg +149 -0
- data/docs/assets/images/enhanced-microservices-routing.svg +115 -0
- data/docs/assets/images/enhanced-pattern-matching.svg +107 -0
- data/docs/assets/images/fluent-api-demo.svg +59 -0
- data/docs/assets/images/performance-comparison.svg +161 -0
- data/docs/assets/images/redis-basic-architecture.svg +53 -0
- data/docs/assets/images/redis-enhanced-architecture.svg +88 -0
- data/docs/assets/images/redis-queue-architecture.svg +101 -0
- data/docs/assets/images/smart_message.jpg +0 -0
- data/docs/assets/images/smart_message_walking.jpg +0 -0
- data/docs/assets/images/smartmessage_architecture_overview.svg +173 -0
- data/docs/assets/images/transport-comparison-matrix.svg +171 -0
- data/docs/assets/javascripts/mathjax.js +17 -0
- data/docs/assets/stylesheets/extra.css +51 -0
- data/docs/{addressing.md → core-concepts/addressing.md} +5 -7
- data/docs/{architecture.md → core-concepts/architecture.md} +80 -145
- data/docs/{dispatcher.md → core-concepts/dispatcher.md} +21 -21
- data/docs/{message_filtering.md → core-concepts/message-filtering.md} +2 -3
- data/docs/{message_processing.md → core-concepts/message-processing.md} +17 -17
- data/docs/{troubleshooting.md → development/troubleshooting.md} +7 -7
- data/docs/{examples.md → getting-started/examples.md} +103 -89
- data/docs/{getting-started.md → getting-started/quick-start.md} +47 -23
- data/docs/index.md +64 -0
- data/docs/{dead_letter_queue.md → reference/dead-letter-queue.md} +2 -3
- data/docs/{logging.md → reference/logging.md} +1 -1
- data/docs/{message_deduplication.md → reference/message-deduplication.md} +1 -0
- data/docs/{proc_handlers_summary.md → reference/proc-handlers.md} +7 -6
- data/docs/reference/serializers.md +245 -0
- data/docs/{transports.md → reference/transports.md} +9 -11
- data/docs/transports/memory-transport.md +374 -0
- data/docs/transports/redis-transport-comparison.md +361 -0
- data/docs/transports/redis-transport.md +490 -0
- data/examples/README.md +104 -14
- data/examples/city_scenario/911_emergency_call_flow.svg +99 -0
- data/examples/city_scenario/README.md +515 -0
- data/examples/city_scenario/ai_visitor_intelligence_flow.svg +108 -0
- data/examples/city_scenario/citizen.rb +195 -0
- data/examples/city_scenario/city_diagram.svg +125 -0
- data/examples/city_scenario/common/health_monitor.rb +80 -0
- data/examples/city_scenario/common/logger.rb +30 -0
- data/examples/city_scenario/emergency_dispatch_center.rb +270 -0
- data/examples/city_scenario/fire_department.rb +446 -0
- data/examples/city_scenario/fire_emergency_flow.svg +95 -0
- data/examples/city_scenario/health_department.rb +100 -0
- data/examples/city_scenario/health_monitoring_system.svg +130 -0
- data/examples/city_scenario/house.rb +244 -0
- data/examples/city_scenario/local_bank.rb +217 -0
- data/examples/city_scenario/messages/emergency_911_message.rb +80 -0
- data/examples/city_scenario/messages/emergency_resolved_message.rb +42 -0
- data/examples/city_scenario/messages/fire_dispatch_message.rb +42 -0
- data/examples/city_scenario/messages/fire_emergency_message.rb +44 -0
- data/examples/city_scenario/messages/health_check_message.rb +21 -0
- data/examples/city_scenario/messages/health_status_message.rb +34 -0
- data/examples/city_scenario/messages/police_dispatch_message.rb +45 -0
- data/examples/city_scenario/messages/silent_alarm_message.rb +37 -0
- data/examples/city_scenario/police_department.rb +316 -0
- data/examples/city_scenario/redis_monitor.rb +129 -0
- data/examples/city_scenario/redis_stats.rb +743 -0
- data/examples/city_scenario/room_for_improvement.md +240 -0
- data/examples/city_scenario/security_emergency_flow.svg +95 -0
- data/examples/city_scenario/service_internal_architecture.svg +154 -0
- data/examples/city_scenario/smart_message_ai_agent.rb +364 -0
- data/examples/city_scenario/start_demo.sh +236 -0
- data/examples/city_scenario/stop_demo.sh +106 -0
- data/examples/city_scenario/visitor.rb +631 -0
- data/examples/{10_message_deduplication.rb → memory/01_message_deduplication_demo.rb} +1 -3
- data/examples/{09_dead_letter_queue_demo.rb → memory/02_dead_letter_queue_demo.rb} +10 -40
- data/examples/{01_point_to_point_orders.rb → memory/03_point_to_point_orders.rb} +1 -3
- data/examples/{02_publish_subscribe_events.rb → memory/04_publish_subscribe_events.rb} +1 -2
- data/examples/{03_many_to_many_chat.rb → memory/05_many_to_many_chat.rb} +1 -4
- data/examples/{show_me.rb → memory/06_pretty_print_demo.rb} +1 -1
- data/examples/{05_proc_handlers.rb → memory/07_proc_handlers_demo.rb} +1 -2
- data/examples/{06_custom_logger_example.rb → memory/08_custom_logger_demo.rb} +13 -14
- data/examples/{07_error_handling_scenarios.rb → memory/09_error_handling_demo.rb} +1 -4
- data/examples/{08_entity_addressing_basic.rb → memory/10_entity_addressing_basic.rb} +2 -8
- data/examples/{08_entity_addressing_with_filtering.rb → memory/11_entity_addressing_with_filtering.rb} +2 -6
- data/examples/{09_regex_filtering_microservices.rb → memory/12_regex_filtering_microservices.rb} +1 -2
- data/examples/{10_header_block_configuration.rb → memory/13_header_block_configuration.rb} +1 -6
- data/examples/{11_global_configuration_example.rb → memory/14_global_configuration_demo.rb} +17 -8
- data/examples/{show_logger.rb → memory/15_logger_demo.rb} +1 -2
- data/examples/memory/README.md +163 -0
- data/examples/memory/memory_transport_architecture.svg +90 -0
- data/examples/memory/point_to_point_pattern.svg +94 -0
- data/examples/memory/publish_subscribe_pattern.svg +125 -0
- data/examples/{04_redis_smart_home_iot.rb → redis/01_smart_home_iot_demo.rb} +1 -5
- data/examples/redis/README.md +228 -0
- data/examples/redis/alert_system_flow.svg +127 -0
- data/examples/redis/dashboard_status_flow.svg +107 -0
- data/examples/redis/device_command_flow.svg +113 -0
- data/examples/redis/redis_transport_architecture.svg +115 -0
- data/examples/{smart_home_iot_dataflow.md → redis/smart_home_iot_dataflow.md} +4 -116
- data/examples/redis/smart_home_system_architecture.svg +133 -0
- data/ideas/README.md +41 -0
- data/ideas/agents.md +1001 -0
- data/ideas/database_transport.md +980 -0
- data/ideas/improvement.md +359 -0
- data/ideas/meshage.md +1788 -0
- data/ideas/message_discovery.md +178 -0
- data/ideas/message_schema.md +1381 -0
- data/lib/smart_message/.idea/.gitignore +8 -0
- data/lib/smart_message/.idea/markdown.xml +6 -0
- data/lib/smart_message/.idea/misc.xml +4 -0
- data/lib/smart_message/.idea/modules.xml +8 -0
- data/lib/smart_message/.idea/smart_message.iml +16 -0
- data/lib/smart_message/.idea/vcs.xml +6 -0
- data/lib/smart_message/addressing.rb +15 -0
- data/lib/smart_message/base.rb +19 -12
- data/lib/smart_message/configuration.rb +2 -23
- data/lib/smart_message/dead_letter_queue.rb +1 -1
- data/lib/smart_message/logger.rb +15 -4
- data/lib/smart_message/messaging.rb +3 -62
- data/lib/smart_message/plugins.rb +6 -44
- data/lib/smart_message/serializer.rb +14 -0
- data/lib/smart_message/transport/base.rb +42 -8
- data/lib/smart_message/transport/memory_transport.rb +23 -4
- data/lib/smart_message/transport/redis_transport.rb +11 -0
- data/lib/smart_message/transport/stdout_transport.rb +28 -10
- data/lib/smart_message/transport.rb +33 -1
- data/lib/smart_message/version.rb +1 -1
- data/lib/smart_message.rb +5 -52
- data/mkdocs.yml +184 -0
- data/p2p_plan.md +326 -0
- data/p2p_roadmap.md +287 -0
- data/smart_message.gemspec +2 -0
- data/smart_message.svg +51 -0
- metadata +145 -45
- data/docs/README.md +0 -57
- data/docs/serializers.md +0 -575
- data/examples/dead_letters.jsonl +0 -12
- data/examples/temp.txt +0 -94
- data/examples/tmux_chat/README.md +0 -283
- data/examples/tmux_chat/bot_agent.rb +0 -278
- data/examples/tmux_chat/human_agent.rb +0 -199
- data/examples/tmux_chat/room_monitor.rb +0 -160
- data/examples/tmux_chat/shared_chat_system.rb +0 -328
- data/examples/tmux_chat/start_chat_demo.sh +0 -190
- data/examples/tmux_chat/stop_chat_demo.sh +0 -22
- /data/docs/{properties.md → core-concepts/properties.md} +0 -0
- /data/docs/{ideas_to_think_about.md → development/ideas.md} +0 -0
@@ -1,199 +0,0 @@
|
|
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(wrapper)
|
73
|
-
message_header, message_payload = wrapper.split
|
74
|
-
chat_data = JSON.parse(message_payload)
|
75
|
-
|
76
|
-
# Only process messages from rooms we're in and not our own messages
|
77
|
-
return unless @active_rooms.include?(chat_data['room_id'])
|
78
|
-
return if chat_data['sender_id'] == @agent_id
|
79
|
-
|
80
|
-
sender_emoji = case chat_data['message_type']
|
81
|
-
when 'bot' then '🤖'
|
82
|
-
when 'system' then '🔔'
|
83
|
-
else '👤'
|
84
|
-
end
|
85
|
-
|
86
|
-
log_display("#{sender_emoji} [#{chat_data['room_id']}] #{chat_data['sender_name']}: #{chat_data['content']}")
|
87
|
-
|
88
|
-
# Auto-respond if mentioned
|
89
|
-
if chat_data['mentions']&.include?(@name.downcase) || chat_data['content'].include?("@#{@name}")
|
90
|
-
respond_to_mention(chat_data)
|
91
|
-
end
|
92
|
-
end
|
93
|
-
|
94
|
-
def handle_system_notification(wrapper)
|
95
|
-
message_header, message_payload = wrapper.split
|
96
|
-
notif_data = JSON.parse(message_payload)
|
97
|
-
|
98
|
-
# Only process notifications from rooms we're in
|
99
|
-
return unless @active_rooms.include?(notif_data['room_id'])
|
100
|
-
|
101
|
-
log_display("🔔 [#{notif_data['room_id']}] #{notif_data['content']}")
|
102
|
-
end
|
103
|
-
|
104
|
-
private
|
105
|
-
|
106
|
-
def handle_command(input)
|
107
|
-
parts = input[1..-1].split(' ')
|
108
|
-
command = parts[0]
|
109
|
-
args = parts[1..-1]
|
110
|
-
|
111
|
-
case command
|
112
|
-
when 'join'
|
113
|
-
if args.empty?
|
114
|
-
log_display("❌ Usage: /join <room_id>")
|
115
|
-
else
|
116
|
-
join_room(args[0])
|
117
|
-
update_display_header
|
118
|
-
end
|
119
|
-
when 'leave'
|
120
|
-
if args.empty?
|
121
|
-
log_display("❌ Usage: /leave <room_id>")
|
122
|
-
else
|
123
|
-
leave_room(args[0])
|
124
|
-
update_display_header
|
125
|
-
end
|
126
|
-
when 'list'
|
127
|
-
log_display("📋 Active rooms: #{@active_rooms.empty? ? 'none' : @active_rooms.join(', ')}")
|
128
|
-
when 'quit', 'exit'
|
129
|
-
log_display("👋 Goodbye!")
|
130
|
-
exit(0)
|
131
|
-
when 'help'
|
132
|
-
log_display("📖 Commands:")
|
133
|
-
log_display(" /join <room> - Join a chat room")
|
134
|
-
log_display(" /leave <room> - Leave a chat room")
|
135
|
-
log_display(" /list - List active rooms")
|
136
|
-
log_display(" /quit - Exit the chat")
|
137
|
-
else
|
138
|
-
log_display("❌ Unknown command: /#{command}. Type /help for help.")
|
139
|
-
end
|
140
|
-
end
|
141
|
-
|
142
|
-
def send_to_active_rooms(message)
|
143
|
-
if @active_rooms.empty?
|
144
|
-
log_display("❌ You're not in any rooms. Use /join <room> to join a room.")
|
145
|
-
return
|
146
|
-
end
|
147
|
-
|
148
|
-
@active_rooms.each do |room_id|
|
149
|
-
send_message(room_id: room_id, content: message)
|
150
|
-
end
|
151
|
-
end
|
152
|
-
|
153
|
-
def respond_to_mention(chat_data)
|
154
|
-
responses = [
|
155
|
-
"Thanks for mentioning me!",
|
156
|
-
"I'm here, what's up?",
|
157
|
-
"How can I help?",
|
158
|
-
"Yes, I saw that!",
|
159
|
-
"Interesting point!"
|
160
|
-
]
|
161
|
-
|
162
|
-
# Delay response slightly to make it feel natural
|
163
|
-
Thread.new do
|
164
|
-
sleep(0.5 + rand)
|
165
|
-
send_message(
|
166
|
-
room_id: chat_data['room_id'],
|
167
|
-
content: responses.sample
|
168
|
-
)
|
169
|
-
end
|
170
|
-
end
|
171
|
-
|
172
|
-
def update_display_header
|
173
|
-
# Move cursor to update rooms line
|
174
|
-
print "\033[4;9H" # Move to line 4, column 9
|
175
|
-
print "#{@active_rooms.join(', ').ljust(42)}"
|
176
|
-
# Move cursor to bottom (default to 40 lines if console unavailable)
|
177
|
-
max_lines = begin
|
178
|
-
IO.console&.winsize&.first || 40
|
179
|
-
rescue
|
180
|
-
40
|
181
|
-
end
|
182
|
-
print "\033[#{max_lines};1H"
|
183
|
-
end
|
184
|
-
end
|
185
|
-
|
186
|
-
# Main execution
|
187
|
-
if __FILE__ == $0
|
188
|
-
if ARGV.length < 2
|
189
|
-
puts "Usage: #{$0} <user_id> <name>"
|
190
|
-
puts "Example: #{$0} alice Alice"
|
191
|
-
exit 1
|
192
|
-
end
|
193
|
-
|
194
|
-
user_id = ARGV[0]
|
195
|
-
name = ARGV[1]
|
196
|
-
|
197
|
-
agent = HumanChatAgent.new(user_id: user_id, name: name)
|
198
|
-
agent.start_interactive_session
|
199
|
-
end
|
@@ -1,160 +0,0 @@
|
|
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(wrapper)
|
66
|
-
message_header, message_payload = wrapper.split
|
67
|
-
chat_data = JSON.parse(message_payload)
|
68
|
-
|
69
|
-
return unless chat_data['room_id'] == @room_id
|
70
|
-
return if chat_data['sender_id'] == @agent_id
|
71
|
-
|
72
|
-
@message_count += 1
|
73
|
-
@last_activity = Time.now
|
74
|
-
@participants.add(chat_data['sender_name'])
|
75
|
-
|
76
|
-
sender_emoji = case chat_data['message_type']
|
77
|
-
when 'bot' then '🤖'
|
78
|
-
when 'system' then '🔔'
|
79
|
-
else '👤'
|
80
|
-
end
|
81
|
-
|
82
|
-
# Show the message with metadata
|
83
|
-
timestamp = Time.now.strftime("%H:%M:%S")
|
84
|
-
log_display("#{sender_emoji} [#{timestamp}] #{chat_data['sender_name']}: #{chat_data['content']}")
|
85
|
-
|
86
|
-
# Check for commands
|
87
|
-
if chat_data['content'].start_with?('/')
|
88
|
-
log_display("⚡ Command detected: #{chat_data['content']}")
|
89
|
-
end
|
90
|
-
|
91
|
-
# Check for mentions
|
92
|
-
if chat_data['mentions'] && !chat_data['mentions'].empty?
|
93
|
-
log_display("🏷️ Mentions: #{chat_data['mentions'].join(', ')}")
|
94
|
-
end
|
95
|
-
end
|
96
|
-
|
97
|
-
def handle_system_notification(wrapper)
|
98
|
-
message_header, message_payload = wrapper.split
|
99
|
-
notif_data = JSON.parse(message_payload)
|
100
|
-
|
101
|
-
return unless notif_data['room_id'] == @room_id
|
102
|
-
|
103
|
-
case notif_data['notification_type']
|
104
|
-
when 'user_joined'
|
105
|
-
@user_count += 1
|
106
|
-
log_display("📥 #{notif_data['content']}")
|
107
|
-
when 'user_left'
|
108
|
-
@user_count = [@user_count - 1, 0].max
|
109
|
-
log_display("📤 #{notif_data['content']}")
|
110
|
-
else
|
111
|
-
log_display("🔔 #{notif_data['content']}")
|
112
|
-
end
|
113
|
-
|
114
|
-
@last_activity = Time.now
|
115
|
-
end
|
116
|
-
|
117
|
-
private
|
118
|
-
|
119
|
-
def update_stats_display
|
120
|
-
# Move cursor to update stats
|
121
|
-
time_since_activity = Time.now - @last_activity
|
122
|
-
activity_status = if time_since_activity < 10
|
123
|
-
"🟢 Active"
|
124
|
-
elsif time_since_activity < 60
|
125
|
-
"🟡 Quiet (#{time_since_activity.to_i}s ago)"
|
126
|
-
else
|
127
|
-
"🔴 Inactive (#{(time_since_activity / 60).to_i}m ago)"
|
128
|
-
end
|
129
|
-
|
130
|
-
# Update the header area with stats
|
131
|
-
print "\033[2;2H" # Move to line 2
|
132
|
-
room_info = " ROOM: #{@room_id} | #{activity_status} "
|
133
|
-
print room_info.center(50)
|
134
|
-
|
135
|
-
print "\033[4;2H" # Move to line 4
|
136
|
-
stats_info = " Messages: #{@message_count} | Participants: #{@participants.size} "
|
137
|
-
print stats_info.center(50)
|
138
|
-
|
139
|
-
# Move cursor back to bottom (default to 40 lines if console unavailable)
|
140
|
-
max_lines = begin
|
141
|
-
IO.console&.winsize&.first || 40
|
142
|
-
rescue
|
143
|
-
40
|
144
|
-
end
|
145
|
-
print "\033[#{max_lines};1H"
|
146
|
-
end
|
147
|
-
end
|
148
|
-
|
149
|
-
# Main execution
|
150
|
-
if __FILE__ == $0
|
151
|
-
if ARGV.empty?
|
152
|
-
puts "Usage: #{$0} <room_id>"
|
153
|
-
puts "Example: #{$0} general"
|
154
|
-
exit 1
|
155
|
-
end
|
156
|
-
|
157
|
-
room_id = ARGV[0]
|
158
|
-
monitor = RoomMonitor.new(room_id)
|
159
|
-
monitor.run
|
160
|
-
end
|
@@ -1,328 +0,0 @@
|
|
1
|
-
#!/usr/bin/env ruby
|
2
|
-
# examples/tmux_chat/shared_chat_system.rb
|
3
|
-
#
|
4
|
-
# Shared messaging system for the tmux chat visualization
|
5
|
-
# This provides the core message types and file-based transport for inter-pane communication
|
6
|
-
|
7
|
-
require_relative '../../lib/smart_message'
|
8
|
-
require 'fileutils'
|
9
|
-
|
10
|
-
# Create shared message queues directory
|
11
|
-
SHARED_DIR = '/tmp/smart_message_chat'
|
12
|
-
FileUtils.mkdir_p(SHARED_DIR)
|
13
|
-
|
14
|
-
# File-based transport for tmux communication
|
15
|
-
class FileTransport < SmartMessage::Transport::Base
|
16
|
-
def default_options
|
17
|
-
{
|
18
|
-
queue_dir: SHARED_DIR,
|
19
|
-
poll_interval: 0.1
|
20
|
-
}
|
21
|
-
end
|
22
|
-
|
23
|
-
def configure
|
24
|
-
@queue_dir = @options[:queue_dir]
|
25
|
-
@poll_interval = @options[:poll_interval]
|
26
|
-
@running = false
|
27
|
-
@subscriber_thread = nil
|
28
|
-
|
29
|
-
FileUtils.mkdir_p(@queue_dir)
|
30
|
-
end
|
31
|
-
|
32
|
-
def publish(message_header, message_payload)
|
33
|
-
message_data = {
|
34
|
-
header: message_header.to_h,
|
35
|
-
payload: message_payload,
|
36
|
-
timestamp: Time.now.to_f
|
37
|
-
}
|
38
|
-
|
39
|
-
# Write to room-specific queue file
|
40
|
-
room_id = extract_room_id(message_payload)
|
41
|
-
queue_file = File.join(@queue_dir, "#{room_id}.queue")
|
42
|
-
|
43
|
-
File.open(queue_file, 'a') do |f|
|
44
|
-
f.puts(JSON.generate(message_data))
|
45
|
-
f.flush
|
46
|
-
end
|
47
|
-
end
|
48
|
-
|
49
|
-
def start_subscriber
|
50
|
-
return if @running
|
51
|
-
|
52
|
-
@running = true
|
53
|
-
@subscriber_thread = Thread.new do
|
54
|
-
processed_positions = {}
|
55
|
-
|
56
|
-
while @running
|
57
|
-
Dir.glob(File.join(@queue_dir, "*.queue")).each do |queue_file|
|
58
|
-
room_id = File.basename(queue_file, '.queue')
|
59
|
-
|
60
|
-
if File.exist?(queue_file)
|
61
|
-
lines = File.readlines(queue_file)
|
62
|
-
start_pos = processed_positions[room_id] || 0
|
63
|
-
|
64
|
-
lines[start_pos..-1].each do |line|
|
65
|
-
begin
|
66
|
-
message_data = JSON.parse(line.strip)
|
67
|
-
header = SmartMessage::Header.new(message_data['header'])
|
68
|
-
receive(header, message_data['payload'])
|
69
|
-
rescue JSON::ParserError
|
70
|
-
# Skip malformed lines
|
71
|
-
end
|
72
|
-
end
|
73
|
-
|
74
|
-
processed_positions[room_id] = lines.length
|
75
|
-
end
|
76
|
-
end
|
77
|
-
|
78
|
-
sleep(@poll_interval)
|
79
|
-
end
|
80
|
-
end
|
81
|
-
end
|
82
|
-
|
83
|
-
def stop_subscriber
|
84
|
-
@running = false
|
85
|
-
@subscriber_thread&.join
|
86
|
-
end
|
87
|
-
|
88
|
-
private
|
89
|
-
|
90
|
-
def extract_room_id(payload)
|
91
|
-
begin
|
92
|
-
data = JSON.parse(payload)
|
93
|
-
data['room_id'] || 'global'
|
94
|
-
rescue
|
95
|
-
'global'
|
96
|
-
end
|
97
|
-
end
|
98
|
-
end
|
99
|
-
|
100
|
-
# Register the file transport
|
101
|
-
SmartMessage::Transport.register(:file, FileTransport)
|
102
|
-
|
103
|
-
# Define the Chat Message
|
104
|
-
class ChatMessage < SmartMessage::Base
|
105
|
-
description "Chat messages for tmux-based multi-pane chat demonstration"
|
106
|
-
|
107
|
-
property :message_id,
|
108
|
-
description: "Unique identifier for this chat message"
|
109
|
-
property :room_id,
|
110
|
-
description: "Chat room identifier for message routing"
|
111
|
-
property :sender_id,
|
112
|
-
description: "Unique ID of the user or bot sending the message"
|
113
|
-
property :sender_name,
|
114
|
-
description: "Display name of the message sender"
|
115
|
-
property :content,
|
116
|
-
description: "The actual text content of the chat message"
|
117
|
-
property :message_type,
|
118
|
-
description: "Message type: 'user', 'bot', or 'system'"
|
119
|
-
property :timestamp,
|
120
|
-
description: "ISO8601 timestamp when message was sent"
|
121
|
-
property :mentions,
|
122
|
-
description: "Array of user IDs mentioned in the message"
|
123
|
-
property :metadata,
|
124
|
-
description: "Additional message metadata for tmux display"
|
125
|
-
|
126
|
-
config do
|
127
|
-
transport SmartMessage::Transport.create(:file)
|
128
|
-
serializer SmartMessage::Serializer::JSON.new
|
129
|
-
end
|
130
|
-
|
131
|
-
def self.process(wrapper)
|
132
|
-
message_header, message_payload = wrapper.split
|
133
|
-
# Default processing - agents will override this
|
134
|
-
end
|
135
|
-
end
|
136
|
-
|
137
|
-
# Define Bot Command Message
|
138
|
-
class BotCommandMessage < SmartMessage::Base
|
139
|
-
description "Commands sent to chat bots in the tmux chat system"
|
140
|
-
|
141
|
-
property :command_id,
|
142
|
-
description: "Unique identifier for this bot command"
|
143
|
-
property :room_id,
|
144
|
-
description: "Chat room where the command was issued"
|
145
|
-
property :user_id,
|
146
|
-
description: "User who issued the bot command"
|
147
|
-
property :user_name,
|
148
|
-
description: "Display name of the user issuing the command"
|
149
|
-
property :command,
|
150
|
-
description: "Bot command name (help, joke, weather, etc.)"
|
151
|
-
property :parameters,
|
152
|
-
description: "Array of parameters for the bot command"
|
153
|
-
property :timestamp,
|
154
|
-
description: "ISO8601 timestamp when command was issued"
|
155
|
-
|
156
|
-
config do
|
157
|
-
transport SmartMessage::Transport.create(:file)
|
158
|
-
serializer SmartMessage::Serializer::JSON.new
|
159
|
-
end
|
160
|
-
|
161
|
-
def self.process(wrapper)
|
162
|
-
message_header, message_payload = wrapper.split
|
163
|
-
# Default processing - bots will override this
|
164
|
-
end
|
165
|
-
end
|
166
|
-
|
167
|
-
# Define System Notification Message
|
168
|
-
class SystemNotificationMessage < SmartMessage::Base
|
169
|
-
description "System notifications for tmux chat room events and status updates"
|
170
|
-
|
171
|
-
property :notification_id,
|
172
|
-
description: "Unique identifier for this system notification"
|
173
|
-
property :room_id,
|
174
|
-
description: "Chat room affected by this notification"
|
175
|
-
property :notification_type,
|
176
|
-
description: "Type of notification (user_joined, user_left, etc.)"
|
177
|
-
property :content,
|
178
|
-
description: "Human-readable description of the system event"
|
179
|
-
property :timestamp,
|
180
|
-
description: "ISO8601 timestamp when the event occurred"
|
181
|
-
property :metadata,
|
182
|
-
description: "Additional system event metadata for tmux display"
|
183
|
-
|
184
|
-
config do
|
185
|
-
transport SmartMessage::Transport.create(:file)
|
186
|
-
serializer SmartMessage::Serializer::JSON.new
|
187
|
-
end
|
188
|
-
|
189
|
-
def self.process(wrapper)
|
190
|
-
message_header, message_payload = wrapper.split
|
191
|
-
# Default processing
|
192
|
-
end
|
193
|
-
end
|
194
|
-
|
195
|
-
# Base Agent class with display utilities
|
196
|
-
class BaseAgent
|
197
|
-
attr_reader :agent_id, :name, :active_rooms
|
198
|
-
|
199
|
-
def initialize(agent_id:, name:, agent_type: 'agent')
|
200
|
-
@agent_id = agent_id
|
201
|
-
@name = name
|
202
|
-
@agent_type = agent_type
|
203
|
-
@active_rooms = []
|
204
|
-
@display_buffer = []
|
205
|
-
@transport = SmartMessage::Transport.create(:file)
|
206
|
-
|
207
|
-
setup_display
|
208
|
-
setup_subscriptions
|
209
|
-
start_message_processing
|
210
|
-
end
|
211
|
-
|
212
|
-
def join_room(room_id)
|
213
|
-
return if @active_rooms.include?(room_id)
|
214
|
-
|
215
|
-
@active_rooms << room_id
|
216
|
-
log_display("📥 Joined room: #{room_id}")
|
217
|
-
|
218
|
-
send_system_notification(
|
219
|
-
room_id: room_id,
|
220
|
-
notification_type: 'user_joined',
|
221
|
-
content: "#{@name} joined the room"
|
222
|
-
)
|
223
|
-
end
|
224
|
-
|
225
|
-
def leave_room(room_id)
|
226
|
-
return unless @active_rooms.include?(room_id)
|
227
|
-
|
228
|
-
@active_rooms.delete(room_id)
|
229
|
-
log_display("📤 Left room: #{room_id}")
|
230
|
-
|
231
|
-
send_system_notification(
|
232
|
-
room_id: room_id,
|
233
|
-
notification_type: 'user_left',
|
234
|
-
content: "#{@name} left the room"
|
235
|
-
)
|
236
|
-
end
|
237
|
-
|
238
|
-
def send_message(room_id:, content:, message_type: 'user')
|
239
|
-
return unless @active_rooms.include?(room_id)
|
240
|
-
|
241
|
-
message = ChatMessage.new(
|
242
|
-
message_id: generate_message_id,
|
243
|
-
room_id: room_id,
|
244
|
-
sender_id: @agent_id,
|
245
|
-
sender_name: @name,
|
246
|
-
content: content,
|
247
|
-
message_type: message_type,
|
248
|
-
timestamp: Time.now.iso8601,
|
249
|
-
mentions: extract_mentions(content),
|
250
|
-
metadata: { agent_type: @agent_type },
|
251
|
-
from: @agent_id
|
252
|
-
)
|
253
|
-
|
254
|
-
log_display("💬 [#{room_id}] #{@name}: #{content}")
|
255
|
-
message.publish
|
256
|
-
end
|
257
|
-
|
258
|
-
def shutdown
|
259
|
-
@active_rooms.dup.each { |room_id| leave_room(room_id) }
|
260
|
-
@transport.stop_subscriber
|
261
|
-
log_display("🔴 #{@name} shutdown")
|
262
|
-
end
|
263
|
-
|
264
|
-
protected
|
265
|
-
|
266
|
-
def setup_display
|
267
|
-
puts "\033[2J\033[H" # Clear screen
|
268
|
-
puts "┌─" + "─" * 50 + "┐"
|
269
|
-
puts "│ #{@agent_type.upcase}: #{@name.center(44)} │"
|
270
|
-
puts "├─" + "─" * 50 + "┤"
|
271
|
-
puts "│ Rooms: #{@active_rooms.join(', ').ljust(42)} │"
|
272
|
-
puts "├─" + "─" * 50 + "┤"
|
273
|
-
puts "│ Messages:".ljust(51) + " │"
|
274
|
-
puts "└─" + "─" * 50 + "┘"
|
275
|
-
puts
|
276
|
-
end
|
277
|
-
|
278
|
-
def log_display(message)
|
279
|
-
timestamp = Time.now.strftime("%H:%M:%S")
|
280
|
-
puts "[#{timestamp}] #{message}"
|
281
|
-
end
|
282
|
-
|
283
|
-
def setup_subscriptions
|
284
|
-
# Override in subclasses
|
285
|
-
end
|
286
|
-
|
287
|
-
def start_message_processing
|
288
|
-
@transport.start_subscriber
|
289
|
-
end
|
290
|
-
|
291
|
-
def send_system_notification(room_id:, notification_type:, content:)
|
292
|
-
notification = SystemNotificationMessage.new(
|
293
|
-
notification_id: "NOTIF-#{Time.now.to_i}-#{rand(1000)}",
|
294
|
-
room_id: room_id,
|
295
|
-
notification_type: notification_type,
|
296
|
-
content: content,
|
297
|
-
timestamp: Time.now.iso8601,
|
298
|
-
metadata: { triggered_by: @agent_id },
|
299
|
-
from: @agent_id
|
300
|
-
)
|
301
|
-
|
302
|
-
notification.publish
|
303
|
-
end
|
304
|
-
|
305
|
-
def generate_message_id
|
306
|
-
"MSG-#{@agent_id}-#{Time.now.to_i}-#{rand(1000)}"
|
307
|
-
end
|
308
|
-
|
309
|
-
def extract_mentions(content)
|
310
|
-
content.scan(/@(\w+)/).flatten
|
311
|
-
end
|
312
|
-
end
|
313
|
-
|
314
|
-
# Cleanup utility
|
315
|
-
def cleanup_shared_queues
|
316
|
-
FileUtils.rm_rf(SHARED_DIR) if Dir.exist?(SHARED_DIR)
|
317
|
-
end
|
318
|
-
|
319
|
-
# Signal handling for cleanup
|
320
|
-
trap('INT') do
|
321
|
-
cleanup_shared_queues
|
322
|
-
exit
|
323
|
-
end
|
324
|
-
|
325
|
-
trap('TERM') do
|
326
|
-
cleanup_shared_queues
|
327
|
-
exit
|
328
|
-
end
|