smart_message 0.0.10 → 0.0.12

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 (169) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/deploy-github-pages.yml +38 -0
  3. data/.gitignore +5 -0
  4. data/CHANGELOG.md +30 -0
  5. data/Gemfile.lock +35 -4
  6. data/README.md +169 -71
  7. data/Rakefile +29 -4
  8. data/docs/assets/images/ddq_architecture.svg +130 -0
  9. data/docs/assets/images/dlq_architecture.svg +115 -0
  10. data/docs/assets/images/enhanced-dual-publishing.svg +136 -0
  11. data/docs/assets/images/enhanced-fluent-api.svg +149 -0
  12. data/docs/assets/images/enhanced-microservices-routing.svg +115 -0
  13. data/docs/assets/images/enhanced-pattern-matching.svg +107 -0
  14. data/docs/assets/images/fluent-api-demo.svg +59 -0
  15. data/docs/assets/images/performance-comparison.svg +161 -0
  16. data/docs/assets/images/redis-basic-architecture.svg +53 -0
  17. data/docs/assets/images/redis-enhanced-architecture.svg +88 -0
  18. data/docs/assets/images/redis-queue-architecture.svg +101 -0
  19. data/docs/assets/images/smart_message.jpg +0 -0
  20. data/docs/assets/images/smart_message_walking.jpg +0 -0
  21. data/docs/assets/images/smartmessage_architecture_overview.svg +173 -0
  22. data/docs/assets/images/transport-comparison-matrix.svg +171 -0
  23. data/docs/assets/javascripts/mathjax.js +17 -0
  24. data/docs/assets/stylesheets/extra.css +51 -0
  25. data/docs/{addressing.md → core-concepts/addressing.md} +5 -7
  26. data/docs/{architecture.md → core-concepts/architecture.md} +78 -138
  27. data/docs/{dispatcher.md → core-concepts/dispatcher.md} +21 -21
  28. data/docs/{message_filtering.md → core-concepts/message-filtering.md} +2 -3
  29. data/docs/{message_processing.md → core-concepts/message-processing.md} +17 -17
  30. data/docs/{troubleshooting.md → development/troubleshooting.md} +7 -7
  31. data/docs/{examples.md → getting-started/examples.md} +115 -89
  32. data/docs/{getting-started.md → getting-started/quick-start.md} +47 -18
  33. data/docs/guides/redis-queue-getting-started.md +697 -0
  34. data/docs/guides/redis-queue-patterns.md +889 -0
  35. data/docs/guides/redis-queue-production.md +1091 -0
  36. data/docs/index.md +64 -0
  37. data/docs/{dead_letter_queue.md → reference/dead-letter-queue.md} +2 -3
  38. data/docs/{logging.md → reference/logging.md} +1 -1
  39. data/docs/{message_deduplication.md → reference/message-deduplication.md} +1 -0
  40. data/docs/{proc_handlers_summary.md → reference/proc-handlers.md} +7 -6
  41. data/docs/{serializers.md → reference/serializers.md} +3 -5
  42. data/docs/{transports.md → reference/transports.md} +133 -11
  43. data/docs/transports/memory-transport.md +374 -0
  44. data/docs/transports/redis-enhanced-transport.md +524 -0
  45. data/docs/transports/redis-queue-transport.md +1304 -0
  46. data/docs/transports/redis-transport-comparison.md +496 -0
  47. data/docs/transports/redis-transport.md +509 -0
  48. data/examples/README.md +98 -5
  49. data/examples/city_scenario/911_emergency_call_flow.svg +99 -0
  50. data/examples/city_scenario/README.md +515 -0
  51. data/examples/city_scenario/ai_visitor_intelligence_flow.svg +108 -0
  52. data/examples/city_scenario/citizen.rb +195 -0
  53. data/examples/city_scenario/city_diagram.svg +125 -0
  54. data/examples/city_scenario/common/health_monitor.rb +80 -0
  55. data/examples/city_scenario/common/logger.rb +30 -0
  56. data/examples/city_scenario/emergency_dispatch_center.rb +270 -0
  57. data/examples/city_scenario/fire_department.rb +446 -0
  58. data/examples/city_scenario/fire_emergency_flow.svg +95 -0
  59. data/examples/city_scenario/health_department.rb +100 -0
  60. data/examples/city_scenario/health_monitoring_system.svg +130 -0
  61. data/examples/city_scenario/house.rb +244 -0
  62. data/examples/city_scenario/local_bank.rb +217 -0
  63. data/examples/city_scenario/messages/emergency_911_message.rb +81 -0
  64. data/examples/city_scenario/messages/emergency_resolved_message.rb +43 -0
  65. data/examples/city_scenario/messages/fire_dispatch_message.rb +43 -0
  66. data/examples/city_scenario/messages/fire_emergency_message.rb +45 -0
  67. data/examples/city_scenario/messages/health_check_message.rb +22 -0
  68. data/examples/city_scenario/messages/health_status_message.rb +35 -0
  69. data/examples/city_scenario/messages/police_dispatch_message.rb +46 -0
  70. data/examples/city_scenario/messages/silent_alarm_message.rb +38 -0
  71. data/examples/city_scenario/police_department.rb +316 -0
  72. data/examples/city_scenario/redis_monitor.rb +129 -0
  73. data/examples/city_scenario/redis_stats.rb +743 -0
  74. data/examples/city_scenario/room_for_improvement.md +240 -0
  75. data/examples/city_scenario/security_emergency_flow.svg +95 -0
  76. data/examples/city_scenario/service_internal_architecture.svg +154 -0
  77. data/examples/city_scenario/smart_message_ai_agent.rb +364 -0
  78. data/examples/city_scenario/start_demo.sh +236 -0
  79. data/examples/city_scenario/stop_demo.sh +106 -0
  80. data/examples/city_scenario/visitor.rb +631 -0
  81. data/examples/{10_message_deduplication.rb → memory/01_message_deduplication_demo.rb} +1 -1
  82. data/examples/{09_dead_letter_queue_demo.rb → memory/02_dead_letter_queue_demo.rb} +13 -40
  83. data/examples/{01_point_to_point_orders.rb → memory/03_point_to_point_orders.rb} +1 -1
  84. data/examples/{02_publish_subscribe_events.rb → memory/04_publish_subscribe_events.rb} +2 -2
  85. data/examples/{03_many_to_many_chat.rb → memory/05_many_to_many_chat.rb} +4 -4
  86. data/examples/{show_me.rb → memory/06_pretty_print_demo.rb} +1 -1
  87. data/examples/{05_proc_handlers.rb → memory/07_proc_handlers_demo.rb} +2 -2
  88. data/examples/{06_custom_logger_example.rb → memory/08_custom_logger_demo.rb} +17 -14
  89. data/examples/{07_error_handling_scenarios.rb → memory/09_error_handling_demo.rb} +4 -4
  90. data/examples/{08_entity_addressing_basic.rb → memory/10_entity_addressing_basic.rb} +8 -8
  91. data/examples/{08_entity_addressing_with_filtering.rb → memory/11_entity_addressing_with_filtering.rb} +6 -6
  92. data/examples/{09_regex_filtering_microservices.rb → memory/12_regex_filtering_microservices.rb} +2 -2
  93. data/examples/{10_header_block_configuration.rb → memory/13_header_block_configuration.rb} +6 -6
  94. data/examples/{11_global_configuration_example.rb → memory/14_global_configuration_demo.rb} +19 -8
  95. data/examples/{show_logger.rb → memory/15_logger_demo.rb} +1 -1
  96. data/examples/memory/README.md +163 -0
  97. data/examples/memory/memory_transport_architecture.svg +90 -0
  98. data/examples/memory/point_to_point_pattern.svg +94 -0
  99. data/examples/memory/publish_subscribe_pattern.svg +125 -0
  100. data/examples/{04_redis_smart_home_iot.rb → redis/01_smart_home_iot_demo.rb} +5 -5
  101. data/examples/redis/README.md +230 -0
  102. data/examples/redis/alert_system_flow.svg +127 -0
  103. data/examples/redis/dashboard_status_flow.svg +107 -0
  104. data/examples/redis/device_command_flow.svg +113 -0
  105. data/examples/redis/redis_transport_architecture.svg +115 -0
  106. data/examples/{smart_home_iot_dataflow.md → redis/smart_home_iot_dataflow.md} +4 -116
  107. data/examples/redis/smart_home_system_architecture.svg +133 -0
  108. data/examples/redis_enhanced/README.md +319 -0
  109. data/examples/redis_enhanced/enhanced_01_basic_patterns.rb +233 -0
  110. data/examples/redis_enhanced/enhanced_02_fluent_api.rb +331 -0
  111. data/examples/redis_enhanced/enhanced_03_dual_publishing.rb +281 -0
  112. data/examples/redis_enhanced/enhanced_04_advanced_routing.rb +419 -0
  113. data/examples/redis_queue/01_basic_messaging.rb +221 -0
  114. data/examples/redis_queue/01_comprehensive_examples.rb +508 -0
  115. data/examples/redis_queue/02_pattern_routing.rb +405 -0
  116. data/examples/redis_queue/03_fluent_api.rb +422 -0
  117. data/examples/redis_queue/04_load_balancing.rb +486 -0
  118. data/examples/redis_queue/05_microservices.rb +735 -0
  119. data/examples/redis_queue/06_emergency_alerts.rb +777 -0
  120. data/examples/redis_queue/07_queue_management.rb +587 -0
  121. data/examples/redis_queue/README.md +366 -0
  122. data/examples/redis_queue/enhanced_01_basic_patterns.rb +233 -0
  123. data/examples/redis_queue/enhanced_02_fluent_api.rb +331 -0
  124. data/examples/redis_queue/enhanced_03_dual_publishing.rb +281 -0
  125. data/examples/redis_queue/enhanced_04_advanced_routing.rb +419 -0
  126. data/examples/redis_queue/redis_queue_architecture.svg +148 -0
  127. data/ideas/README.md +41 -0
  128. data/ideas/agents.md +1001 -0
  129. data/ideas/database_transport.md +980 -0
  130. data/ideas/improvement.md +359 -0
  131. data/ideas/meshage.md +1788 -0
  132. data/ideas/message_discovery.md +178 -0
  133. data/ideas/message_schema.md +1381 -0
  134. data/lib/smart_message/.idea/.gitignore +8 -0
  135. data/lib/smart_message/.idea/markdown.xml +6 -0
  136. data/lib/smart_message/.idea/misc.xml +4 -0
  137. data/lib/smart_message/.idea/modules.xml +8 -0
  138. data/lib/smart_message/.idea/smart_message.iml +16 -0
  139. data/lib/smart_message/.idea/vcs.xml +6 -0
  140. data/lib/smart_message/addressing.rb +15 -0
  141. data/lib/smart_message/base.rb +0 -2
  142. data/lib/smart_message/configuration.rb +1 -1
  143. data/lib/smart_message/logger.rb +15 -4
  144. data/lib/smart_message/plugins.rb +5 -2
  145. data/lib/smart_message/serializer.rb +14 -0
  146. data/lib/smart_message/transport/redis_enhanced_transport.rb +399 -0
  147. data/lib/smart_message/transport/redis_queue_transport.rb +555 -0
  148. data/lib/smart_message/transport/registry.rb +1 -0
  149. data/lib/smart_message/transport.rb +34 -1
  150. data/lib/smart_message/version.rb +1 -1
  151. data/lib/smart_message.rb +5 -52
  152. data/mkdocs.yml +184 -0
  153. data/p2p_plan.md +326 -0
  154. data/p2p_roadmap.md +287 -0
  155. data/smart_message.gemspec +2 -0
  156. data/smart_message.svg +51 -0
  157. metadata +170 -44
  158. data/docs/README.md +0 -57
  159. data/examples/dead_letters.jsonl +0 -12
  160. data/examples/temp.txt +0 -94
  161. data/examples/tmux_chat/README.md +0 -283
  162. data/examples/tmux_chat/bot_agent.rb +0 -278
  163. data/examples/tmux_chat/human_agent.rb +0 -199
  164. data/examples/tmux_chat/room_monitor.rb +0 -160
  165. data/examples/tmux_chat/shared_chat_system.rb +0 -328
  166. data/examples/tmux_chat/start_chat_demo.sh +0 -190
  167. data/examples/tmux_chat/stop_chat_demo.sh +0 -22
  168. /data/docs/{properties.md → core-concepts/properties.md} +0 -0
  169. /data/docs/{ideas_to_think_about.md → development/ideas.md} +0 -0
