rails_console_ai 0.23.0 → 0.24.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 6f97d0243924a105c21ebd4a86049bef4fdda6ed76d738c1c2850244f8cb6e0e
4
- data.tar.gz: 930e70d14ae27b0d87999b4625f299390bc928646e73417aef2fc5f7f46aaf5f
3
+ metadata.gz: 41b94c2b3a01cba1210242c6c8752510fc1976bf9b7195272f4a109dceadcdab
4
+ data.tar.gz: 6c813abfeeda804b115dfd2a385bc67e1796b0af80cc438e5f41d66886fa519e
5
5
  SHA512:
6
- metadata.gz: 17d56e01106acbeee8e1299c39e2fbcc3c0f3150922b977652f8857139d8ad7c481c13d080216f492833e22287df616eb4967a6d46162d39b618ac51e74bbeb4
7
- data.tar.gz: 259216bdd4cf23d0b6c8634fad2d48d520eac0b4600d16697cdf176e55a3677a5a7975304697be2f36817bf04aaa0f7317459a7e9d79da591cfe49879398cca3
6
+ metadata.gz: 52281835764610528b039d12ac786ec7056d035e9055d5574bee3a3143c9833e8fa5943a5932980c7ee28ae77351a178186dc15b3afb2f0e9acb7c71b1104c5f
7
+ data.tar.gz: d1f56cd511ff3411b6e2783c0790bbc3d012a420971fe2cd400f7f573ffc168f5b6bca5c692618bf5c4ef2eaa9d0d0283958afcf1b7ac71d426dd4f2f4377425
data/CHANGELOG.md CHANGED
@@ -2,6 +2,14 @@
2
2
 
3
3
  All notable changes to this project will be documented in this file.
4
4
 
5
+ ## [0.24.0]
6
+
7
+ - Refactor thinking text display and include in Slack with more technical detail
8
+ - Add `a` command to trigger auto-accept mode
9
+ - Include code output in Slack server logs
10
+ - Fix Bedrock issue after declining code execution
11
+ - Fix `allow_code_execution` configuration
12
+
5
13
  ## [0.23.0]
6
14
 
7
15
  - Add `save_skill` tool
@@ -86,7 +86,7 @@ RailsConsoleAi.configure do |config|
86
86
  # config.channels = {
87
87
  # 'slack' => {
88
88
  # 'allowed_usernames' => ['alice', 'bob'], # who can use the bot (or 'ALL')
89
- # 'allow_code_execution' => ['alice'], # who can run code (nil = everyone)
89
+ # 'allow_code_execution' => ['alice'], # who can run code directly via ``` (nil = everyone)
90
90
  # 'pinned_memory_tags' => ['sharding'],
91
91
  # 'bypass_guards_for_methods' => ['ChangeApproval#approve_by!']
92
92
  # },
@@ -2,7 +2,8 @@ module RailsConsoleAi
2
2
  module Channel
3
3
  class Base
4
4
  def display(text); raise NotImplementedError; end
5
- def display_dim(text); raise NotImplementedError; end
5
+ def display_thinking(text); raise NotImplementedError; end
6
+ def display_status(text); raise NotImplementedError; end
6
7
  def display_warning(text); raise NotImplementedError; end
7
8
  def display_error(text); raise NotImplementedError; end
8
9
  def display_code(code); raise NotImplementedError; end
@@ -14,7 +14,11 @@ module RailsConsoleAi
14
14
  $stdout.puts colorize(text, :cyan)
15
15
  end
16
16
 
17
- def display_dim(text)
17
+ def display_thinking(text)
18
+ $stdout.puts "\e[2m#{text}\e[0m"
19
+ end
20
+
21
+ def display_status(text)
18
22
  $stdout.puts "\e[2m#{text}\e[0m"
19
23
  end
20
24
 
@@ -28,23 +28,21 @@ module RailsConsoleAi
28
28
  post(strip_ansi(text))
29
29
  end
30
30
 
31
- def display_dim(text)
32
- raw = strip_ansi(text)
33
- stripped = raw.strip
31
+ def display_thinking(text)
32
+ stripped = strip_ansi(text).strip
33
+ return if stripped.empty?
34
+ post(stripped)
35
+ end
36
+
37
+ def display_status(text)
38
+ stripped = strip_ansi(text).strip
39
+ return if stripped.empty?
34
40
 
