botiasloop 0.0.1 → 0.0.8

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.
@@ -1,6 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "logger"
4
3
  require "json"
5
4
 
6
5
  module Botiasloop
@@ -18,16 +17,18 @@ module Botiasloop
18
17
  @model = model
19
18
  @registry = registry
20
19
  @max_iterations = max_iterations
21
- @logger = Logger.new($stderr)
22
20
  end
23
21
 
24
22
  # Run the ReAct loop
25
23
  #
26
24
  # @param conversation [Conversation] Conversation instance
27
25
  # @param user_input [String] User input
26
+ # @param verbose_callback [Proc, nil] Callback for verbose messages
28
27
  # @return [String] Final response
29
28
  # @raise [Error] If max iterations exceeded
30
- def run(conversation, user_input)
29
+ def run(conversation, user_input, verbose_callback = nil)
30
+ @conversation = conversation
31
+ @verbose_callback = verbose_callback
31
32
  conversation.add("user", user_input)
32
33
  messages = build_messages(conversation)
33
34
 
@@ -43,15 +44,26 @@ module Botiasloop
43
44
  total_output_tokens += response.output_tokens || 0
44
45
 
45
46
  if response.tool_call?
46
- # Add the assistant's message with tool_calls first
47
- messages << response
47
+ # Send reasoning to verbose callback if verbose mode enabled
48
+ if @conversation.verbose && @verbose_callback && response.content && !response.content.empty?
49
+ reasoning_content = format_reasoning_message(response.content)
50
+ @verbose_callback.call(reasoning_content)
51
+ end
52
+
53
+ # Add the assistant's message as a RubyLLM::Message object
54
+ messages << RubyLLM::Message.new(
55
+ role: :assistant,
56
+ content: response.content || ""
57
+ )
48
58
 
49
59
  response.tool_calls.each_value do |tool_call|
50
60
  observation = execute_tool(tool_call)
51
61
  messages << build_tool_result_message(tool_call.id, observation)
52
62
  end
53
63
  else
54
- conversation.add("assistant", response.content, input_tokens: total_input_tokens, output_tokens: total_output_tokens)
64
+ conversation.add("assistant", response.content, input_tokens: total_input_tokens,
65
+ output_tokens: total_output_tokens)
66
+ maybe_auto_label(conversation)
55
67
  return response.content
56
68
  end
57
69
  end
@@ -96,20 +108,92 @@ module Botiasloop
96
108
  end
97
109
 
98
110
  def execute_tool(tool_call)
99
- @logger.info "[Tool] Executing #{tool_call.name} with arguments: #{tool_call.arguments}"
111
+ Logger.info "[Tool] Executing #{tool_call.name} with arguments: #{tool_call.arguments}"
112
+
113
+ if @conversation.verbose && @verbose_callback
114
+ verbose_content = format_tool_message(tool_call)
115
+ @verbose_callback.call(verbose_content)
116
+ end
117
+
100
118
  retries = 0
101
119
  begin
102
120
  result = @registry.execute(tool_call.name, tool_call.arguments)
103
- build_observation(result)
121
+ observation = build_observation(result)
122
+
123
+ if @conversation.verbose && @verbose_callback
124
+ result_content = format_result_message(observation)
125
+ @verbose_callback.call(result_content)
126
+ end
127
+
128
+ observation
104
129
  rescue Error => e
105
130
  retries += 1
106
131
  retry if retries < MAX_TOOL_RETRIES
107
- "Error: #{e.message}"
132
+ error_content = "Error: #{e.message}"
133
+ @verbose_callback.call(format_error_message(error_content)) if @conversation.verbose && @verbose_callback
134
+ error_content
108
135
  end
109
136
  end
110
137
 
