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.
@@ -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,6 +44,12 @@ module Botiasloop
43
44
  total_output_tokens += response.output_tokens || 0
44
45
 
45
46
  if response.tool_call?
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
+
46
53
  # Add the assistant's message with tool_calls first
47
54
  messages << response
48
55
 
@@ -51,7 +58,9 @@ module Botiasloop
51
58
  messages << build_tool_result_message(tool_call.id, observation)
52
59
  end
53
60
  else
54
- conversation.add("assistant", response.content, input_tokens: total_input_tokens, output_tokens: total_output_tokens)
61
+ conversation.add("assistant", response.content, input_tokens: total_input_tokens,
62
+ output_tokens: total_output_tokens)
63
+ maybe_auto_label(conversation)
55
64
  return response.content
56
65
  end
57
66
  end
@@ -96,20 +105,92 @@ module Botiasloop
96
105
  end
97
106
 
98
107
  def execute_tool(tool_call)
99
- @logger.info "[Tool] Executing #{tool_call.name} with arguments: #{tool_call.arguments}"
108
+ Logger.info "[Tool] Executing #{tool_call.name} with arguments: #{tool_call.arguments}"
109
+
110
+ if @conversation.verbose && @verbose_callback
111
+ verbose_content = format_tool_message(tool_call)
112
+ @verbose_callback.call(verbose_content)
113
+ end
114
+
100
115
  retries = 0
101
116
  begin
102
117
  result = @registry.execute(tool_call.name, tool_call.arguments)
103
- build_observation(result)
118
+ observation = build_observation(result)
119
+
120
+ if @conversation.verbose && @verbose_callback
121
+ result_content = format_result_message(observation)
122
+ @verbose_callback.call(result_content)
123
+ end
124
+
125
+ observation
104
126
  rescue Error => e
105
127
  retries += 1
106
128
  retry if retries < MAX_TOOL_RETRIES
107
- "Error: #{e.message}"
129
+ error_content = "Error: #{e.message}"
130
+ @verbose_callback.call(format_error_message(error_content)) if @conversation.verbose && @verbose_callback
131
+ error_content
108
132
  end
109
133
  end
110
134
 
135
+ def format_error_message(error_content)
136
+ error_msg = "⚠️ **Error**"
137
+
138
+ if error_content && !error_content.empty?
139
+ error_display = error_content.to_s
140
+ error_display = error_display[0..500] + "..." if error_display.length > 500
141
+ error_msg += "\n```\n#{error_display}\n```"
142
+ end
143
+
144
+ error_msg
145
+ end
146
+
147
+ def format_reasoning_message(content)
148
+ reasoning_msg = "💭 **Reasoning**"
149
+
150
+ if content && !content.empty?
151
+ reasoning_display = content.to_s
152
+ reasoning_display = reasoning_display[0..500] + "..." if reasoning_display.length > 500
153
+ reasoning_msg += "```\n#{reasoning_display}\n```"
154
+ end
155
+
156
+ reasoning_msg
157
+ end
158
+
159
+ def format_tool_message(tool_call)
160
+ tool_msg = "🔧 **Tool** `#{tool_call.name}`"
161
+
162
+ if tool_call.arguments && !tool_call.arguments.empty?
163
+ args_display = JSON.pretty_generate(tool_call.arguments)
164
+ args_display = args_display[0..500] + "..." if args_display.length > 500
165
+ tool_msg += "\n```\n#{args_display}\n```"
166
+ end
167
+
168
+ tool_msg
169
+ end
170
+
171
+ def format_result_message(observation)
172
+ result_msg = "📥 **Result**"
173
+
174
+ if observation && !observation.empty?
175
+ result_display = observation.to_s
176
+ result_display = result_display[0..500] + "..." if result_display.length > 500
177
+ result_msg += "```\n#{result_display}\n```"
178
+ else
179
+ result_msg += " (empty)"
180
+ end
181
+
182
+ result_msg
183
+ end
184
+
111
185
  def build_observation(result)
112
186
  result.to_s
113
187
  end
188
+
189
+ def maybe_auto_label(conversation)
190
+ label = AutoLabel.generate(conversation)
191
+ return unless label
192
+
193
+ conversation.update(label: label)
194
+ end
114
195
  end
115
196
  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.7"
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.7
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: []