35
41
  if stripped =~ /\AThinking\.\.\.|\AAttempting to fix|\ACancelled|\A_session:/
36
42
  post(stripped)
37
- elsif stripped =~ /\ACalling LLM/
38
- # Technical LLM round status — suppress in Slack
39
- @output_log.write("#{stripped}\n")
40
- STDOUT.puts "#{@log_prefix} (dim) #{stripped}"
41
- elsif raw =~ /\A {2,4}\S/ && stripped.length > 10
42
- # LLM thinking text (2-space indent from conversation engine) — show as status
43
- post(stripped)
44
43
  else
45
- # Tool result previews (5+ space indent) and other technical noise — log only
46
44
  @output_log.write("#{stripped}\n")
47
- STDOUT.puts "#{@log_prefix} (dim) #{stripped}"
45
+ STDOUT.puts "#{@log_prefix} (status) #{stripped}"
48
46
  end
49
47
  end
50
48
 
@@ -112,9 +110,9 @@ module RailsConsoleAi
112
110
 
113
111
  def system_instructions
114
112
  <<~INSTRUCTIONS.strip
115
- ## Response Formatting (Slack Channel)
113
+ ## Slack Channel
116
114
 
117
- You are responding to non-technical users in Slack. Follow these rules:
115
+ You are responding in a Slack thread.
118
116
 
119
117
  ## Code Execution
120
118
  - ALWAYS use the `execute_code` tool to run Ruby code. Do NOT put code in markdown
@@ -132,11 +130,8 @@ module RailsConsoleAi
132
130
  123 John Smith john@example.com
133
131
  456 Jane Doe jane@example.com