@@ -0,0 +1,364 @@
1
+ #!/usr/bin/env ruby
2
+ # smart_message_ai_agent.rb
3
+ #
4
+ # Abstract base class for building AI agents that can dynamically discover,
5
+ # select, and use SmartMessage classes for communication based on context.
6
+ #
7
+ # This pattern enables AI agents to:
8
+ # 1. Discover available message types through introspection
9
+ # 2. Use LLM to select appropriate messages for scenarios
10
+ # 3. Generate valid message property values using AI
11
+ # 4. Handle validation errors with AI-assisted correction
12
+ # 5. Publish messages through SmartMessage transport
13
+
14
+ require 'ruby_llm'
15
+ require 'json'
16
+ require 'securerandom'
17
+
18
+ class SmartMessageAIAgent
19
+ attr_reader :service_name, :llm, :ai_available, :logger
20
+
21
+ def initialize(service_name, logger = nil)
22
+ @service_name = service_name
23
+ @logger = logger || default_logger
24
+ @message_cache = {}
25
+ @retry_attempts = 3
26
+
27
+ setup_ai
28
+ logger.info("#{self.class} initialized as '#{@service_name}' - AI-powered messaging agent ready")
29
+ end
30
+
31
+ # Override this method to configure RubyLLM with your API keys
32
+ def configure_rubyllm
33
+ RubyLLM.configure do |config|
34
+ config.anthropic_api_key = ENV.fetch('ANTHROPIC_API_KEY', nil)
35
+ config.openai_api_key = ENV.fetch('OPENAI_API_KEY', nil)
36
+ # Add other provider configurations as needed
37
+
38
+ config.log_file = "log/#{@service_name}_llm.log"
39
+ config.log_level = :info
40
+ end
41
+ end
42
+
43
+ # Override this to specify which module contains your message classes
44
+ # Default: Messages
45
+ def message_module
46
+ Messages
47
+ end
48
+
49
+ # Override this to filter which message classes are available to the agent
50
+ # Return true to include the message class, false to exclude
51
+ def include_message_class?(message_class)
52
+ true
53
+ end
54
+
55
+ # Override this to provide scenario-specific context for message selection
56
+ def build_selection_context(scenario)
57
+ "An AI agent needs to send a message for this scenario: #{scenario[:description]}"
58
+ end
59
+
60
+ # Override this to provide scenario-specific context for property generation
61
+ def build_property_context(scenario, message_class)
62
+ "Generate realistic values for a #{message_class} message based on: #{scenario[:description]}"
63
+ end
64
+
65
+ # Override this to provide fallback values for specific message types
66
+ def generate_fallback_values(message_class, scenario = nil)
67
+ {
68
+ 'timestamp' => Time.now.iso8601,
69
+ 'from' => @service_name,
70
+ 'details' => "Generated by #{@service_name} agent"
71
+ }
72
+ end
73
+
74
+ # Core method to send a message based on a scenario
75
+ def send_message_for_scenario(scenario)
76
+ logger.info("Processing scenario: #{scenario[:type]}")
77
+
78
+ # Step 1: Discover available messages
79
+ available_messages = discover_message_types
80
+
81
+ # Step 2: Select appropriate message type
82
+ selected_class = select_message_type(available_messages, scenario)
83
+ return nil unless selected_class
84
+
85
+ # Step 3: Generate and publish message with retry logic
86
+ publish_with_retry(selected_class, scenario)
87
+ end
88
+
89
+ # Discover all available SmartMessage classes
90
+ def discover_message_types
91
+ return @message_cache if @message_cache.any?
92
+
93
+ logger.info("Discovering message types from #{message_module}")
94
+
95
+ message_module.constants.each do |const_name|
96
+ const = message_module.const_get(const_name)
97
+
98
+ if const.is_a?(Class) && const < SmartMessage::Base && include_message_class?(const)
99
+ description = const.respond_to?(:description) ? const.description : "No description available"
100
+ @message_cache[const_name.to_s] = {
101
+ class: const,
102
+ description: description
103
+ }
104
+ logger.debug("Discovered: #{const_name} - #{description[0..100]}")
105
+ end
106
+ end
107
+
108
+ logger.info("Discovered #{@message_cache.size} message types")
109
+ @message_cache
110
+ end
111
+
112
+ # Use AI to select the most appropriate message type
113
+ def select_message_type(available_messages, scenario)
114
+ logger.info("Selecting message type for scenario: #{scenario[:type]}")
115
+
116
+ if @ai_available
117
+ selected = ai_select_message(available_messages, scenario)
118
+ return selected if selected
119
+ end
120
+
121
+ # Fallback selection logic
122
+ fallback_select_message(available_messages, scenario)
123
+ end
124
+
125
+ # Generate message instance with AI-provided or fallback values
126
+ def generate_message_instance(message_class, scenario, validation_errors = [])
127
+ properties = collect_property_metadata(message_class)
128
+
129
+ values = if @ai_available
130
+ ai_generate_properties(message_class, properties, scenario, validation_errors)
131
+ else
132
+ generate_fallback_values(message_class, scenario)
133
+ end
134
+
135
+ # Ensure 'from' is set to our service name
136
+ values['from'] = @service_name
137
+
138
+ # Create instance
139
+ kwargs = values.transform_keys(&:to_sym)
140
+ message_class.new(**kwargs)
141
+ rescue => e
142
+ logger.error("Failed to create message instance: #{e.message}")
143
+ raise
144
+ end
145
+
146
+ # Publish message with retry on validation errors
147
+ def publish_with_retry(message_class, scenario)
148
+ validation_errors = []
149
+
150
+ @retry_attempts.times do |attempt|
151
+ begin
152
+ message = generate_message_instance(message_class, scenario, validation_errors)
153
+ message.publish
154
+ logger.info("Successfully published #{message_class} (attempt #{attempt + 1})")
155
+ return message
156
+ rescue => e
157
+ if validation_error = parse_validation_error(e.message)
158
+ validation_errors = build_validation_context(validation_error)
159
+ logger.warn("Validation error on attempt #{attempt + 1}: #{validation_error[:message]}")
160
+ next if attempt < @retry_attempts - 1
161
+ end
162
+ raise
163
+ end
164
+ end
165
+ end
166
+
167
+ private
168
+
169
+ def setup_ai
170
+ begin
171
+ configure_rubyllm
172
+ @llm = RubyLLM.chat
173
+ @ai_available = true
174
+ logger.info("AI model initialized successfully")
175
+ rescue => e
176
+ @ai_available = false
177
+ logger.warn("AI not available: #{e.message}. Using fallback logic.")
178
+ end
179
+ end
180
+
181
+ def default_logger
182
+ require 'logger'
183
+ Logger.new("log/#{@service_name}.log")
184
+ end
185
+
186
+ def ai_select_message(available_messages, scenario)
187
+ descriptions = available_messages.map { |name, info| "#{name}: #{info[:description]}" }.join("\n\n")
188
+
189
+ prompt = <<~PROMPT
190
+ #{build_selection_context(scenario)}
191
+
192
+ Available message types:
193
+ #{descriptions}
194
+
195
+ Which message type is most appropriate? Respond with ONLY the class name.
196
+ PROMPT
197
+
198
+ begin
199
+ response = @llm.ask(prompt)
200
+ class_name = response.content.strip
201
+
202
+ if available_messages[class_name]
203
+ logger.info("AI selected: #{class_name}")
204
+ return available_messages[class_name][:class]
205
+ end
206
+ rescue => e
207
+ logger.error("AI selection failed: #{e.message}")
208
+ end
209
+
210
+ nil
211
+ end
212
+
213
+ def fallback_select_message(available_messages, scenario)
214
+ # Simple keyword matching fallback
215
+ type_keyword = scenario[:type].to_s.downcase
216
+
217
+ # Try exact match
218
+ match = available_messages.find { |name, _| name.downcase.include?(type_keyword) }
219
+ return match[1][:class] if match
220
+
221
+ # Try partial match on description
222
+ match = available_messages.find { |_, info| info[:description].downcase.include?(type_keyword) }
223
+ return match[1][:class] if match
224
+
225
+ # Return first available
226
+ first = available_messages.first
227
+ first[1][:class] if first
228
+ end
229
+
230
+ def collect_property_metadata(message_class)
231
+ return {} unless message_class.respond_to?(:property_descriptions)
232
+
233
+ properties = {}
234
+ message_class.property_descriptions.each do |prop, desc|
235
+ enhanced_desc = desc.to_s
236
+
237
+ # Add validation constraints if available
238
+ if message_class.respond_to?(:property_validations)
239
+ validation = message_class.property_validations[prop]
240
+ if validation && validation[:validation_message]
241
+ enhanced_desc += " (#{validation[:validation_message]})"
242
+ end
243
+ end
244
+
245
+ # Check for valid value constants
246
+ const_name = "VALID_#{prop.to_s.upcase}S"
247
+ if message_class.const_defined?(const_name)
248
+ valid_values = message_class.const_get(const_name)
249
+ enhanced_desc += " Valid values: #{valid_values.join(', ')}"
250
+ end
251
+
252
+ properties[prop] = enhanced_desc
253
+ end
254
+
255
+ properties
256
+ end
257
+
258
+ def ai_generate_properties(message_class, properties, scenario, validation_errors)
259
+ properties_text = properties.map { |prop, desc| "#{prop}: #{desc}" }.join("\n")
260
+
261
+ error_context = if validation_errors.any?
262
+ "\n\nPREVIOUS VALIDATION ERRORS:\n" + validation_errors.join("\n") +
263
+ "\n\nFix these specific properties with valid values."
264
+ else
265
+ ""
266
+ end
267
+
268
+ prompt = <<~PROMPT
269
+ #{build_property_context(scenario, message_class)}
270
+
271
+ Properties to fill:
272
+ #{properties_text}
273
+
274
+ The 'from' field should be '#{@service_name}'.
275
+ #{error_context}
276
+
277
+ Respond with a JSON object containing the property values.
278
+ PROMPT
279
+
280
+ begin
281
+ response = @llm.ask(prompt)
282
+ JSON.parse(response.content)
283
+ rescue => e
284
+ logger.error("AI property generation failed: #{e.message}")
285
+ generate_fallback_values(message_class, scenario)
286
+ end
287
+ end
288
+
289
+ def parse_validation_error(error_message)
290
+ # Parse validation errors in various formats
291
+ if error_message =~ /Messages::(\w+)#(\w+):\s*(.+)/
292
+ return {
293
+ class_name: $1,
294
+ property: $2,
295
+ message: $3,
296
+ valid_values: extract_valid_values($3)
297
+ }
298
+ elsif error_message =~ /property\s+'(\w+)'\s+is\s+required/i
299
+ return {
300
+ property: $1,
301
+ message: error_message
302
+ }
303
+ elsif error_message.include?("ValidationError")
304
+ return {
305
+ property: "unknown",
306
+ message: error_message
307
+ }
308
+ end
309
+
310
+ nil
311
+ end
312
+
313
+ def extract_valid_values(message)
314
+ if message =~ /must be(?:\s+one\s+of)?:\s*(.+)/i
315
+ $1.strip.split(/,\s*/).map(&:strip)
316
+ end
317
+ end
318
+
319
+ def build_validation_context(validation_error)
320
+ context = ["Property '#{validation_error[:property]}' has invalid value."]
321
+ context << "Error: #{validation_error[:message]}"
322
+ context << "Valid values: #{validation_error[:valid_values].join(', ')}" if validation_error[:valid_values]
323
+ context
324
+ end
325
+ end
326
+
327
+ # Example concrete implementation
328
+ class ExampleAIAgent < SmartMessageAIAgent
329
+ def initialize
330
+ super('example_agent')
331
+ end
332
+
333
+ # Customize which messages this agent can use
334
+ def include_message_class?(message_class)
335
+ # Only include emergency-related messages
336
+ message_class.to_s.include?('Emergency') || message_class.to_s.include?('Alert')
337
+ end
338
+
339
+ # Provide domain-specific context
340
+ def build_selection_context(scenario)
341
+ "An emergency response agent observed: #{scenario[:description]}. Select the appropriate emergency message type."
342
+ end
343
+
344
+ def build_property_context(scenario, message_class)
345
+ "Generate emergency report values for #{message_class}. Incident: #{scenario[:description]}. Use high severity for serious incidents."
346
+ end
347
+
348
+ # Domain-specific fallback values
349
+ def generate_fallback_values(message_class, scenario = nil)
350
+ base_values = super
351
+
352
+ # Add emergency-specific defaults
353
+ if message_class.to_s.include?('Emergency')
354
+ base_values.merge!({
355
+ 'severity' => 'high',
356
+ 'response_required' => true,
357
+ 'location' => 'Unknown Location',
358
+ 'reported_by' => @service_name
359
+ })
360
+ end
361
+
362
+ base_values
363
+ end
364
+ end
@@ -0,0 +1,236 @@
1
+ #!/bin/bash
2
+
3
+ # SmartMessage City Demo Launcher for iTerm2
4
+ # Creates a new iTerm2 window with separate tabs for each city service
5
+
6
+ DEMO_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
7
+
8
+ echo "Removing old log files..."
9
+ rm -f $DEMO_DIR/log/*.log
10
+
11
+ # Check if iTerm2 is available
12
+ if ! ls /Applications/iTerm.app &>/dev/null; then
13
+ echo "Error: iTerm2 is not installed."
14
+ echo "Please install iTerm2 from https://iterm2.com/"
15
+ exit 1
16
+ fi
17
+
18
+ echo "Starting SmartMessage City Demo in iTerm2..."
19
+
20
+ # Create the iTerm2 window and tabs using AppleScript
21
+ osascript <<EOF
22
+ tell application "iTerm2"
23
+ activate
24
+
25
+ -- Create new window
26
+ set newWindow to (create window with default profile)
27
+
28
+ -- Tab 1: Health Department (already created)
29
+ tell current session of current tab of newWindow
30
+ set name to "Health Department"
31
+ write text "cd '$DEMO_DIR'"
32
+ write text "clear"
33
+ write text "echo 'Starting Health Department...'"
34
+ write text "ruby health_department.rb; exit"
35
+ end tell
36
+
37
+ -- Tab 2: Police Department
38
+ tell newWindow
39
+ set newTab to (create tab with default profile)
40
+ tell current session of newTab
41
+ set name to "Police Department"
42
+ write text "cd '$DEMO_DIR'"
43
+ write text "clear"
44
+ write text "echo 'Starting Police Department...'"
45
+ write text "ruby police_department.rb; exit"
46
+ end tell
47
+ end tell
48
+
49
+ -- Tab 3: Fire Department
50
+ tell newWindow
51
+ set newTab to (create tab with default profile)
52
+ tell current session of newTab
53
+ set name to "Fire Department"
54
+ write text "cd '$DEMO_DIR'"
55
+ write text "clear"
56
+ write text "echo 'Starting Fire Department...'"
57
+ write text "ruby fire_department.rb; exit"
58
+ end tell
59
+ end tell
60
+
61
+ -- Tab 4: Local Bank
62
+ tell newWindow
63
+ set newTab to (create tab with default profile)
64
+ tell current session of newTab
65
+ set name to "Local Bank"
66
+ write text "cd '$DEMO_DIR'"
67
+ write text "clear"
68
+ write text "echo 'Starting Local Bank...'"
69
+ write text "ruby local_bank.rb; exit"
70
+ end tell
71
+ end tell
72
+
73
+ -- Tab 5: House #1
74
+ tell newWindow
75
+ set newTab to (create tab with default profile)
76
+ tell current session of newTab
77
+ set name to "House #1"
78
+ write text "cd '$DEMO_DIR'"
79
+ write text "clear"
80
+ write text "echo 'Starting House #1...'"
81
+ write text "ruby house.rb '456 Oak Street'; exit"
82
+ end tell
83
+ end tell
84
+
85
+ -- Tab 6: House #2
86
+ tell newWindow
87
+ set newTab to (create tab with default profile)
88
+ tell current session of newTab
89
+ set name to "House #2"
90
+ write text "cd '$DEMO_DIR'"
91
+ write text "clear"
92
+ write text "echo 'Starting House #2...'"
93
+ write text "ruby house.rb '789 Pine Lane'; exit"
94
+ end tell
95
+ end tell
96
+
97
+ -- Tab 7: Emergency Dispatch (911)
98
+ tell newWindow
99
+ set newTab to (create tab with default profile)
100
+ tell current session of newTab
101
+ set name to "911 Dispatch"
102
+ write text "cd '$DEMO_DIR'"
103
+ write text "clear"
104
+ write text "echo 'Starting Emergency Dispatch Center (911)...'"
105
+ write text "ruby emergency_dispatch_center.rb; exit"
106
+ end tell
107
+ end tell
108
+
109
+ -- Tab 8: Citizen Caller
110
+ tell newWindow
111
+ set newTab to (create tab with default profile)
112
+ tell current session of newTab
113
+ set name to "Citizen"
114
+ write text "cd '$DEMO_DIR'"
115
+ write text "clear"
116
+ write text "echo 'Starting Citizen 911 Caller...'"
117
+ write text "sleep 3; ruby citizen.rb auto; exit"
118
+ end tell
119
+ end tell
120
+
121
+ -- Tab 9: Redis Monitor
122
+ tell newWindow
123
+ set newTab to (create tab with default profile)
124
+ tell current session of newTab
125
+ set name to "Redis Monitor"
126
+ write text "cd '$DEMO_DIR'"
127
+ write text "clear"
128
+ write text "echo 'Starting Redis Message Monitor...'"
129
+ write text "ruby redis_monitor.rb; exit"
130
+ end tell
131
+ end tell
132
+
133
+ -- Tab 10: Redis Statistics
134
+ tell newWindow
135
+ set newTab to (create tab with default profile)
136
+ tell current session of newTab
137
+ set name to "Redis Statistics"
138
+ write text "cd '$DEMO_DIR'"
139
+ write text "clear"
140
+ write text "echo 'Starting Redis Statistics Dashboard...'"
141
+ write text "ruby redis_stats.rb; exit"
142
+ end tell
143
+ end tell
144
+
145
+ -- Tab 11: Control Panel
146
+ tell newWindow
147
+ set newTab to (create tab with default profile)
148
+ tell current session of newTab
149
+ set name to "Control Panel"
150
+ write text "cd '$DEMO_DIR'"
151
+ write text "clear"
152
+ write text "cat << 'CONTROL_PANEL_EOF'
153
+ ===== SmartMessage City Demo Control Panel =====
154
+
155
+ CITY SERVICES:
156
+ Tab 1: Health Department (broadcasts health checks every 5s)
157
+ Tab 2: Police Department (responds to alarms & 911 calls)
158
+ Tab 3: Fire Department (responds to fires & 911 emergencies)
159
+ Tab 4: Local Bank (occasional silent alarms)
160
+ Tab 5: House #1 (456 Oak Street)
161
+ Tab 6: House #2 (789 Pine Lane)
162
+ Tab 7: 911 Dispatch (emergency call routing center)
163
+ Tab 8: Citizen (makes 911 calls automatically)
164
+ Tab 9: Redis Monitor (real-time message traffic)
165
+ Tab 10: Redis Statistics (performance dashboard)
166
+ Tab 11: Control Panel (this tab)
167
+
168
+ CONTROLS:
169
+ ./stop_demo.sh - Stop all city services
170
+ Cmd+W - Close current tab
171
+ Cmd+1,2,3,4,5,6,7,8,9,0 - Switch to tab
172
+ Ctrl+C - Stop service in current tab
173
+
174
+ MESSAGE FLOWS:
175
+ Health Checks: Health Dept -> All Services
176
+ Health Status: All Services -> Health Dept (colored output)
177
+ Bank Alarms: Bank -> Police (silent alarm system)
178
+ House Fires: Houses -> Fire Dept (emergency response)
179
+ 911 Calls: Citizen -> 911 Dispatch -> Police/Fire
180
+ Emergencies: All -> Health Dept (incident resolution)
181
+
182
+ WHAT TO WATCH:
183
+ Tab 1: Health status with GREEN/YELLOW/ORANGE/RED colors
184
+ Tab 2: Police dispatching units to crimes & accidents
185
+ Tab 3: Fire trucks responding to fires, medical & rescue calls
186
+ Tab 4: Bank triggering occasional silent alarms
187
+ Tab 5/6: Houses occasionally catching fire
188
+ Tab 7: 911 dispatch routing emergency calls
189
+ Tab 8: Citizens calling 911 with various emergencies
190
+ Tab 9: Real-time Redis message traffic (color-coded)
191
+ Tab 10: Redis performance metrics & pub/sub statistics
192
+
193
+ STATUS COLORS:
194
+ 🟢 Green: healthy 🟡 Yellow: warning
195
+ 🟠 Orange: critical 🔴 Red: failed
196
+
197
+ Ready! Watch the city come alive with emergency services!
198
+ ========================================================
199
+ CONTROL_PANEL_EOF"
200
+ write text "# Type 'exit' or use ./stop_demo.sh to close all tabs"
201
+ write text "bash"
202
+ end tell
203
+ end tell
204
+
205
+ -- Switch back to first tab
206
+ tell newWindow
207
+ select (first tab)
208
+ end tell
209
+
210
+ end tell
211
+ EOF
212
+
213
+ if [ $? -eq 0 ]; then
214
+ echo ""
215
+ echo "✅ City Demo started successfully in iTerm2!"
216
+ echo ""
217
+ echo "🏥 Tab 1: Health Department - monitors all city services"
218
+ echo "🚔 Tab 2: Police Department - responds to alarms & 911 calls"
219
+ echo "🚒 Tab 3: Fire Department - responds to fires & 911 emergencies"
220
+ echo "🏦 Tab 4: Local Bank - triggers occasional alarms"
221
+ echo "🏠 Tab 5/6: Houses - occasionally catch fire"
222
+ echo "📞 Tab 7: 911 Dispatch - emergency call routing center"
223
+ echo "👤 Tab 8: Citizen - makes 911 calls automatically"
224
+ echo "🔍 Tab 9: Redis Monitor - real-time message traffic"
225
+ echo "📊 Tab 10: Redis Statistics - performance dashboard"
226
+ echo ""
227
+ echo "📱 Use Cmd+1,2,3,4,5,6,7,8,9,0 to switch between tabs"
228
+ echo "🛑 Run ./stop_demo.sh to stop all services"
229
+ echo ""
230
+ echo "🌟 Watch Tab 1 for colored health status updates!"
231
+ echo "📞 Watch Tab 7 for 911 dispatch routing & Tab 8 for citizen emergencies!"
232
+ echo "🔍 Check Tab 9 for real-time message traffic & Tab 10 for Redis stats!"
233
+ else
234
+ echo "��� Failed to start demo. Please check that iTerm2 is installed and running."
235
+ exit 1
236
+ fi
@@ -0,0 +1,106 @@
1
+ #!/bin/bash
2
+
3
+ # SmartMessage City Demo Shutdown Script for iTerm2
4
+ # Stops all city services and closes the iTerm2 demo window
5
+
6
+ echo "Stopping SmartMessage City Demo..."
7
+
8
+ # Check if iTerm2 is running (process name is "iTerm" not "iTerm2")
9
+ if ! pgrep -x "iTerm" > /dev/null; then
10
+ echo "iTerm2 is not running."
11
+ # Still continue to check for orphaned processes
12
+ fi
13
+
14
+ # Function to find and close demo window
15
+ echo "Looking for SmartMessage city demo window in iTerm2..."
16
+
17
+ WINDOW_CLOSED=$(osascript <<'EOF'
18
+ tell application "iTerm2"
19
+ set windowFound to false
20
+
21
+ repeat with theWindow in windows
22
+ tell theWindow
23
+ set tabNames to {}
24
+ repeat with theTab in tabs
25
+ set end of tabNames to (name of current session of theTab)
26
+ end repeat
27
+
28
+ -- Check if this window has our city demo tabs
29
+ if "Health Department" is in tabNames or "Police Department" is in tabNames or "Fire Department" is in tabNames or "Local Bank" is in tabNames or "911 Dispatch" is in tabNames then
30
+ set windowFound to true
31
+
32
+ -- Send Ctrl+C to stop programs and exit shells
33
+ repeat with theTab in tabs
34
+ tell current session of theTab
35
+ write text (character id 3) -- Ctrl+C
36
+ delay 0.5
37
+ write text "exit" -- Exit the shell to close cleanly
38
+ end tell
39
+ end repeat
40
+
41
+ delay 2
42
+ -- Force close the window
43
+ close theWindow
44
+ exit repeat
45
+ end if
46
+ end tell
47
+ end repeat
48
+
49
+ return windowFound
50
+ end tell
51
+ EOF
52
+ )
53
+
54
+ if [ "$WINDOW_CLOSED" = "true" ]; then
55
+ echo "✅ City demo window found and closed successfully."
56
+ else
57
+ echo "⚠️ No city demo window found. Checking for orphaned processes..."
58
+ fi
59
+
60
+ # Clean up any remaining city service processes
61
+ echo "Checking for remaining city service processes..."
62
+
63
+ # Include all demo programs in the cleanup
64
+ ORPHANS=$(pgrep -f "(health_department|police_department|fire_department|local_bank|house|emergency_dispatch_center|citizen|redis_monitor|redis_stats)\.rb")
65
+
66
+ if [ -n "$ORPHANS" ]; then
67
+ echo "Found orphaned city service processes. Cleaning up..."
68
+ echo "$ORPHANS" | while read pid; do
69
+ echo " Stopping process $pid..."
70
+ kill -TERM "$pid" 2>/dev/null || true
71
+ done
72
+
73
+ # Wait a moment for graceful termination
74
+ sleep 1
75
+
76
+ # Force kill any remaining processes
77
+ REMAINING=$(pgrep -f "(health_department|police_department|fire_department|local_bank|house|emergency_dispatch_center|citizen|redis_monitor|redis_stats)\.rb")
78
+ if [ -n "$REMAINING" ]; then
79
+ echo "Force killing remaining processes..."
80
+ echo "$REMAINING" | while read pid; do
81
+ echo " Force killing process $pid..."
82
+ kill -KILL "$pid" 2>/dev/null || true
83
+ done
84
+ fi
85
+
86
+ echo "✅ Orphaned processes cleaned up."
87
+ else
88
+ echo "✅ No orphaned processes found."
89
+ fi
90
+
91
+ # Clean up Redis channels and reset message counts
92
+ echo "Cleaning up Redis channels..."
93
+ redis-cli <<EOF > /dev/null 2>&1
94
+ # Unsubscribe all clients from SmartMessage channels
95
+ CLIENT LIST | grep -o 'id=[0-9]*' | cut -d= -f2 | xargs -I {} CLIENT KILL ID {}
96
+ # Note: We can't actually "clear" channels in Redis, but killing clients removes subscriptions
97
+ # The publish counts remain in Redis stats but that's historical data
98
+ EOF
99
+
100
+ echo "✅ Redis channels cleaned up."
101
+
102
+ echo ""
103
+ echo "🛑 SmartMessage city demo has been stopped."
104
+ echo " All emergency services are offline."
105
+ echo ""
106
+ echo "💡 To start the city demo again, run: ./start_demo.sh"