138
+ def format_error_message(error_content)
139
+ error_msg = "⚠️ **Error**"
140
+
141
+ if error_content && !error_content.empty?
142
+ error_display = error_content.to_s
143
+ error_display = error_display[0..500] + "..." if error_display.length > 500
144
+ error_msg += "\n```\n#{error_display}\n```"
145
+ end
146
+
147
+ error_msg
148
+ end
149
+
150
+ def format_reasoning_message(content)
151
+ reasoning_msg = "💭 **Reasoning**"
152
+
153
+ if content && !content.empty?
154
+ reasoning_display = content.to_s
155
+ reasoning_display = reasoning_display[0..500] + "..." if reasoning_display.length > 500
156
+ reasoning_msg += "```\n#{reasoning_display}\n```"
157
+ end
158
+
159
+ reasoning_msg
160
+ end
161
+
162
+ def format_tool_message(tool_call)
163
+ tool_msg = "🔧 **Tool** `#{tool_call.name}`"
164
+
165
+ if tool_call.arguments && !tool_call.arguments.empty?
166
+ args_display = JSON.pretty_generate(tool_call.arguments)
167
+ args_display = args_display[0..500] + "..." if args_display.length > 500
168
+ tool_msg += "\n```\n#{args_display}\n```"
169
+ end
170
+
171
+ tool_msg
172
+ end
173
+
174
+ def format_result_message(observation)
175
+ result_msg = "📥 **Result**"
176
+
177
+ if observation && !observation.empty?
178
+ result_display = observation.to_s
179
+ result_display = result_display[0..500] + "..." if result_display.length > 500
180
+ result_msg += "```\n#{result_display}\n```"
181
+ else
182
+ result_msg += " (empty)"
183
+ end
184
+
185
+ result_msg
186
+ end
187
+
111
188
  def build_observation(result)
112
189
  result.to_s
113
190
  end
191
+
192
+ def maybe_auto_label(conversation)
193
+ label = AutoLabel.generate(conversation)
194
+ return unless label
195
+
196
+ conversation.update(label: label)
197
+ end
114
198
  end
115
199
  end
@@ -10,27 +10,22 @@ module Botiasloop
10
10
  # automatically on user login and run in the background.
11
11
  #
12
12
  # @example Install and enable the service
13
- # service = Botiasloop::SystemdService.new(config)
13
+ # service = Botiasloop::SystemdService.new
14
14
  # service.install
15
15
  # service.enable
16
16
  # service.start
17
17
  #
18
18
  # @example Check status
19
- # service = Botiasloop::SystemdService.new(config)
19
+ # service = Botiasloop::SystemdService.new
20
20
  # status = service.status
21
21
  # puts "Running: #{status[:active]}"
22
22
  #
23
23
  class SystemdService
24
- attr_reader :config
25
-
26
24
  # Service name used by systemd
27
25
  SERVICE_NAME = "botiasloop.service"
28
26
 
29
27
  # Initialize a new SystemdService instance
30
- #
31
- # @param config [Config] Configuration instance
32
- def initialize(config)
33
- @config = config
28
+ def initialize
34
29
  end
35
30
 
36
31
  # Check if systemd is available on the system
@@ -209,6 +204,21 @@ module Botiasloop
209
204
  }
210
205
  end
211
206
 
207
+ # Display service logs using journalctl
208
+ #
209
+ # @param follow [Boolean] Whether to follow logs in real-time (tail -f mode)
210
+ # @param lines [Integer] Number of lines to show (default: 50)
211
+ # @raise [SystemdError] If systemd is not available
212
+ # @return [Boolean] True on success
213
+ def logs(follow: false, lines: 50)
214
+ raise SystemdError, "systemd is not available on this system" unless systemd_available?
215
+
216
+ args = ["--user", "-u", SERVICE_NAME, "-n", lines.to_s]
217
+ args << (follow ? "-f" : "--no-pager")
218
+
219
+ system("journalctl", *args)
220
+ end
221
+
212
222
  private
213
223
 
214
224
  # Get the systemd user directory path
@@ -232,7 +242,7 @@ module Botiasloop
232
242
  <<~SERVICE