134
132
  ```
135
- - Use `puts` with formatted output instead of returning arrays or hashes
136
- - Summarize findings in plain, simple language
137
- - Do NOT show technical details like SQL queries, token counts, or class names
138
- - Keep explanations simple and jargon-free
139
- - Never return raw Ruby objects — always present data in a human-readable way
133
+ - Use `puts` with formatted output instead of returning arrays or hashes.
134
+ - Never return raw Ruby objects — always present data in a human-readable way.
140
135
  - The output of `puts` in your code is automatically shown to the user. Do NOT
141
136
  repeat or re-display data that your code already printed via `puts`.
142
137
  Just add a brief summary after (e.g. "10 events found" or "Let me know if you need more detail").
@@ -97,7 +97,8 @@ module RailsConsoleAi
97
97
  - Give ONE concise answer. Do not offer multiple alternatives or variations.
98
98
  - For multi-step tasks, use execute_plan to break the work into small, clear steps.
99
99
  - For simple queries, use the execute_code tool.
100
- - Include a brief one-line explanation before or after executing code.
100
+ - Before calling tools, briefly state what you're about to do (e.g., "Let me check the
101
+ user's migration status." or "I'll look up the table structure."). Keep it to one sentence.
101
102
  - Use the app's actual model names, associations, and schema.
102
103
  - Prefer ActiveRecord query interface over raw SQL.
103
104
  - For destructive operations, add a comment warning.
@@ -49,7 +49,7 @@ module RailsConsoleAi
49
49
  conversation << { role: :assistant, content: @_last_result_text }
50
50
  conversation << { role: :user, content: error_msg }
51
51
 
52
- @channel.display_dim(" Ran into an issue, trying a different approach...")
52
+ @channel.display_status(" Ran into an issue, trying a different approach...")
53
53
  exec_result, code, executed = one_shot_round(conversation)
54
54
  end
55
55
 
@@ -114,7 +114,7 @@ module RailsConsoleAi
114
114
 
115
115
  status = send_and_execute
116
116
  if status == :error
117
- @channel.display_dim(" Ran into an issue, trying a different approach...")
117
+ @channel.display_status(" Ran into an issue, trying a different approach...")
118
118
  send_and_execute
119
119
  end
120
120
  end
@@ -232,7 +232,7 @@ module RailsConsoleAi
232
232
  @channel.display_warning("No code to retry.")
233
233
  return
234
234
  end
235
- @channel.display_dim(" Retrying last code...")
235
+ @channel.display_status(" Retrying last code...")
236
236
  execute_direct(code)
237
237
  end
238
238
 
@@ -790,19 +790,19 @@ module RailsConsoleAi
790
790
 
791
791
  max_rounds.times do |round|
792
792
  if @channel.cancelled?
793
- @channel.display_dim(" Cancelled.")
793
+ @channel.display_status(" Cancelled.")
794
794
  break
795
795
  end
796
796
 
797
797
  if round == 0
798
- @channel.display_dim(" Thinking...")
798
+ @channel.display_status(" Thinking...")
799
799
  else
800
800
  if last_thinking
801
801
  last_thinking.split("\n").each do |line|
802
- @channel.display_dim(" #{line}")
802
+ @channel.display_thinking(" #{line}")
803
803
  end
804
804
  end
805
- @channel.display_dim(" #{llm_status(round, messages, total_input, last_thinking, last_tool_names)}")
805
+ @channel.display_status(" #{llm_status(round, messages, total_input, last_thinking, last_tool_names)}")
806
806
  end
807
807
 
808
808
  # Trim large tool outputs between rounds to prevent context explosion.
@@ -856,7 +856,7 @@ module RailsConsoleAi
856
856
  else
857
857
  "No matching outputs found with id(s) #{ids.join(', ')}."
858
858
  end
859
- @channel.display_dim(" #{tool_result}")
859
+ @channel.display_status(" #{tool_result}")
860
860
  tool_msg = provider.format_tool_result(tc[:id], tool_result)
861
861
  messages << tool_msg
862
862
  new_messages << tool_msg
@@ -865,7 +865,7 @@ module RailsConsoleAi
865
865
 
866
866
  # Display any pending LLM text before executing the tool
867
867
  if last_thinking
868
- last_thinking.split("\n").each { |line| @channel.display_dim(" #{line}") }
868
+ last_thinking.split("\n").each { |line| @channel.display_thinking(" #{line}") }
869
869
  last_thinking = nil
870
870
  end
871
871
 
@@ -879,7 +879,7 @@ module RailsConsoleAi
879
879
 
880
880
  preview = compact_tool_result(tc[:name], tool_result)
881
881
  cached_tag = tools.last_cached? ? " (cached)" : ""
882
- @channel.display_dim(" #{preview}#{cached_tag}")
882
+ @channel.display_status(" #{preview}#{cached_tag}")
883
883
  end
884
884
 
885
885
  if RailsConsoleAi.configuration.debug
@@ -908,10 +908,10 @@ module RailsConsoleAi
908
908
  tool_call_counts[key] += 1
909
909
 
910
910
  if tool_call_counts[key] >= LOOP_BREAK_THRESHOLD
911
- @channel.display_dim(" Loop detected: #{tc[:name]} called #{tool_call_counts[key]} times with same args — stopping.")
911
+ @channel.display_status(" Loop detected: #{tc[:name]} called #{tool_call_counts[key]} times with same args — stopping.")
912
912
  exhausted = true
913
913
  elsif tool_call_counts[key] >= LOOP_WARN_THRESHOLD
914
- @channel.display_dim(" Warning: #{tc[:name]} called #{tool_call_counts[key]} times with same args — consider a different approach.")
914
+ @channel.display_status(" Warning: #{tc[:name]} called #{tool_call_counts[key]} times with same args — consider a different approach.")
915
915
  messages << { role: :user, content: "You are repeating the same tool call (#{tc[:name]}) with the same arguments. This is not making progress. Try a different approach or provide your answer now." }
916
916
  end
917
917
  end
@@ -1095,6 +1095,12 @@ module RailsConsoleAi
1095
1095
  else
1096
1096
  truncate(result, 80)
1097
1097
  end
1098
+ when 'execute_code'
1099
+ lines = result.split("\n").reject { |l| l.strip.empty? }
1100
+ output_lines = lines.select { |l| !l.start_with?('Output:') && !l.start_with?('Return value:') }
1101
+ summary = output_lines.first(2).map { |l| l.strip }.join('; ')
1102
+ summary = truncate(summary, 70) if summary.length > 70
1103
+ "#{output_lines.length} lines: #{summary}"
1098
1104
  when 'execute_plan'
1099
1105
  steps_done = result.scan(/^Step \d+/).length
1100
1106
  steps_done > 0 ? "#{steps_done} steps executed" : truncate(result, 80)
@@ -216,7 +216,20 @@ module RailsConsoleAi
216
216
 
217
217
  loop do
218
218
  case answer
219
- when 'y', 'yes', 'a'
219
+ when 'a', 'auto'
220
+ RailsConsoleAi.configuration.auto_execute = true
221
+ if @channel
222
+ @channel.display_status("Auto-execute: ON")
223
+ else
224
+ $stdout.puts colorize("Auto-execute: ON", :cyan)
225
+ end
226
+ result = execute(code)
227
+ if @last_safety_error
228
+ return nil unless danger_allowed?
229
+ return offer_danger_retry(code)
230
+ end
231
+ return result
232
+ when 'y', 'yes'
220
233
  result = execute(code)
221
234
  if @last_safety_error
222
235
  return nil unless danger_allowed?
@@ -355,9 +368,9 @@ module RailsConsoleAi
355
368
  def execute_prompt
356
369
  guards = RailsConsoleAi.configuration.safety_guards
357
370
  if !guards.empty? && guards.enabled? && danger_allowed?
358
- "Execute? [y/N/danger] "
371
+ "Execute? [y/N/a/danger] "
359
372
  else
360
- "Execute? [y/N] "
373
+ "Execute? [y/N/a] "
361
374
  end
362
375
  end
363
376
 
@@ -121,10 +121,10 @@ module RailsConsoleAi
121
121
  else
122
122
  [{ text: msg[:content].to_s }]
123
123
  end
124
- # Bedrock rejects empty text blocks in content arrays
125
- content.reject! { |block| block.is_a?(Hash) && block.key?(:text) && !block.key?(:tool_use) && !block.key?(:tool_result) && block[:text].to_s.empty? }
124
+ # Bedrock rejects empty or whitespace-only text blocks in content arrays
125
+ content.reject! { |block| block.is_a?(Hash) && block.key?(:text) && !block.key?(:tool_use) && !block.key?(:tool_result) && block[:text].to_s.strip.empty? }
126
126
  # Bedrock also rejects messages with completely empty content arrays
127
- content << { text: ' ' } if content.empty?
127
+ content << { text: '.' } if content.empty?
128
128
  { role: msg[:role].to_s, content: content }
129
129
  end
130
130
 
@@ -443,10 +443,10 @@ module RailsConsoleAi
443
443
  Thread.current.report_on_exception = false
444
444
  Thread.current[:log_prefix] = "[#{channel_id}/#{thread_ts}]#{channel_log_tag(channel_id)} @#{user_name}"
445
445
  begin
446
- channel.display_dim("_session: #{channel_id}/#{thread_ts}_")
446
+ channel.display_status("_session: #{channel_id}/#{thread_ts}_")
447
447
  if restored
448
448
  puts "Restored session for thread #{thread_ts} (#{engine.history.length} messages)"
449
- channel.display_dim("_(session restored — continuing from previous conversation)_")
449
+ channel.display_status("_(session restored — continuing from previous conversation)_")
450
450
  end
451
451
  engine.process_message(text)
452
452
  rescue => e
@@ -368,13 +368,6 @@ module RailsConsoleAi
368
368
  def register_execute_plan
369
369
  return unless @executor
370
370
 
371
- # Check per-channel code execution permission
372
- if @channel
373
- unless RailsConsoleAi.configuration.username_allowed?(@channel.mode, 'allow_code_execution', @channel.user_identity)
374
- return
375
- end
376
- end
377
-
378
371
  register(
379
372
  name: 'execute_code',
380
373
  description: 'Execute Ruby code in the Rails console and return the result. Use this for all code execution — simple queries, data lookups, reports, etc. The output of puts/print statements is automatically shown to the user. The return value is sent back to you so you can summarize the findings.',
@@ -419,12 +412,8 @@ module RailsConsoleAi
419
412
  # Show the code to the user
420
413
  @executor.display_code_block(code)
421
414
 
422
- # Slack: execute directly, suppress display (output goes back to LLM as tool result).
423
- # Console: show code and confirm before executing, display output directly.
424
- exec_result = if @channel&.mode == 'slack'
425
- @executor.execute(code, display: false)
426
- elsif RailsConsoleAi.configuration.auto_execute
427
- @executor.execute(code, display: false)
415
+ exec_result = if @channel&.mode == 'slack' || RailsConsoleAi.configuration.auto_execute
416
+ @executor.execute(code)
428
417
  else
429
418
  @executor.confirm_and_execute(code)
430
419
  end
@@ -1,3 +1,3 @@
1
1
  module RailsConsoleAi
2
- VERSION = '0.23.0'.freeze
2
+ VERSION = '0.24.0'.freeze
3
3
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rails_console_ai
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.23.0
4
+ version: 0.24.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Cortfr