rails_console_ai 0.13.0 → 0.14.0
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/CHANGELOG.md +4 -0
- data/README.md +17 -17
- data/app/controllers/rails_console_ai/application_controller.rb +5 -5
- data/app/controllers/rails_console_ai/sessions_controller.rb +1 -1
- data/app/helpers/rails_console_ai/sessions_helper.rb +1 -1
- data/app/models/rails_console_ai/session.rb +2 -2
- data/app/views/layouts/rails_console_ai/application.html.erb +2 -2
- data/config/routes.rb +1 -1
- data/lib/generators/rails_console_ai/install_generator.rb +3 -3
- data/lib/generators/rails_console_ai/templates/initializer.rb +4 -4
- data/lib/rails_console_ai/channel/base.rb +1 -1
- data/lib/rails_console_ai/channel/console.rb +15 -15
- data/lib/rails_console_ai/channel/slack.rb +2 -2
- data/lib/rails_console_ai/configuration.rb +1 -1
- data/lib/rails_console_ai/console_methods.rb +19 -19
- data/lib/rails_console_ai/context_builder.rb +6 -6
- data/lib/rails_console_ai/conversation_engine.rb +29 -29
- data/lib/rails_console_ai/engine.rb +2 -2
- data/lib/rails_console_ai/executor.rb +11 -11
- data/lib/rails_console_ai/providers/anthropic.rb +1 -1
- data/lib/rails_console_ai/providers/base.rb +3 -3
- data/lib/rails_console_ai/providers/bedrock.rb +1 -1
- data/lib/rails_console_ai/providers/local.rb +1 -1
- data/lib/rails_console_ai/providers/openai.rb +1 -1
- data/lib/rails_console_ai/railtie.rb +6 -6
- data/lib/rails_console_ai/repl.rb +1 -1
- data/lib/rails_console_ai/safety_guards.rb +6 -6
- data/lib/rails_console_ai/session_logger.rb +13 -13
- data/lib/rails_console_ai/slack_bot.rb +12 -12
- data/lib/rails_console_ai/storage/base.rb +1 -1
- data/lib/rails_console_ai/storage/file_storage.rb +1 -1
- data/lib/rails_console_ai/tools/code_tools.rb +1 -1
- data/lib/rails_console_ai/tools/memory_tools.rb +4 -4
- data/lib/rails_console_ai/tools/model_tools.rb +1 -1
- data/lib/rails_console_ai/tools/registry.rb +3 -3
- data/lib/rails_console_ai/tools/schema_tools.rb +1 -1
- data/lib/rails_console_ai/version.rb +2 -2
- data/lib/rails_console_ai.rb +14 -14
- data/lib/tasks/rails_console_ai.rake +2 -2
- metadata +1 -1
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
module
|
|
1
|
+
module RailsConsoleAi
|
|
2
2
|
class ConversationEngine
|
|
3
3
|
attr_reader :history, :total_input_tokens, :total_output_tokens,
|
|
4
4
|
:interactive_session_id, :session_name
|
|
@@ -65,10 +65,10 @@ module RailsConsoleAI
|
|
|
65
65
|
log_session(@_last_log_attrs.merge(console_output: console_capture.string))
|
|
66
66
|
exec_result
|
|
67
67
|
rescue Providers::ProviderError => e
|
|
68
|
-
@channel.display_error("
|
|
68
|
+
@channel.display_error("RailsConsoleAi Error: #{e.message}")
|
|
69
69
|
nil
|
|
70
70
|
rescue => e
|
|
71
|
-
@channel.display_error("
|
|
71
|
+
@channel.display_error("RailsConsoleAi Error: #{e.class}: #{e.message}")
|
|
72
72
|
nil
|
|
73
73
|
end
|
|
74
74
|
|
|
@@ -93,10 +93,10 @@ module RailsConsoleAI
|
|
|
93
93
|
log_session(@_last_log_attrs.merge(console_output: console_capture.string))
|
|
94
94
|
nil
|
|
95
95
|
rescue Providers::ProviderError => e
|
|
96
|
-
@channel.display_error("
|
|
96
|
+
@channel.display_error("RailsConsoleAi Error: #{e.message}")
|
|
97
97
|
nil
|
|
98
98
|
rescue => e
|
|
99
|
-
@channel.display_error("
|
|
99
|
+
@channel.display_error("RailsConsoleAi Error: #{e.class}: #{e.message}")
|
|
100
100
|
nil
|
|
101
101
|
end
|
|
102
102
|
|
|
@@ -115,9 +115,9 @@ module RailsConsoleAI
|
|
|
115
115
|
end
|
|
116
116
|
|
|
117
117
|
def init_guide
|
|
118
|
-
storage =
|
|
118
|
+
storage = RailsConsoleAi.storage
|
|
119
119
|
existing_guide = begin
|
|
120
|
-
content = storage.read(
|
|
120
|
+
content = storage.read(RailsConsoleAi::GUIDE_KEY)
|
|
121
121
|
(content && !content.strip.empty?) ? content.strip : nil
|
|
122
122
|
rescue
|
|
123
123
|
nil
|
|
@@ -134,8 +134,8 @@ module RailsConsoleAI
|
|
|
134
134
|
sys_prompt = init_system_prompt(existing_guide)
|
|
135
135
|
messages = [{ role: :user, content: "Explore this Rails application and generate the application guide." }]
|
|
136
136
|
|
|
137
|
-
original_timeout =
|
|
138
|
-
|
|
137
|
+
original_timeout = RailsConsoleAi.configuration.timeout
|
|
138
|
+
RailsConsoleAi.configuration.timeout = [original_timeout, 120].max
|
|
139
139
|
|
|
140
140
|
result, _ = send_query_with_tools(messages, system_prompt: sys_prompt, tools_override: init_tools)
|
|
141
141
|
|
|
@@ -148,8 +148,8 @@ module RailsConsoleAI
|
|
|
148
148
|
return nil
|
|
149
149
|
end
|
|
150
150
|
|
|
151
|
-
storage.write(
|
|
152
|
-
path = storage.respond_to?(:root_path) ? File.join(storage.root_path,
|
|
151
|
+
storage.write(RailsConsoleAi::GUIDE_KEY, guide_text)
|
|
152
|
+
path = storage.respond_to?(:root_path) ? File.join(storage.root_path, RailsConsoleAi::GUIDE_KEY) : RailsConsoleAi::GUIDE_KEY
|
|
153
153
|
$stdout.puts "\e[32m Guide saved to #{path} (#{guide_text.length} chars)\e[0m"
|
|
154
154
|
display_usage(result)
|
|
155
155
|
nil
|
|
@@ -157,13 +157,13 @@ module RailsConsoleAI
|
|
|
157
157
|
$stdout.puts "\n\e[33m Interrupted.\e[0m"
|
|
158
158
|
nil
|
|
159
159
|
rescue Providers::ProviderError => e
|
|
160
|
-
@channel.display_error("
|
|
160
|
+
@channel.display_error("RailsConsoleAi Error: #{e.message}")
|
|
161
161
|
nil
|
|
162
162
|
rescue => e
|
|
163
|
-
@channel.display_error("
|
|
163
|
+
@channel.display_error("RailsConsoleAi Error: #{e.class}: #{e.message}")
|
|
164
164
|
nil
|
|
165
165
|
ensure
|
|
166
|
-
|
|
166
|
+
RailsConsoleAi.configuration.timeout = original_timeout if original_timeout
|
|
167
167
|
end
|
|
168
168
|
|
|
169
169
|
# --- Interactive session management ---
|
|
@@ -250,7 +250,7 @@ module RailsConsoleAI
|
|
|
250
250
|
if e.message.include?("prompt is too long") && @history.length >= 6
|
|
251
251
|
@channel.display_warning(" Context limit reached. Run /compact to reduce context size, then try again.")
|
|
252
252
|
else
|
|
253
|
-
@channel.display_error("
|
|
253
|
+
@channel.display_error("RailsConsoleAi Error: #{e.class}: #{e.message}")
|
|
254
254
|
end
|
|
255
255
|
return :error
|
|
256
256
|
rescue Interrupt
|
|
@@ -271,7 +271,7 @@ module RailsConsoleAI
|
|
|
271
271
|
return :no_code unless code && !code.strip.empty?
|
|
272
272
|
return :cancelled if @channel.cancelled?
|
|
273
273
|
|
|
274
|
-
exec_result = if
|
|
274
|
+
exec_result = if RailsConsoleAi.configuration.auto_execute
|
|
275
275
|
@executor.execute(code)
|
|
276
276
|
else
|
|
277
277
|
@executor.confirm_and_execute(code)
|
|
@@ -352,7 +352,7 @@ module RailsConsoleAI
|
|
|
352
352
|
|
|
353
353
|
@token_usage.each do |model, usage|
|
|
354
354
|
pricing = Configuration::PRICING[model]
|
|
355
|
-
pricing ||= { input: 0.0, output: 0.0 } if
|
|
355
|
+
pricing ||= { input: 0.0, output: 0.0 } if RailsConsoleAi.configuration.provider == :local
|
|
356
356
|
input_str = "in: #{format_tokens(usage[:input])}"
|
|
357
357
|
output_str = "out: #{format_tokens(usage[:output])}"
|
|
358
358
|
|
|
@@ -406,7 +406,7 @@ module RailsConsoleAI
|
|
|
406
406
|
end
|
|
407
407
|
|
|
408
408
|
def upgrade_to_thinking_model
|
|
409
|
-
config =
|
|
409
|
+
config = RailsConsoleAi.configuration
|
|
410
410
|
current = config.resolved_model
|
|
411
411
|
thinking = config.resolved_thinking_model
|
|
412
412
|
|
|
@@ -558,7 +558,7 @@ module RailsConsoleAI
|
|
|
558
558
|
private
|
|
559
559
|
|
|
560
560
|
def safety_context
|
|
561
|
-
guards =
|
|
561
|
+
guards = RailsConsoleAi.configuration.safety_guards
|
|
562
562
|
return nil if guards.empty?
|
|
563
563
|
|
|
564
564
|
if !@channel.supports_danger?
|
|
@@ -597,7 +597,7 @@ module RailsConsoleAI
|
|
|
597
597
|
has_code = code && !code.strip.empty?
|
|
598
598
|
|
|
599
599
|
if has_code
|
|
600
|
-
exec_result = if
|
|
600
|
+
exec_result = if RailsConsoleAi.configuration.auto_execute
|
|
601
601
|
@executor.execute(code)
|
|
602
602
|
else
|
|
603
603
|
@executor.confirm_and_execute(code)
|
|
@@ -682,7 +682,7 @@ module RailsConsoleAI
|
|
|
682
682
|
end
|
|
683
683
|
|
|
684
684
|
def send_query(query, conversation: nil)
|
|
685
|
-
|
|
685
|
+
RailsConsoleAi.configuration.validate!
|
|
686
686
|
|
|
687
687
|
messages = if conversation
|
|
688
688
|
conversation.dup
|
|
@@ -699,7 +699,7 @@ module RailsConsoleAI
|
|
|
699
699
|
require 'rails_console_ai/tools/registry'
|
|
700
700
|
tools = tools_override || Tools::Registry.new(executor: @executor, channel: @channel)
|
|
701
701
|
active_system_prompt = system_prompt || context
|
|
702
|
-
max_rounds =
|
|
702
|
+
max_rounds = RailsConsoleAi.configuration.max_tool_rounds
|
|
703
703
|
total_input = 0
|
|
704
704
|
total_output = 0
|
|
705
705
|
result = nil
|
|
@@ -726,7 +726,7 @@ module RailsConsoleAI
|
|
|
726
726
|
@channel.display_dim(" #{llm_status(round, messages, total_input, last_thinking, last_tool_names)}")
|
|
727
727
|
end
|
|
728
728
|
|
|
729
|
-
if
|
|
729
|
+
if RailsConsoleAi.configuration.debug
|
|
730
730
|
debug_pre_call(round, messages, active_system_prompt, tools, total_input, total_output)
|
|
731
731
|
end
|
|
732
732
|
|
|
@@ -742,7 +742,7 @@ module RailsConsoleAI
|
|
|
742
742
|
|
|
743
743
|
break if @channel.cancelled?
|
|
744
744
|
|
|
745
|
-
if
|
|
745
|
+
if RailsConsoleAi.configuration.debug
|
|
746
746
|
debug_post_call(round, result, @total_input_tokens + total_input, @total_output_tokens + total_output)
|
|
747
747
|
end
|
|
748
748
|
|
|
@@ -770,7 +770,7 @@ module RailsConsoleAI
|
|
|
770
770
|
@channel.display_dim(" #{preview}#{cached_tag}")
|
|
771
771
|
end
|
|
772
772
|
|
|
773
|
-
if
|
|
773
|
+
if RailsConsoleAi.configuration.debug
|
|
774
774
|
$stderr.puts "\e[35m[debug] tool result (#{tool_result.to_s.length} chars)\e[0m"
|
|
775
775
|
end
|
|
776
776
|
|
|
@@ -786,7 +786,7 @@ module RailsConsoleAI
|
|
|
786
786
|
end
|
|
787
787
|
|
|
788
788
|
if exhausted
|
|
789
|
-
$stdout.puts "\e[33m Hit tool round limit (#{max_rounds}). Forcing final answer. Increase with:
|
|
789
|
+
$stdout.puts "\e[33m Hit tool round limit (#{max_rounds}). Forcing final answer. Increase with: RailsConsoleAi.configure { |c| c.max_tool_rounds = 200 }\e[0m"
|
|
790
790
|
messages << { role: :user, content: "You've used all available tool rounds. Please provide your best answer now based on what you've learned so far." }
|
|
791
791
|
result = provider.chat(messages, system_prompt: active_system_prompt)
|
|
792
792
|
total_input += result.input_tokens || 0
|
|
@@ -806,7 +806,7 @@ module RailsConsoleAI
|
|
|
806
806
|
@total_input_tokens += result.input_tokens || 0
|
|
807
807
|
@total_output_tokens += result.output_tokens || 0
|
|
808
808
|
|
|
809
|
-
model =
|
|
809
|
+
model = RailsConsoleAi.configuration.resolved_model
|
|
810
810
|
@token_usage[model][:input] += result.input_tokens || 0
|
|
811
811
|
@token_usage[model][:output] += result.output_tokens || 0
|
|
812
812
|
@token_usage[model][:cache_read] = (@token_usage[model][:cache_read] || 0) + (result.cache_read_input_tokens || 0)
|
|
@@ -1006,9 +1006,9 @@ module RailsConsoleAI
|
|
|
1006
1006
|
|
|
1007
1007
|
input_t = result.input_tokens || 0
|
|
1008
1008
|
output_t = result.output_tokens || 0
|
|
1009
|
-
model =
|
|
1009
|
+
model = RailsConsoleAi.configuration.resolved_model
|
|
1010
1010
|
pricing = Configuration::PRICING[model]
|
|
1011
|
-
pricing ||= { input: 0.0, output: 0.0 } if
|
|
1011
|
+
pricing ||= { input: 0.0, output: 0.0 } if RailsConsoleAi.configuration.provider == :local
|
|
1012
1012
|
|
|
1013
1013
|
cache_r = result.cache_read_input_tokens || 0
|
|
1014
1014
|
cache_w = result.cache_write_input_tokens || 0
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
require 'stringio'
|
|
2
2
|
|
|
3
|
-
module
|
|
3
|
+
module RailsConsoleAi
|
|
4
4
|
# Writes to two IO streams simultaneously
|
|
5
5
|
class TeeIO
|
|
6
6
|
attr_reader :secondary
|
|
@@ -112,7 +112,7 @@ module RailsConsoleAI
|
|
|
112
112
|
|
|
113
113
|
@last_output = captured_output.string
|
|
114
114
|
result
|
|
115
|
-
rescue
|
|
115
|
+
rescue RailsConsoleAi::SafetyError => e
|
|
116
116
|
$stdout = old_stdout if old_stdout
|
|
117
117
|
@last_error = "SafetyError: #{e.message}"
|
|
118
118
|
@last_safety_error = true
|
|
@@ -280,7 +280,7 @@ module RailsConsoleAI
|
|
|
280
280
|
case answer
|
|
281
281
|
when 'a', 'allow'
|
|
282
282
|
if blocked_key && guard
|
|
283
|
-
|
|
283
|
+
RailsConsoleAi.configuration.safety_guards.allow(guard, blocked_key)
|
|
284
284
|
allow_desc = allow_description(guard, blocked_key)
|
|
285
285
|
$stdout.puts colorize("Allowed #{allow_desc} for this session.", :green)
|
|
286
286
|
return execute(code)
|
|
@@ -328,7 +328,7 @@ module RailsConsoleAI
|
|
|
328
328
|
end
|
|
329
329
|
|
|
330
330
|
def execute_unsafe(code)
|
|
331
|
-
guards =
|
|
331
|
+
guards = RailsConsoleAi.configuration.safety_guards
|
|
332
332
|
guards.disable!
|
|
333
333
|
execute(code)
|
|
334
334
|
ensure
|
|
@@ -336,7 +336,7 @@ module RailsConsoleAI
|
|
|
336
336
|
end
|
|
337
337
|
|
|
338
338
|
def execute_prompt
|
|
339
|
-
guards =
|
|
339
|
+
guards = RailsConsoleAi.configuration.safety_guards
|
|
340
340
|
if !guards.empty? && guards.enabled? && danger_allowed?
|
|
341
341
|
"Execute? [y/N/edit/danger] "
|
|
342
342
|
else
|
|
@@ -345,26 +345,26 @@ module RailsConsoleAI
|
|
|
345
345
|
end
|
|
346
346
|
|
|
347
347
|
def with_safety_guards(&block)
|
|
348
|
-
|
|
348
|
+
RailsConsoleAi.configuration.safety_guards.wrap(&block)
|
|
349
349
|
end
|
|
350
350
|
|
|
351
351
|
# Check if an exception is or wraps a SafetyError (e.g. AR::StatementInvalid wrapping it)
|
|
352
352
|
def safety_error?(exception)
|
|
353
|
-
return true if exception.is_a?(
|
|
354
|
-
return true if exception.message.include?("
|
|
353
|
+
return true if exception.is_a?(RailsConsoleAi::SafetyError)
|
|
354
|
+
return true if exception.message.include?("RailsConsoleAi safe mode")
|
|
355
355
|
cause = exception.cause
|
|
356
356
|
while cause
|
|
357
|
-
return true if cause.is_a?(
|
|
357
|
+
return true if cause.is_a?(RailsConsoleAi::SafetyError)
|
|
358
358
|
cause = cause.cause
|
|
359
359
|
end
|
|
360
360
|
false
|
|
361
361
|
end
|
|
362
362
|
|
|
363
363
|
def extract_safety_exception(exception)
|
|
364
|
-
return exception if exception.is_a?(
|
|
364
|
+
return exception if exception.is_a?(RailsConsoleAi::SafetyError)
|
|
365
365
|
cause = exception.cause
|
|
366
366
|
while cause
|
|
367
|
-
return cause if cause.is_a?(
|
|
367
|
+
return cause if cause.is_a?(RailsConsoleAi::SafetyError)
|
|
368
368
|
cause = cause.cause
|
|
369
369
|
end
|
|
370
370
|
nil
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
require 'faraday'
|
|
2
2
|
require 'json'
|
|
3
3
|
|
|
4
|
-
module
|
|
4
|
+
module RailsConsoleAi
|
|
5
5
|
module Providers
|
|
6
6
|
class Base
|
|
7
7
|
attr_reader :config
|
|
8
8
|
|
|
9
|
-
def initialize(config =
|
|
9
|
+
def initialize(config = RailsConsoleAi.configuration)
|
|
10
10
|
@config = config
|
|
11
11
|
end
|
|
12
12
|
|
|
@@ -95,7 +95,7 @@ module RailsConsoleAI
|
|
|
95
95
|
end
|
|
96
96
|
end
|
|
97
97
|
|
|
98
|
-
def self.build(config =
|
|
98
|
+
def self.build(config = RailsConsoleAi.configuration)
|
|
99
99
|
case config.provider
|
|
100
100
|
when :anthropic
|
|
101
101
|
require 'rails_console_ai/providers/anthropic'
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
require 'rails_console_ai'
|
|
2
2
|
|
|
3
|
-
module
|
|
3
|
+
module RailsConsoleAi
|
|
4
4
|
class Railtie < Rails::Railtie
|
|
5
5
|
rake_tasks do
|
|
6
6
|
load File.expand_path('../tasks/rails_console_ai.rake', __dir__)
|
|
@@ -11,23 +11,23 @@ module RailsConsoleAI
|
|
|
11
11
|
|
|
12
12
|
# Inject into IRB if available
|
|
13
13
|
if defined?(IRB::ExtendCommandBundle)
|
|
14
|
-
IRB::ExtendCommandBundle.include(
|
|
14
|
+
IRB::ExtendCommandBundle.include(RailsConsoleAi::ConsoleMethods)
|
|
15
15
|
end
|
|
16
16
|
|
|
17
17
|
# Extend TOPLEVEL_BINDING's receiver as well
|
|
18
|
-
TOPLEVEL_BINDING.eval('self').extend(
|
|
18
|
+
TOPLEVEL_BINDING.eval('self').extend(RailsConsoleAi::ConsoleMethods)
|
|
19
19
|
|
|
20
20
|
# Welcome message
|
|
21
21
|
if $stdout.respond_to?(:tty?) && $stdout.tty?
|
|
22
|
-
$stdout.puts "\e[36m[
|
|
22
|
+
$stdout.puts "\e[36m[RailsConsoleAi v#{RailsConsoleAi::VERSION}] AI assistant loaded. Try: ai \"show me all tables\"\e[0m"
|
|
23
23
|
end
|
|
24
24
|
|
|
25
25
|
# Pre-build context in background
|
|
26
26
|
Thread.new do
|
|
27
27
|
require 'rails_console_ai/context_builder'
|
|
28
|
-
|
|
28
|
+
RailsConsoleAi::ContextBuilder.new.build
|
|
29
29
|
rescue => e
|
|
30
|
-
|
|
30
|
+
RailsConsoleAi.logger.debug("RailsConsoleAi: background context build failed: #{e.message}")
|
|
31
31
|
end
|
|
32
32
|
end
|
|
33
33
|
end
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
module
|
|
1
|
+
module RailsConsoleAi
|
|
2
2
|
# Raised by safety guards to block dangerous operations.
|
|
3
3
|
# Host apps should raise this error in their custom guards.
|
|
4
|
-
#
|
|
4
|
+
# RailsConsoleAi will catch it and guide the user to use 'd' or /danger.
|
|
5
5
|
class SafetyError < StandardError
|
|
6
6
|
attr_reader :guard, :blocked_key
|
|
7
7
|
|
|
@@ -98,10 +98,10 @@ module RailsConsoleAI
|
|
|
98
98
|
return unless Thread.current[:rails_console_ai_block_writes] && sql.match?(WRITE_PATTERN)
|
|
99
99
|
|
|
100
100
|
table = sql.match(TABLE_PATTERN)&.captures&.first
|
|
101
|
-
guards =
|
|
101
|
+
guards = RailsConsoleAi.configuration.safety_guards
|
|
102
102
|
return if table && guards.allowed?(:database_writes, table)
|
|
103
103
|
|
|
104
|
-
raise
|
|
104
|
+
raise RailsConsoleAi::SafetyError.new(
|
|
105
105
|
"Database write blocked: #{sql.strip.split(/\s+/).first(3).join(' ')}...",
|
|
106
106
|
guard: :database_writes,
|
|
107
107
|
blocked_key: table
|
|
@@ -157,9 +157,9 @@ module RailsConsoleAI
|
|
|
157
157
|
def request(req, *args, &block)
|
|
158
158
|
if Thread.current[:rails_console_ai_block_http] && !SAFE_METHODS.include?(req.method)
|
|
159
159
|
host = @address.to_s
|
|
160
|
-
guards =
|
|
160
|
+
guards = RailsConsoleAi.configuration.safety_guards
|
|
161
161
|
unless guards.allowed?(:http_mutations, host)
|
|
162
|
-
raise
|
|
162
|
+
raise RailsConsoleAi::SafetyError.new(
|
|
163
163
|
"HTTP #{req.method} blocked (#{host}#{req.path})",
|
|
164
164
|
guard: :http_mutations,
|
|
165
165
|
blocked_key: host
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
module
|
|
1
|
+
module RailsConsoleAi
|
|
2
2
|
module SessionLogger
|
|
3
3
|
class << self
|
|
4
4
|
def log(attrs)
|
|
5
|
-
return unless
|
|
5
|
+
return unless RailsConsoleAi.configuration.session_logging
|
|
6
6
|
return unless table_exists?
|
|
7
7
|
|
|
8
8
|
create_attrs = {
|
|
@@ -18,8 +18,8 @@ module RailsConsoleAI
|
|
|
18
18
|
code_result: attrs[:code_result],
|
|
19
19
|
console_output: attrs[:console_output],
|
|
20
20
|
executed: attrs[:executed] || false,
|
|
21
|
-
provider:
|
|
22
|
-
model:
|
|
21
|
+
provider: RailsConsoleAi.configuration.provider.to_s,
|
|
22
|
+
model: RailsConsoleAi.configuration.resolved_model,
|
|
23
23
|
duration_ms: attrs[:duration_ms],
|
|
24
24
|
created_at: Time.respond_to?(:current) ? Time.current : Time.now
|
|
25
25
|
}
|
|
@@ -27,24 +27,24 @@ module RailsConsoleAI
|
|
|
27
27
|
record = session_class.create!(create_attrs)
|
|
28
28
|
record.id
|
|
29
29
|
rescue => e
|
|
30
|
-
msg = "
|
|
30
|
+
msg = "RailsConsoleAi: session logging failed: #{e.class}: #{e.message}"
|
|
31
31
|
$stderr.puts "\e[33m#{msg}\e[0m" if $stderr.respond_to?(:puts)
|
|
32
|
-
|
|
32
|
+
RailsConsoleAi.logger.warn(msg)
|
|
33
33
|
nil
|
|
34
34
|
end
|
|
35
35
|
|
|
36
36
|
def find_by_slack_thread(thread_ts)
|
|
37
|
-
return nil unless
|
|
37
|
+
return nil unless RailsConsoleAi.configuration.session_logging
|
|
38
38
|
return nil unless table_exists?
|
|
39
39
|
session_class.where(slack_thread_ts: thread_ts).order(created_at: :desc).first
|
|
40
40
|
rescue => e
|
|
41
|
-
|
|
41
|
+
RailsConsoleAi.logger.warn("RailsConsoleAi: session lookup failed: #{e.class}: #{e.message}")
|
|
42
42
|
nil
|
|
43
43
|
end
|
|
44
44
|
|
|
45
45
|
def update(id, attrs)
|
|
46
46
|
return unless id
|
|
47
|
-
return unless
|
|
47
|
+
return unless RailsConsoleAi.configuration.session_logging
|
|
48
48
|
return unless table_exists?
|
|
49
49
|
|
|
50
50
|
updates = {}
|
|
@@ -61,9 +61,9 @@ module RailsConsoleAI
|
|
|
61
61
|
|
|
62
62
|
session_class.where(id: id).update_all(updates) unless updates.empty?
|
|
63
63
|
rescue => e
|
|
64
|
-
msg = "
|
|
64
|
+
msg = "RailsConsoleAi: session update failed: #{e.class}: #{e.message}"
|
|
65
65
|
$stderr.puts "\e[33m#{msg}\e[0m" if $stderr.respond_to?(:puts)
|
|
66
|
-
|
|
66
|
+
RailsConsoleAi.logger.warn(msg)
|
|
67
67
|
nil
|
|
68
68
|
end
|
|
69
69
|
|
|
@@ -79,11 +79,11 @@ module RailsConsoleAI
|
|
|
79
79
|
end
|
|
80
80
|
|
|
81
81
|
def session_class
|
|
82
|
-
Object.const_get('
|
|
82
|
+
Object.const_get('RailsConsoleAi::Session')
|
|
83
83
|
end
|
|
84
84
|
|
|
85
85
|
def current_user_name
|
|
86
|
-
|
|
86
|
+
RailsConsoleAi.current_user || ENV['USER']
|
|
87
87
|
end
|
|
88
88
|
end
|
|
89
89
|
end
|
|
@@ -8,16 +8,16 @@ require 'rails_console_ai/context_builder'
|
|
|
8
8
|
require 'rails_console_ai/providers/base'
|
|
9
9
|
require 'rails_console_ai/executor'
|
|
10
10
|
|
|
11
|
-
module
|
|
11
|
+
module RailsConsoleAi
|
|
12
12
|
class SlackBot
|
|
13
13
|
def initialize
|
|
14
|
-
@bot_token =
|
|
15
|
-
@app_token =
|
|
14
|
+
@bot_token = RailsConsoleAi.configuration.slack_bot_token || ENV['SLACK_BOT_TOKEN']
|
|
15
|
+
@app_token = RailsConsoleAi.configuration.slack_app_token || ENV['SLACK_APP_TOKEN']
|
|
16
16
|
@channel_ids = resolve_channel_ids
|
|
17
17
|
|
|
18
18
|
raise ConfigurationError, "SLACK_BOT_TOKEN is required" unless @bot_token
|
|
19
19
|
raise ConfigurationError, "SLACK_APP_TOKEN is required (Socket Mode)" unless @app_token
|
|
20
|
-
raise ConfigurationError, "slack_allowed_usernames must be configured (e.g. ['alice'] or 'ALL')" unless
|
|
20
|
+
raise ConfigurationError, "slack_allowed_usernames must be configured (e.g. ['alice'] or 'ALL')" unless RailsConsoleAi.configuration.slack_allowed_usernames
|
|
21
21
|
|
|
22
22
|
@bot_user_id = nil
|
|
23
23
|
@sessions = {} # thread_ts → { channel:, engine:, thread: }
|
|
@@ -242,7 +242,7 @@ module RailsConsoleAI
|
|
|
242
242
|
user_id = event[:user]
|
|
243
243
|
user_name = resolve_user_name(user_id)
|
|
244
244
|
|
|
245
|
-
allowed_list = Array(
|
|
245
|
+
allowed_list = Array(RailsConsoleAi.configuration.slack_allowed_usernames).map(&:to_s).map(&:downcase)
|
|
246
246
|
unless allowed_list.include?('all') || allowed_list.include?(user_name.to_s.downcase)
|
|
247
247
|
puts "[#{channel_id}/#{thread_ts}] @#{user_name} << (ignored — not in allowed usernames)"
|
|
248
248
|
post_message(channel: channel_id, thread_ts: thread_ts, text: "Sorry, I don't recognize your username (@#{user_name}). Ask an admin to add you to the allowed usernames list.")
|
|
@@ -283,7 +283,7 @@ module RailsConsoleAI
|
|
|
283
283
|
start_session(channel_id, thread_ts, text.strip, user_name)
|
|
284
284
|
end
|
|
285
285
|
rescue => e
|
|
286
|
-
|
|
286
|
+
RailsConsoleAi.logger.error("SlackBot event handling error: #{e.class}: #{e.message}")
|
|
287
287
|
end
|
|
288
288
|
|
|
289
289
|
def start_session(channel_id, thread_ts, text, user_name)
|
|
@@ -318,7 +318,7 @@ module RailsConsoleAI
|
|
|
318
318
|
engine.process_message(text)
|
|
319
319
|
rescue => e
|
|
320
320
|
channel.display_error("Error: #{e.class}: #{e.message}")
|
|
321
|
-
|
|
321
|
+
RailsConsoleAi.logger.error("SlackBot session error: #{e.class}: #{e.message}\n#{e.backtrace.first(5).join("\n")}")
|
|
322
322
|
ensure
|
|
323
323
|
ActiveRecord::Base.clear_active_connections! if defined?(ActiveRecord::Base)
|
|
324
324
|
end
|
|
@@ -334,7 +334,7 @@ module RailsConsoleAI
|
|
|
334
334
|
engine.restore_session(saved)
|
|
335
335
|
true
|
|
336
336
|
rescue => e
|
|
337
|
-
|
|
337
|
+
RailsConsoleAi.logger.warn("SlackBot: failed to restore session for #{thread_ts}: #{e.message}")
|
|
338
338
|
false
|
|
339
339
|
end
|
|
340
340
|
|
|
@@ -355,7 +355,7 @@ module RailsConsoleAI
|
|
|
355
355
|
engine.process_message(text)
|
|
356
356
|
rescue => e
|
|
357
357
|
channel.display_error("Error: #{e.class}: #{e.message}")
|
|
358
|
-
|
|
358
|
+
RailsConsoleAi.logger.error("SlackBot session error: #{e.class}: #{e.message}\n#{e.backtrace.first(5).join("\n")}")
|
|
359
359
|
ensure
|
|
360
360
|
ActiveRecord::Base.clear_active_connections! if defined?(ActiveRecord::Base)
|
|
361
361
|
end
|
|
@@ -422,7 +422,7 @@ module RailsConsoleAI
|
|
|
422
422
|
end
|
|
423
423
|
|
|
424
424
|
def resolve_channel_ids
|
|
425
|
-
ids =
|
|
425
|
+
ids = RailsConsoleAi.configuration.slack_channel_ids || ENV['CONSOLE_AGENT_SLACK_CHANNELS']
|
|
426
426
|
return nil if ids.nil?
|
|
427
427
|
ids = ids.split(',').map(&:strip) if ids.is_a?(String)
|
|
428
428
|
ids
|
|
@@ -446,7 +446,7 @@ module RailsConsoleAI
|
|
|
446
446
|
name = result.dig("user", "name") if name.nil? || name.empty?
|
|
447
447
|
@user_cache[user_id] = name || user_id
|
|
448
448
|
rescue => e
|
|
449
|
-
|
|
449
|
+
RailsConsoleAi.logger.warn("Failed to resolve user name for #{user_id}: #{e.message}")
|
|
450
450
|
@user_cache[user_id] = user_id
|
|
451
451
|
end
|
|
452
452
|
|
|
@@ -456,7 +456,7 @@ module RailsConsoleAI
|
|
|
456
456
|
else
|
|
457
457
|
"all channels"
|
|
458
458
|
end
|
|
459
|
-
puts "
|
|
459
|
+
puts "RailsConsoleAi SlackBot started (#{channel_info}, bot: #{@bot_user_id})"
|
|
460
460
|
|
|
461
461
|
channel = Channel::Slack.new(slack_bot: self, channel_id: "boot", thread_ts: "boot")
|
|
462
462
|
engine = ConversationEngine.new(
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
require 'yaml'
|
|
2
2
|
|
|
3
|
-
module
|
|
3
|
+
module RailsConsoleAi
|
|
4
4
|
module Tools
|
|
5
5
|
class MemoryTools
|
|
6
6
|
MEMORIES_DIR = 'memories'
|
|
7
7
|
|
|
8
8
|
def initialize(storage = nil)
|
|
9
|
-
@storage = storage ||
|
|
9
|
+
@storage = storage || RailsConsoleAi.storage
|
|
10
10
|
end
|
|
11
11
|
|
|
12
12
|
def save_memory(name:, description:, tags: [])
|
|
@@ -105,7 +105,7 @@ module RailsConsoleAI
|
|
|
105
105
|
return nil if content.nil? || content.strip.empty?
|
|
106
106
|
parse_memory(content)
|
|
107
107
|
rescue => e
|
|
108
|
-
|
|
108
|
+
RailsConsoleAi.logger.warn("RailsConsoleAi: failed to load memory #{key}: #{e.message}")
|
|
109
109
|
nil
|
|
110
110
|
end
|
|
111
111
|
|
|
@@ -113,7 +113,7 @@ module RailsConsoleAI
|
|
|
113
113
|
keys = @storage.list("#{MEMORIES_DIR}/*.md")
|
|
114
114
|
keys.map { |key| load_memory(key) }.compact
|
|
115
115
|
rescue => e
|
|
116
|
-
|
|
116
|
+
RailsConsoleAi.logger.warn("RailsConsoleAi: failed to load memories: #{e.message}")
|
|
117
117
|
[]
|
|
118
118
|
end
|
|
119
119
|
|