233
243
  [Unit]
234
244
  Description=botiasloop - AI Agent Gateway
235
- Documentation=https://github.com/anomalyco/botiasloop
245
+ Documentation=https://github.com/0x7466/botiasloop
236
246
  After=network.target
237
247
 
238
248
  [Service]
@@ -245,7 +255,7 @@ module Botiasloop
245
255
  Environment="PATH=#{ruby_bin_path}:/usr/local/bin:/usr/bin:/bin"
246
256
 
247
257
  [Install]
248
- WantedBy=multi-user.target
258
+ WantedBy=default.target
249
259
  SERVICE
250
260
  end
251
261
 
@@ -13,9 +13,14 @@ module Botiasloop
13
13
  #
14
14
  # @param command [String] Shell command to execute
15
15
  # @return [Hash] Result with stdout, stderr, exit_code, and success?
16
+ # @raise [Botiasloop::Error] When command execution fails
16
17
  def execute(command:)
17
18
  stdout, stderr, status = Open3.capture3(command)
18
19
  Result.new(stdout, stderr, status.exitstatus).to_h
20
+ rescue Errno::ENOENT => e
21
+ raise Error, "Command not found: #{e.message}"
22
+ rescue Errno::EACCES => e
23
+ raise Error, "Permission denied: #{e.message}"
19
24
  end
20
25
 
21
26
  # Result wrapper for shell execution
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Botiasloop
4
- VERSION = "0.0.1"
4
+ VERSION = "0.0.8"
5
5
  end
data/lib/botiasloop.rb CHANGED
@@ -2,10 +2,13 @@
2
2
 
3
3
  require_relative "botiasloop/version"
4
4
  require_relative "botiasloop/config"
5
+ require_relative "botiasloop/logger"
5
6
  require_relative "botiasloop/database"
7
+ require_relative "botiasloop/human_id"
6
8
 
9
+ require_relative "botiasloop/chat"
7
10
  require_relative "botiasloop/conversation"
8
- require_relative "botiasloop/conversation_manager"
11
+ require_relative "botiasloop/auto_label"
9
12
  require_relative "botiasloop/tool"
10
13
  require_relative "botiasloop/tools/registry"
11
14
  require_relative "botiasloop/tools/shell"
@@ -42,4 +45,8 @@ module Botiasloop
42
45
  super("I've reached my thinking limit (#{max_iterations} iterations). Please try a more specific question.")
43
46
  end
44
47
  end
48
+
49
+ # Eagerly initialize the singleton Agent instance at boot time
50
+ # This ensures thread-safe initialization in single-threaded boot context
51
+ Agent.instance
45
52
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: botiasloop
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.1
4
+ version: 0.0.8
5
5
  platform: ruby
6
6
  authors:
7
7
  - Tobias Feistmantl
@@ -10,33 +10,33 @@ cert_chain: []
10
10
  date: 1980-01-02 00:00:00.000000000 Z
11
11
  dependencies:
12
12
  - !ruby/object:Gem::Dependency
13
- name: ruby_llm
13
+ name: anyway_config
14
14
  requirement: !ruby/object:Gem::Requirement
15
15
  requirements:
16
16
  - - "~>"
17
17
  - !ruby/object:Gem::Version
18
- version: 1.12.1
18
+ version: '2.8'
19
19
  type: :runtime
20
20
  prerelease: false
21
21
  version_requirements: !ruby/object:Gem::Requirement
22
22
  requirements:
23
23
  - - "~>"
24
24
  - !ruby/object:Gem::Version
25
- version: 1.12.1
25
+ version: '2.8'
26
26
  - !ruby/object:Gem::Dependency
27
- name: telegram-bot-ruby
27
+ name: ffaker
28
28
  requirement: !ruby/object:Gem::Requirement
29
29
  requirements:
30
30
  - - "~>"
31
31
  - !ruby/object:Gem::Version
