botiasloop 0.0.1 → 0.0.7
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/LICENSE +7 -0
- data/README.md +266 -122
- data/bin/botiasloop +65 -15
- data/lib/botiasloop/agent.rb +25 -12
- data/lib/botiasloop/auto_label.rb +117 -0
- data/lib/botiasloop/channels/base.rb +48 -44
- data/lib/botiasloop/channels/cli.rb +14 -18
- data/lib/botiasloop/channels/telegram.rb +95 -42
- data/lib/botiasloop/channels_manager.rb +23 -30
- data/lib/botiasloop/chat.rb +122 -0
- data/lib/botiasloop/commands/archive.rb +34 -11
- data/lib/botiasloop/commands/compact.rb +1 -1
- data/lib/botiasloop/commands/context.rb +6 -6
- data/lib/botiasloop/commands/conversations.rb +11 -6
- data/lib/botiasloop/commands/label.rb +9 -11
- data/lib/botiasloop/commands/new.rb +2 -2
- data/lib/botiasloop/commands/status.rb +2 -2
- data/lib/botiasloop/commands/switch.rb +5 -7
- data/lib/botiasloop/commands/verbose.rb +29 -0
- data/lib/botiasloop/commands.rb +1 -0
- data/lib/botiasloop/config.rb +16 -0
- data/lib/botiasloop/conversation.rb +100 -11
- data/lib/botiasloop/database.rb +16 -4
- data/lib/botiasloop/human_id.rb +58 -0
- data/lib/botiasloop/logger.rb +45 -0
- data/lib/botiasloop/loop.rb +88 -7
- data/lib/botiasloop/systemd_service.rb +20 -10
- data/lib/botiasloop/tools/shell.rb +5 -0
- data/lib/botiasloop/version.rb +1 -1
- data/lib/botiasloop.rb +8 -1
- metadata +46 -27
- data/lib/botiasloop/conversation_manager.rb +0 -225
data/lib/botiasloop/agent.rb
CHANGED
|
@@ -1,16 +1,28 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require "ruby_llm"
|
|
4
|
-
require "logger"
|
|
5
4
|
|
|
6
5
|
module Botiasloop
|
|
7
6
|
class Agent
|
|
7
|
+
@instance = nil
|
|
8
|
+
|
|
9
|
+
class << self
|
|
10
|
+
# @return [Agent] Singleton instance of the agent
|
|
11
|
+
def instance
|
|
12
|
+
@instance ||= new
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
# @return [Agent] Singleton instance of the agent (alias for instance)
|
|
16
|
+
def chat(message, conversation: nil, verbose_callback: nil)
|
|
17
|
+
instance.chat(message, conversation: conversation, verbose_callback: verbose_callback)
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
# Set the instance directly (primarily for testing)
|
|
21
|
+
# @param agent [Agent, nil] Agent instance or nil to reset
|
|
22
|
+
attr_writer :instance
|
|
23
|
+
end
|
|
8
24
|
# Initialize the agent
|
|
9
|
-
|
|
10
|
-
# @param config [Config, nil] Configuration instance (loads default if nil)
|
|
11
|
-
def initialize(config = nil)
|
|
12
|
-
@config = config || Config.new
|
|
13
|
-
@logger = Logger.new($stderr)
|
|
25
|
+
def initialize
|
|
14
26
|
setup_ruby_llm
|
|
15
27
|
end
|
|
16
28
|
|
|
@@ -18,15 +30,16 @@ module Botiasloop
|
|
|
18
30
|
#
|
|
19
31
|
# @param message [String] User message
|
|
20
32
|
# @param conversation [Conversation, nil] Existing conversation
|
|
33
|
+
# @param verbose_callback [Proc, nil] Callback for verbose messages
|
|
21
34
|
# @return [String] Assistant response
|
|
22
|
-
def chat(message, conversation: nil)
|
|
35
|
+
def chat(message, conversation: nil, verbose_callback: nil)
|
|
23
36
|
conversation ||= Conversation.new
|
|
24
37
|
|
|
25
38
|
registry = create_registry
|
|
26
39
|
provider, model = create_provider_and_model
|
|
27
|
-
loop = Loop.new(provider, model, registry, max_iterations:
|
|
40
|
+
loop = Loop.new(provider, model, registry, max_iterations: Config.instance.max_iterations)
|
|
28
41
|
|
|
29
|
-
loop.run(conversation, message)
|
|
42
|
+
loop.run(conversation, message, verbose_callback)
|
|
30
43
|
rescue MaxIterationsExceeded => e
|
|
31
44
|
e.message
|
|
32
45
|
end
|
|
@@ -34,7 +47,7 @@ module Botiasloop
|
|
|
34
47
|
private
|
|
35
48
|
|
|
36
49
|
def setup_ruby_llm
|
|
37
|
-
provider_name, provider_config =
|
|
50
|
+
provider_name, provider_config = Config.instance.active_provider
|
|
38
51
|
|
|
39
52
|
RubyLLM.configure do |config|
|
|
40
53
|
configure_provider(config, provider_name, provider_config)
|
|
@@ -85,7 +98,7 @@ module Botiasloop
|
|
|
85
98
|
end
|
|
86
99
|
|
|
87
100
|
def create_provider_and_model
|
|
88
|
-
_provider_name, provider_config =
|
|
101
|
+
_provider_name, provider_config = Config.instance.active_provider
|
|
89
102
|
model_id = provider_config["model"]
|
|
90
103
|
model = RubyLLM::Models.find(model_id)
|
|
91
104
|
provider_class = RubyLLM::Provider.for(model_id)
|
|
@@ -106,7 +119,7 @@ module Botiasloop
|
|
|
106
119
|
end
|
|
107
120
|
|
|
108
121
|
def web_search_url
|
|
109
|
-
|
|
122
|
+
Config.instance.tools["web_search"]["searxng_url"]
|
|
110
123
|
end
|
|
111
124
|
end
|
|
112
125
|
end
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "ruby_llm"
|
|
4
|
+
require "logger"
|
|
5
|
+
|
|
6
|
+
module Botiasloop
|
|
7
|
+
# Service class for automatically generating conversation labels
|
|
8
|
+
# Triggered after 3rd user message if no label is set
|
|
9
|
+
class AutoLabel
|
|
10
|
+
MIN_MESSAGES_FOR_AUTO_LABEL = 6 # 3 user + 3 assistant messages
|
|
11
|
+
|
|
12
|
+
# Generate a label for the conversation if conditions are met
|
|
13
|
+
#
|
|
14
|
+
# @param conversation [Conversation] The conversation to label
|
|
15
|
+
# @return [String, nil] The generated label or nil if not applicable
|
|
16
|
+
def self.generate(conversation)
|
|
17
|
+
return nil unless should_generate?(conversation)
|
|
18
|
+
|
|
19
|
+
label = new.generate_label(conversation)
|
|
20
|
+
|
|
21
|
+
Logger.info "[AutoLabel] Generated label '#{label}' for conversation #{conversation.uuid}" if label
|
|
22
|
+
|
|
23
|
+
label
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
# Check if auto-labelling should run
|
|
27
|
+
#
|
|
28
|
+
# @param conversation [Conversation] The conversation to check
|
|
29
|
+
# @return [Boolean] True if conditions are met
|
|
30
|
+
def self.should_generate?(conversation)
|
|
31
|
+
return false unless Config.instance.features&.dig("auto_labelling", "enabled") != false
|
|
32
|
+
return false if conversation.label?
|
|
33
|
+
return false if conversation.message_count < MIN_MESSAGES_FOR_AUTO_LABEL
|
|
34
|
+
|
|
35
|
+
true
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def initialize
|
|
39
|
+
@config = Config.instance
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
# Generate a label based on conversation content
|
|
43
|
+
#
|
|
44
|
+
# @param conversation [Conversation] The conversation to label
|
|
45
|
+
# @return [String, nil] The generated and formatted label
|
|
46
|
+
def generate_label(conversation)
|
|
47
|
+
messages = conversation.history
|
|
48
|
+
raw_label = generate_label_text(messages)
|
|
49
|
+
return nil unless raw_label
|
|
50
|
+
|
|
51
|
+
formatted_label = format_label(raw_label)
|
|
52
|
+
return nil unless valid_label?(formatted_label)
|
|
53
|
+
|
|
54
|
+
formatted_label
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
private
|
|
58
|
+
|
|
59
|
+
def generate_label_text(messages)
|
|
60
|
+
chat = create_chat
|
|
61
|
+
|
|
62
|
+
conversation_text = messages.first(MIN_MESSAGES_FOR_AUTO_LABEL).map do |msg|
|
|
63
|
+
"#{msg[:role]}: #{msg[:content]}"
|
|
64
|
+
end.join("\n\n")
|
|
65
|
+
|
|
66
|
+
prompt = <<~PROMPT
|
|
67
|
+
Based on the following conversation, generate a short label (1-2 words) that describes the topic.
|
|
68
|
+
Use lowercase letters only. If two words, separate them with a dash (-).
|
|
69
|
+
Examples: "coding-help", "travel-planning", "recipe-ideas", "debugging"
|
|
70
|
+
|
|
71
|
+
Conversation:
|
|
72
|
+
#{conversation_text}
|
|
73
|
+
|
|
74
|
+
Label (respond with just the label, nothing else):
|
|
75
|
+
PROMPT
|
|
76
|
+
|
|
77
|
+
chat.add_message(role: :user, content: prompt)
|
|
78
|
+
response = chat.complete
|
|
79
|
+
|
|
80
|
+
response.content&.strip
|
|
81
|
+
rescue
|
|
82
|
+
nil
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def create_chat
|
|
86
|
+
label_config = @config.features["auto_labelling"] || {}
|
|
87
|
+
|
|
88
|
+
if label_config["model"]
|
|
89
|
+
RubyLLM.chat(model: label_config["model"])
|
|
90
|
+
else
|
|
91
|
+
default_model = @config.providers["openrouter"]["model"]
|
|
92
|
+
RubyLLM.chat(model: default_model)
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
def format_label(raw_label)
|
|
97
|
+
# Remove non-alphanumeric characters except dashes, underscores, and spaces
|
|
98
|
+
cleaned = raw_label.gsub(/[^a-zA-Z0-9\s\-_]/, "")
|
|
99
|
+
|
|
100
|
+
# Split into words (by whitespace only, preserve underscores in words)
|
|
101
|
+
words = cleaned.split(/\s+/).reject(&:empty?)
|
|
102
|
+
|
|
103
|
+
# Take max 2 words
|
|
104
|
+
words = words.first(2)
|
|
105
|
+
|
|
106
|
+
# Join with dash, lowercase
|
|
107
|
+
words.join("-").downcase
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
def valid_label?(label)
|
|
111
|
+
return false if label.nil? || label.empty?
|
|
112
|
+
return false unless label.match?(Conversation::LABEL_REGEX)
|
|
113
|
+
|
|
114
|
+
true
|
|
115
|
+
end
|
|
116
|
+
end
|
|
117
|
+
end
|
|
@@ -2,7 +2,6 @@
|
|
|
2
2
|
|
|
3
3
|
require "json"
|
|
4
4
|
require "fileutils"
|
|
5
|
-
require "logger"
|
|
6
5
|
|
|
7
6
|
module Botiasloop
|
|
8
7
|
module Channels
|
|
@@ -14,9 +13,7 @@ module Botiasloop
|
|
|
14
13
|
# @param name [Symbol] Channel identifier (e.g., :telegram)
|
|
15
14
|
# @return [Symbol] The channel identifier
|
|
16
15
|
def channel_name(name = nil)
|
|
17
|
-
if name
|
|
18
|
-
@channel_identifier = name
|
|
19
|
-
end
|
|
16
|
+
@channel_identifier = name if name
|
|
20
17
|
@channel_identifier
|
|
21
18
|
end
|
|
22
19
|
|
|
@@ -39,12 +36,8 @@ module Botiasloop
|
|
|
39
36
|
|
|
40
37
|
# Initialize the channel
|
|
41
38
|
#
|
|
42
|
-
# @param config [Config] Configuration instance
|
|
43
39
|
# @raise [Error] If required configuration is missing
|
|
44
|
-
def initialize
|
|
45
|
-
@config = config
|
|
46
|
-
@logger = Logger.new($stderr)
|
|
47
|
-
|
|
40
|
+
def initialize
|
|
48
41
|
validate_required_config!
|
|
49
42
|
end
|
|
50
43
|
|
|
@@ -53,19 +46,27 @@ module Botiasloop
|
|
|
53
46
|
#
|
|
54
47
|
# @return [Hash] Channel configuration hash
|
|
55
48
|
def channel_config
|
|
56
|
-
|
|
49
|
+
Config.instance.channels[self.class.channel_identifier.to_s] || {}
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
# Get the channel type string (e.g., "telegram", "cli")
|
|
53
|
+
# Override in subclasses if needed
|
|
54
|
+
#
|
|
55
|
+
# @return [String] Channel type string
|
|
56
|
+
def channel_type
|
|
57
|
+
self.class.channel_identifier.to_s
|
|
57
58
|
end
|
|
58
59
|
|
|
59
60
|
# Start the channel and begin listening for messages
|
|
60
61
|
# @raise [NotImplementedError] Subclass must implement
|
|
61
|
-
def
|
|
62
|
-
raise NotImplementedError, "Subclass must implement #
|
|
62
|
+
def start_listening
|
|
63
|
+
raise NotImplementedError, "Subclass must implement #start_listening"
|
|
63
64
|
end
|
|
64
65
|
|
|
65
66
|
# Stop the channel and cleanup
|
|
66
67
|
# @raise [NotImplementedError] Subclass must implement
|
|
67
|
-
def
|
|
68
|
-
raise NotImplementedError, "Subclass must implement #
|
|
68
|
+
def stop_listening
|
|
69
|
+
raise NotImplementedError, "Subclass must implement #stop_listening"
|
|
69
70
|
end
|
|
70
71
|
|
|
71
72
|
# Check if the channel is currently running
|
|
@@ -80,7 +81,7 @@ module Botiasloop
|
|
|
80
81
|
# @param source_id [String] Unique identifier for the message source (e.g., chat_id, user_id)
|
|
81
82
|
# @param raw_message [Object] Raw message object (varies by channel)
|
|
82
83
|
# @param metadata [Hash] Additional metadata about the message
|
|
83
|
-
def process_message(source_id, raw_message,
|
|
84
|
+
def process_message(source_id, raw_message, _metadata = {})
|
|
84
85
|
# Hook: Extract content from raw message
|
|
85
86
|
content = extract_content(raw_message)
|
|
86
87
|
return if content.nil? || content.to_s.empty?
|
|
@@ -98,22 +99,25 @@ module Botiasloop
|
|
|
98
99
|
before_process(source_id, user_id, content, raw_message)
|
|
99
100
|
|
|
100
101
|
# Core processing logic
|
|
101
|
-
|
|
102
|
+
chat = chat_for(source_id, user_identifier: user_id)
|
|
103
|
+
conversation = chat.current_conversation
|
|
102
104
|
|
|
103
105
|
response = if Commands.command?(content)
|
|
104
106
|
context = Commands::Context.new(
|
|
105
107
|
conversation: conversation,
|
|
106
|
-
|
|
108
|
+
chat: chat,
|
|
107
109
|
channel: self,
|
|
108
110
|
user_id: source_id
|
|
109
111
|
)
|
|
110
112
|
Commands.execute(content, context)
|
|
111
113
|
else
|
|
112
|
-
|
|
113
|
-
|
|
114
|
+
verbose_callback = proc do |verbose_message|
|
|
115
|
+
send_message(source_id, verbose_message)
|
|
116
|
+
end
|
|
117
|
+
Agent.chat(content, conversation: conversation, verbose_callback: verbose_callback)
|
|
114
118
|
end
|
|
115
119
|
|
|
116
|
-
|
|
120
|
+
send_message(source_id, response)
|
|
117
121
|
|
|
118
122
|
# Hook: Post-processing
|
|
119
123
|
after_process(source_id, user_id, response, raw_message)
|
|
@@ -136,7 +140,7 @@ module Botiasloop
|
|
|
136
140
|
# @param source_id [String] Source identifier
|
|
137
141
|
# @param raw_message [Object] Raw message object
|
|
138
142
|
# @return [String] User ID for authorization
|
|
139
|
-
def extract_user_id(source_id,
|
|
143
|
+
def extract_user_id(source_id, _raw_message)
|
|
140
144
|
source_id
|
|
141
145
|
end
|
|
142
146
|
|
|
@@ -168,8 +172,8 @@ module Botiasloop
|
|
|
168
172
|
# @param source_id [String] Source identifier
|
|
169
173
|
# @param user_id [String] User ID that was denied
|
|
170
174
|
# @param raw_message [Object] Raw message object
|
|
171
|
-
def handle_unauthorized(source_id, user_id,
|
|
172
|
-
|
|
175
|
+
def handle_unauthorized(source_id, user_id, _raw_message)
|
|
176
|
+
Logger.warn "[#{self.class.channel_identifier}] Unauthorized access from #{user_id} (source: #{source_id})"
|
|
173
177
|
end
|
|
174
178
|
|
|
175
179
|
# Handle errors during message processing
|
|
@@ -179,8 +183,8 @@ module Botiasloop
|
|
|
179
183
|
# @param user_id [String] User ID
|
|
180
184
|
# @param error [Exception] The error that occurred
|
|
181
185
|
# @param raw_message [Object] Raw message object
|
|
182
|
-
def handle_error(
|
|
183
|
-
|
|
186
|
+
def handle_error(_source_id, _user_id, error, _raw_message)
|
|
187
|
+
Logger.error "[#{self.class.channel_identifier}] Error processing message: #{error.message}"
|
|
184
188
|
raise error
|
|
185
189
|
end
|
|
186
190
|
|
|
@@ -188,43 +192,43 @@ module Botiasloop
|
|
|
188
192
|
#
|
|
189
193
|
# @param source_id [String] Source identifier to check
|
|
190
194
|
# @return [Boolean] False by default (secure default)
|
|
191
|
-
def authorized?(
|
|
195
|
+
def authorized?(_source_id)
|
|
192
196
|
false
|
|
193
197
|
end
|
|
194
198
|
|
|
195
|
-
# Get or create a
|
|
196
|
-
# Uses the global ConversationManager for state management.
|
|
199
|
+
# Get or create a chat for a source
|
|
197
200
|
#
|
|
198
201
|
# @param source_id [String] Source identifier
|
|
199
|
-
# @
|
|
200
|
-
|
|
201
|
-
|
|
202
|
+
# @param user_identifier [String, nil] Optional user identifier (e.g., username)
|
|
203
|
+
# @return [Chat] Chat instance
|
|
204
|
+
def chat_for(source_id, user_identifier: nil)
|
|
205
|
+
Chat.find_or_create(channel_type, source_id, user_identifier: user_identifier)
|
|
202
206
|
end
|
|
203
207
|
|
|
204
|
-
# Format a
|
|
208
|
+
# Format a message for this channel
|
|
205
209
|
#
|
|
206
|
-
# @param content [String] Raw
|
|
207
|
-
# @return [String] Formatted
|
|
208
|
-
def
|
|
210
|
+
# @param content [String] Raw message content
|
|
211
|
+
# @return [String] Formatted message
|
|
212
|
+
def format_message(content)
|
|
209
213
|
content
|
|
210
214
|
end
|
|
211
215
|
|
|
212
|
-
# Send a
|
|
216
|
+
# Send a message to a source
|
|
213
217
|
#
|
|
214
218
|
# @param source_id [String] Source identifier
|
|
215
|
-
# @param
|
|
216
|
-
def
|
|
217
|
-
formatted =
|
|
218
|
-
|
|
219
|
+
# @param message [String] Message content
|
|
220
|
+
def send_message(source_id, message)
|
|
221
|
+
formatted = format_message(message)
|
|
222
|
+
deliver_message(source_id, formatted)
|
|
219
223
|
end
|
|
220
224
|
|
|
221
|
-
# Deliver a formatted
|
|
225
|
+
# Deliver a formatted message to a source
|
|
222
226
|
#
|
|
223
227
|
# @param source_id [String] Source identifier
|
|
224
|
-
# @param formatted_content [String] Formatted
|
|
228
|
+
# @param formatted_content [String] Formatted message content
|
|
225
229
|
# @raise [NotImplementedError] Subclass must implement
|
|
226
|
-
def
|
|
227
|
-
raise NotImplementedError, "Subclass must implement #
|
|
230
|
+
def deliver_message(source_id, formatted_content)
|
|
231
|
+
raise NotImplementedError, "Subclass must implement #deliver_message"
|
|
228
232
|
end
|
|
229
233
|
|
|
230
234
|
private
|
|
@@ -1,7 +1,5 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require "logger"
|
|
4
|
-
|
|
5
3
|
module Botiasloop
|
|
6
4
|
module Channels
|
|
7
5
|
class CLI < Base
|
|
@@ -11,17 +9,15 @@ module Botiasloop
|
|
|
11
9
|
SOURCE_ID = "cli"
|
|
12
10
|
|
|
13
11
|
# Initialize CLI channel
|
|
14
|
-
|
|
15
|
-
# @param config [Config] Configuration instance
|
|
16
|
-
def initialize(config)
|
|
12
|
+
def initialize
|
|
17
13
|
super
|
|
18
14
|
@running = false
|
|
19
15
|
end
|
|
20
16
|
|
|
21
17
|
# Start the CLI interactive mode
|
|
22
|
-
def
|
|
18
|
+
def start_listening
|
|
23
19
|
@running = true
|
|
24
|
-
|
|
20
|
+
Logger.info "[CLI] Starting interactive mode..."
|
|
25
21
|
|
|
26
22
|
puts "botiasloop v#{VERSION} - Interactive Mode"
|
|
27
23
|
puts "Type 'exit', 'quit', or '\\q' to exit"
|
|
@@ -37,17 +33,17 @@ module Botiasloop
|
|
|
37
33
|
end
|
|
38
34
|
|
|
39
35
|
@running = false
|
|
40
|
-
|
|
36
|
+
Logger.info "[CLI] Interactive mode ended"
|
|
41
37
|
rescue Interrupt
|
|
42
38
|
@running = false
|
|
43
39
|
puts "\nGoodbye!"
|
|
44
|
-
|
|
40
|
+
Logger.info "[CLI] Interrupted by user"
|
|
45
41
|
end
|
|
46
42
|
|
|
47
43
|
# Stop the CLI channel
|
|
48
|
-
def
|
|
44
|
+
def stop_listening
|
|
49
45
|
@running = false
|
|
50
|
-
|
|
46
|
+
Logger.info "[CLI] Stopping..."
|
|
51
47
|
end
|
|
52
48
|
|
|
53
49
|
# Check if CLI channel is running
|
|
@@ -70,7 +66,7 @@ module Botiasloop
|
|
|
70
66
|
#
|
|
71
67
|
# @param source_id [String] Source identifier to check
|
|
72
68
|
# @return [Boolean] Always true for CLI
|
|
73
|
-
def authorized?(
|
|
69
|
+
def authorized?(_source_id)
|
|
74
70
|
true
|
|
75
71
|
end
|
|
76
72
|
|
|
@@ -80,16 +76,16 @@ module Botiasloop
|
|
|
80
76
|
# @param user_id [String] User ID
|
|
81
77
|
# @param error [Exception] The error that occurred
|
|
82
78
|
# @param raw_message [Object] Raw message object
|
|
83
|
-
def handle_error(source_id,
|
|
84
|
-
|
|
85
|
-
|
|
79
|
+
def handle_error(source_id, _user_id, error, _raw_message)
|
|
80
|
+
Logger.error "[CLI] Error processing message: #{error.message}"
|
|
81
|
+
send_message(source_id, "Error: #{error.message}")
|
|
86
82
|
end
|
|
87
83
|
|
|
88
|
-
# Deliver a formatted
|
|
84
|
+
# Deliver a formatted message to the CLI
|
|
89
85
|
#
|
|
90
86
|
# @param source_id [String] Source identifier
|
|
91
|
-
# @param formatted_content [String] Formatted
|
|
92
|
-
def
|
|
87
|
+
# @param formatted_content [String] Formatted message content
|
|
88
|
+
def deliver_message(_source_id, formatted_content)
|
|
93
89
|
puts "Agent: #{formatted_content}"
|
|
94
90
|
puts
|
|
95
91
|
end
|