32
- version: '2.5'
32
+ version: 2.25.0
33
33
  type: :runtime
34
34
  prerelease: false
35
35
  version_requirements: !ruby/object:Gem::Requirement
36
36
  requirements:
37
37
  - - "~>"
38
38
  - !ruby/object:Gem::Version
39
- version: '2.5'
39
+ version: 2.25.0
40
40
  - !ruby/object:Gem::Dependency
41
41
  name: redcarpet
42
42
  requirement: !ruby/object:Gem::Requirement
@@ -52,19 +52,19 @@ dependencies:
52
52
  - !ruby/object:Gem::Version
53
53
  version: '3.6'
54
54
  - !ruby/object:Gem::Dependency
55
- name: anyway_config
55
+ name: ruby_llm
56
56
  requirement: !ruby/object:Gem::Requirement
57
57
  requirements:
58
58
  - - "~>"
59
59
  - !ruby/object:Gem::Version
60
- version: '2.8'
60
+ version: 1.12.1
61
61
  type: :runtime
62
62
  prerelease: false
63
63
  version_requirements: !ruby/object:Gem::Requirement
64
64
  requirements:
65
65
  - - "~>"
66
66
  - !ruby/object:Gem::Version
67
- version: '2.8'
67
+ version: 1.12.1
68
68
  - !ruby/object:Gem::Dependency
69
69
  name: sequel
70
70
  requirement: !ruby/object:Gem::Requirement
@@ -94,47 +94,61 @@ dependencies:
94
94
  - !ruby/object:Gem::Version
95
95
  version: '2.9'
96
96
  - !ruby/object:Gem::Dependency
97
- name: rspec
97
+ name: telegram-bot-ruby
98
98
  requirement: !ruby/object:Gem::Requirement
99
99
  requirements:
100
100
  - - "~>"
101
101
  - !ruby/object:Gem::Version
102
- version: 3.13.2
102
+ version: '2.5'
103
+ type: :runtime
104
+ prerelease: false
105
+ version_requirements: !ruby/object:Gem::Requirement
106
+ requirements:
107
+ - - "~>"
108
+ - !ruby/object:Gem::Version
109
+ version: '2.5'
110
+ - !ruby/object:Gem::Dependency
111
+ name: rake
112
+ requirement: !ruby/object:Gem::Requirement
113
+ requirements:
114
+ - - "~>"
115
+ - !ruby/object:Gem::Version
116
+ version: '13.0'
103
117
  type: :development
104
118
  prerelease: false
105
119
  version_requirements: !ruby/object:Gem::Requirement
106
120
  requirements:
107
121
  - - "~>"
108
122
  - !ruby/object:Gem::Version
109
- version: 3.13.2
123
+ version: '13.0'
110
124
  - !ruby/object:Gem::Dependency
111
- name: vcr
125
+ name: rspec
112
126
  requirement: !ruby/object:Gem::Requirement
113
127
  requirements:
114
128
  - - "~>"
115
129
  - !ruby/object:Gem::Version
116
- version: 6.4.0
130
+ version: 3.13.2
117
131
  type: :development
118
132
  prerelease: false
119
133
  version_requirements: !ruby/object:Gem::Requirement
120
134
  requirements:
121
135
  - - "~>"
122
136
  - !ruby/object:Gem::Version
123
- version: 6.4.0
137
+ version: 3.13.2
124
138
  - !ruby/object:Gem::Dependency
125
- name: webmock
139
+ name: simplecov
126
140
  requirement: !ruby/object:Gem::Requirement
127
141
  requirements:
128
142
  - - "~>"
129
143
  - !ruby/object:Gem::Version
130
- version: 3.26.1
144
+ version: 0.22.0
131
145
  type: :development
132
146
  prerelease: false
133
147
  version_requirements: !ruby/object:Gem::Requirement
134
148
  requirements:
135
149
  - - "~>"
136
150
  - !ruby/object:Gem::Version
137
- version: 3.26.1
151
+ version: 0.22.0
138
152
  - !ruby/object:Gem::Dependency
139
153
  name: standard
140
154
  requirement: !ruby/object:Gem::Requirement
@@ -150,33 +164,33 @@ dependencies:
150
164
  - !ruby/object:Gem::Version
151
165
  version: 1.54.0
152
166
  - !ruby/object:Gem::Dependency
153
- name: simplecov
167
+ name: vcr
154
168
  requirement: !ruby/object:Gem::Requirement
155
169
  requirements:
156
170
  - - "~>"
157
171
  - !ruby/object:Gem::Version
158
- version: 0.22.0
172
+ version: 6.4.0
159
173
  type: :development
160
174
  prerelease: false
161
175
  version_requirements: !ruby/object:Gem::Requirement
162
176
  requirements:
163
177
  - - "~>"
164
178
  - !ruby/object:Gem::Version
165
- version: 0.22.0
179
+ version: 6.4.0
166
180
  - !ruby/object:Gem::Dependency
167
- name: rake
181
+ name: webmock
168
182
  requirement: !ruby/object:Gem::Requirement
169
183
  requirements:
170
184
  - - "~>"
171
185
  - !ruby/object:Gem::Version
172
- version: '13.0'
186
+ version: 3.26.1
173
187
  type: :development
174
188
  prerelease: false
175
189
  version_requirements: !ruby/object:Gem::Requirement
176
190
  requirements:
177
191
  - - "~>"
178
192
  - !ruby/object:Gem::Version
179
- version: '13.0'
193
+ version: 3.26.1
180
194
  description: A minimal Ruby gem for building agentic AI applications using the ReAct
181
195
  (Reasoning + Acting) loop pattern
182
196
  email:
@@ -186,6 +200,7 @@ executables:
186
200
  extensions: []
187
201
  extra_rdoc_files: []
188
202
  files:
203
+ - LICENSE
189
204
  - README.md
190
205
  - bin/botiasloop
191
206
  - data/skills/skill-creator/SKILL.md
@@ -193,11 +208,13 @@ files:
193
208
  - data/skills/skill-creator/references/specification.md
194
209
  - lib/botiasloop.rb
195
210
  - lib/botiasloop/agent.rb
211
+ - lib/botiasloop/auto_label.rb
196
212
  - lib/botiasloop/channels.rb
197
213
  - lib/botiasloop/channels/base.rb
198
214
  - lib/botiasloop/channels/cli.rb
199
215
  - lib/botiasloop/channels/telegram.rb
200
216
  - lib/botiasloop/channels_manager.rb
217
+ - lib/botiasloop/chat.rb
201
218
  - lib/botiasloop/commands.rb
202
219
  - lib/botiasloop/commands/archive.rb
203
220
  - lib/botiasloop/commands/base.rb
@@ -212,10 +229,12 @@ files:
212
229
  - lib/botiasloop/commands/status.rb
213
230
  - lib/botiasloop/commands/switch.rb
214
231
  - lib/botiasloop/commands/system_prompt.rb
232
+ - lib/botiasloop/commands/verbose.rb
215
233
  - lib/botiasloop/config.rb
216
234
  - lib/botiasloop/conversation.rb
217
- - lib/botiasloop/conversation_manager.rb
218
235
  - lib/botiasloop/database.rb
236
+ - lib/botiasloop/human_id.rb
237
+ - lib/botiasloop/logger.rb
219
238
  - lib/botiasloop/loop.rb
220
239
  - lib/botiasloop/skills/loader.rb
221
240
  - lib/botiasloop/skills/registry.rb
@@ -244,7 +263,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
244
263
  - !ruby/object:Gem::Version
245
264
  version: '0'
246
265
  requirements: []
247
- rubygems_version: 4.0.6
266
+ rubygems_version: 3.6.9
248
267
  specification_version: 4
249
268
  summary: Minimal agentic AI application with ReAct loop
250
269
  test_files: []