rails_console_ai 0.22.0 → 0.23.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 +22 -0
- data/lib/generators/rails_console_ai/templates/initializer.rb +8 -4
- data/lib/rails_console_ai/channel/slack.rb +2 -1
- data/lib/rails_console_ai/configuration.rb +23 -0
- data/lib/rails_console_ai/context_builder.rb +14 -7
- data/lib/rails_console_ai/conversation_engine.rb +327 -104
- data/lib/rails_console_ai/executor.rb +19 -10
- data/lib/rails_console_ai/providers/bedrock.rb +2 -0
- data/lib/rails_console_ai/repl.rb +2 -2
- data/lib/rails_console_ai/skill_loader.rb +49 -0
- data/lib/rails_console_ai/slack_bot.rb +20 -16
- data/lib/rails_console_ai/tools/memory_tools.rb +22 -5
- data/lib/rails_console_ai/tools/model_tools.rb +28 -0
- data/lib/rails_console_ai/tools/registry.rb +61 -3
- data/lib/rails_console_ai/version.rb +1 -1
- metadata +1 -1
|
@@ -148,7 +148,7 @@ module RailsConsoleAi
|
|
|
148
148
|
rescue SyntaxError => e
|
|
149
149
|
$stdout = old_stdout if old_stdout
|
|
150
150
|
@last_error = "SyntaxError: #{e.message}"
|
|
151
|
-
|
|
151
|
+
log_execution_error(@last_error)
|
|
152
152
|
@last_output = nil
|
|
153
153
|
nil
|
|
154
154
|
rescue => e
|
|
@@ -166,7 +166,7 @@ module RailsConsoleAi
|
|
|
166
166
|
end
|
|
167
167
|
@last_error = "#{e.class}: #{e.message}"
|
|
168
168
|
backtrace = e.backtrace.first(3).map { |line| " #{line}" }.join("\n")
|
|
169
|
-
|
|
169
|
+
log_execution_error("Error: #{@last_error}\n#{backtrace}")
|
|
170
170
|
@last_output = captured_output&.string
|
|
171
171
|
nil
|
|
172
172
|
end
|
|
@@ -310,6 +310,14 @@ module RailsConsoleAi
|
|
|
310
310
|
end
|
|
311
311
|
end
|
|
312
312
|
|
|
313
|
+
def execute_unsafe(code)
|
|
314
|
+
guards = RailsConsoleAi.configuration.safety_guards
|
|
315
|
+
guards.disable!
|
|
316
|
+
execute(code)
|
|
317
|
+
ensure
|
|
318
|
+
guards.enable!
|
|
319
|
+
end
|
|
320
|
+
|
|
313
321
|
private
|
|
314
322
|
|
|
315
323
|
def danger_allowed?
|
|
@@ -324,6 +332,15 @@ module RailsConsoleAi
|
|
|
324
332
|
end
|
|
325
333
|
end
|
|
326
334
|
|
|
335
|
+
# Log code execution errors — full details to STDOUT, brief summary to Slack
|
|
336
|
+
def log_execution_error(msg)
|
|
337
|
+
if @channel && @channel.mode == 'slack'
|
|
338
|
+
RailsConsoleAi.logger.info("Code execution error: #{msg}")
|
|
339
|
+
else
|
|
340
|
+
display_error(msg)
|
|
341
|
+
end
|
|
342
|
+
end
|
|
343
|
+
|
|
327
344
|
def allow_description(guard, blocked_key)
|
|
328
345
|
case guard
|
|
329
346
|
when :database_writes
|
|
@@ -335,14 +352,6 @@ module RailsConsoleAi
|
|
|
335
352
|
end
|
|
336
353
|
end
|
|
337
354
|
|
|
338
|
-
def execute_unsafe(code)
|
|
339
|
-
guards = RailsConsoleAi.configuration.safety_guards
|
|
340
|
-
guards.disable!
|
|
341
|
-
execute(code)
|
|
342
|
-
ensure
|
|
343
|
-
guards.enable!
|
|
344
|
-
end
|
|
345
|
-
|
|
346
355
|
def execute_prompt
|
|
347
356
|
guards = RailsConsoleAi.configuration.safety_guards
|
|
348
357
|
if !guards.empty? && guards.enabled? && danger_allowed?
|
|
@@ -123,6 +123,8 @@ module RailsConsoleAi
|
|
|
123
123
|
end
|
|
124
124
|
# Bedrock rejects empty text blocks in content arrays
|
|
125
125
|
content.reject! { |block| block.is_a?(Hash) && block.key?(:text) && !block.key?(:tool_use) && !block.key?(:tool_result) && block[:text].to_s.empty? }
|
|
126
|
+
# Bedrock also rejects messages with completely empty content arrays
|
|
127
|
+
content << { text: ' ' } if content.empty?
|
|
126
128
|
{ role: msg[:role].to_s, content: content }
|
|
127
129
|
end
|
|
128
130
|
|
|
@@ -58,8 +58,8 @@ module RailsConsoleAi
|
|
|
58
58
|
@engine.send(:send_query, query, conversation: conversation)
|
|
59
59
|
end
|
|
60
60
|
|
|
61
|
-
def
|
|
62
|
-
@engine.send(:
|
|
61
|
+
def trim_large_outputs(messages)
|
|
62
|
+
@engine.send(:trim_large_outputs, messages)
|
|
63
63
|
end
|
|
64
64
|
end
|
|
65
65
|
end
|
|
@@ -32,8 +32,57 @@ module RailsConsoleAi
|
|
|
32
32
|
skills.find { |s| s['name'].to_s.downcase == name.to_s.downcase }
|
|
33
33
|
end
|
|
34
34
|
|
|
35
|
+
def save_skill(name:, description:, body:, tags: [], bypass_guards_for_methods: [])
|
|
36
|
+
key = skill_key(name)
|
|
37
|
+
existing = find_skill(name)
|
|
38
|
+
|
|
39
|
+
frontmatter = {
|
|
40
|
+
'name' => name,
|
|
41
|
+
'description' => description,
|
|
42
|
+
'tags' => Array(tags)
|
|
43
|
+
}
|
|
44
|
+
bypasses = Array(bypass_guards_for_methods)
|
|
45
|
+
frontmatter['bypass_guards_for_methods'] = bypasses unless bypasses.empty?
|
|
46
|
+
|
|
47
|
+
content = "---\n#{YAML.dump(frontmatter).sub("---\n", '').strip}\n---\n\n#{body}\n"
|
|
48
|
+
@storage.write(key, content)
|
|
49
|
+
|
|
50
|
+
path = @storage.respond_to?(:root_path) ? File.join(@storage.root_path, key) : key
|
|
51
|
+
if existing
|
|
52
|
+
"Skill updated: \"#{name}\" (#{path})"
|
|
53
|
+
else
|
|
54
|
+
"Skill created: \"#{name}\" (#{path})"
|
|
55
|
+
end
|
|
56
|
+
rescue Storage::StorageError => e
|
|
57
|
+
"FAILED to save skill (#{e.message})."
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def delete_skill(name:)
|
|
61
|
+
key = skill_key(name)
|
|
62
|
+
unless @storage.exists?(key)
|
|
63
|
+
found = load_all_skills.find { |s| s['name'].to_s.downcase == name.to_s.downcase }
|
|
64
|
+
return "No skill found: \"#{name}\"" unless found
|
|
65
|
+
key = skill_key(found['name'])
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
skill = load_skill(key)
|
|
69
|
+
@storage.delete(key)
|
|
70
|
+
"Skill deleted: \"#{skill ? skill['name'] : name}\""
|
|
71
|
+
rescue Storage::StorageError => e
|
|
72
|
+
"FAILED to delete skill (#{e.message})."
|
|
73
|
+
end
|
|
74
|
+
|
|
35
75
|
private
|
|
36
76
|
|
|
77
|
+
def skill_key(name)
|
|
78
|
+
slug = name.downcase.strip
|
|
79
|
+
.gsub(/[^a-z0-9\s-]/, '')
|
|
80
|
+
.gsub(/[\s]+/, '-')
|
|
81
|
+
.gsub(/-+/, '-')
|
|
82
|
+
.sub(/^-/, '').sub(/-$/, '')
|
|
83
|
+
"#{SKILLS_DIR}/#{slug}.md"
|
|
84
|
+
end
|
|
85
|
+
|
|
37
86
|
def load_skill(key)
|
|
38
87
|
content = @storage.read(key)
|
|
39
88
|
return nil if content.nil? || content.strip.empty?
|
|
@@ -22,7 +22,9 @@ module RailsConsoleAi
|
|
|
22
22
|
|
|
23
23
|
raise ConfigurationError, "SLACK_BOT_TOKEN is required" unless @bot_token
|
|
24
24
|
raise ConfigurationError, "SLACK_APP_TOKEN is required (Socket Mode)" unless @app_token
|
|
25
|
-
|
|
25
|
+
unless RailsConsoleAi.configuration.channel_setting('slack', 'allowed_usernames')
|
|
26
|
+
raise ConfigurationError, "slack allowed_usernames must be configured — either channels['slack']['allowed_usernames'] or slack_allowed_usernames (e.g. ['alice'] or 'ALL')"
|
|
27
|
+
end
|
|
26
28
|
|
|
27
29
|
@bot_user_id = nil
|
|
28
30
|
@sessions = {} # thread_ts → { channel:, engine:, thread:, owner_user_id: }
|
|
@@ -316,8 +318,7 @@ module RailsConsoleAi
|
|
|
316
318
|
unless session[:owner_user_id] == user_id
|
|
317
319
|
# Non-owner: tell unrecognized users, silently ignore recognized non-owners
|
|
318
320
|
chk_name = resolve_user_name(user_id)
|
|
319
|
-
|
|
320
|
-
unless chk_list.include?('all') || chk_list.include?(chk_name.to_s.downcase)
|
|
321
|
+
unless RailsConsoleAi.configuration.username_allowed?('slack', 'allowed_usernames', chk_name)
|
|
321
322
|
puts "[#{channel_id}/#{thread_ts}]#{channel_log_tag(channel_id)} @#{chk_name} << (ignored — not in allowed usernames)"
|
|
322
323
|
post_message(channel: channel_id, thread_ts: thread_ts, text: "Sorry, I don't recognize your username (@#{chk_name}). Ask an admin to add you to the allowed usernames list.")
|
|
323
324
|
end
|
|
@@ -338,8 +339,7 @@ module RailsConsoleAi
|
|
|
338
339
|
# --- Common processing (DMs and channels) ---
|
|
339
340
|
user_name = resolve_user_name(user_id)
|
|
340
341
|
|
|
341
|
-
|
|
342
|
-
unless allowed_list.include?('all') || allowed_list.include?(user_name.to_s.downcase)
|
|
342
|
+
unless RailsConsoleAi.configuration.username_allowed?('slack', 'allowed_usernames', user_name)
|
|
343
343
|
puts "[#{channel_id}/#{thread_ts}]#{channel_log_tag(channel_id)} @#{user_name} << (ignored — not in allowed usernames)"
|
|
344
344
|
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.")
|
|
345
345
|
return
|
|
@@ -450,7 +450,7 @@ module RailsConsoleAi
|
|
|
450
450
|
end
|
|
451
451
|
engine.process_message(text)
|
|
452
452
|
rescue => e
|
|
453
|
-
channel.display_error("
|
|
453
|
+
channel.display_error("Something went wrong. Please try again.")
|
|
454
454
|
RailsConsoleAi.logger.error("SlackBot session error: #{e.class}: #{e.message}\n#{e.backtrace.first(5).join("\n")}")
|
|
455
455
|
ensure
|
|
456
456
|
ActiveRecord::Base.clear_active_connections! if defined?(ActiveRecord::Base)
|
|
@@ -488,7 +488,7 @@ module RailsConsoleAi
|
|
|
488
488
|
begin
|
|
489
489
|
engine.process_message(text)
|
|
490
490
|
rescue => e
|
|
491
|
-
channel.display_error("
|
|
491
|
+
channel.display_error("Something went wrong. Please try again.")
|
|
492
492
|
RailsConsoleAi.logger.error("SlackBot session error: #{e.class}: #{e.message}\n#{e.backtrace.first(5).join("\n")}")
|
|
493
493
|
ensure
|
|
494
494
|
ActiveRecord::Base.clear_active_connections! if defined?(ActiveRecord::Base)
|
|
@@ -497,6 +497,14 @@ module RailsConsoleAi
|
|
|
497
497
|
end
|
|
498
498
|
|
|
499
499
|
def handle_direct_code(session, channel_id, thread_ts, raw_code, user_name, user_id)
|
|
500
|
+
# Check code execution permission
|
|
501
|
+
unless RailsConsoleAi.configuration.username_allowed?('slack', 'allow_code_execution', user_name)
|
|
502
|
+
puts "[#{channel_id}/#{thread_ts}]#{channel_log_tag(channel_id)} @#{user_name} << (blocked — not in allow_code_execution)"
|
|
503
|
+
post_message(channel: channel_id, thread_ts: thread_ts,
|
|
504
|
+
text: "Sorry, you don't have permission to execute code directly. Ask an admin to add you to the `allow_code_execution` list.")
|
|
505
|
+
return
|
|
506
|
+
end
|
|
507
|
+
|
|
500
508
|
# Ensure a session exists for this thread
|
|
501
509
|
unless session
|
|
502
510
|
start_direct_session(channel_id, thread_ts, user_name, user_id)
|
|
@@ -521,7 +529,7 @@ module RailsConsoleAi
|
|
|
521
529
|
end
|
|
522
530
|
engine.send(:log_interactive_turn)
|
|
523
531
|
rescue => e
|
|
524
|
-
channel.display_error("
|
|
532
|
+
channel.display_error("Something went wrong. Please try again.")
|
|
525
533
|
RailsConsoleAi.logger.error("SlackBot direct code error: #{e.class}: #{e.message}\n#{e.backtrace.first(5).join("\n")}")
|
|
526
534
|
ensure
|
|
527
535
|
ActiveRecord::Base.clear_active_connections! if defined?(ActiveRecord::Base)
|
|
@@ -646,7 +654,7 @@ module RailsConsoleAi
|
|
|
646
654
|
engine.send(:log_interactive_turn)
|
|
647
655
|
rescue => e
|
|
648
656
|
post_message(channel: channel_id, thread_ts: thread_ts,
|
|
649
|
-
text: "Retry
|
|
657
|
+
text: ":x: Retry didn't work. Please try again.")
|
|
650
658
|
ensure
|
|
651
659
|
ActiveRecord::Base.clear_active_connections! if defined?(ActiveRecord::Base)
|
|
652
660
|
end
|
|
@@ -744,13 +752,9 @@ module RailsConsoleAi
|
|
|
744
752
|
history = engine.history
|
|
745
753
|
return "No conversation history yet." if history.empty?
|
|
746
754
|
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
asst_msgs = history.count { |m| m[:role].to_s == 'assistant' }
|
|
751
|
-
name_str = engine.session_name ? " (*#{engine.session_name}*)" : ""
|
|
752
|
-
|
|
753
|
-
"Context#{name_str}: #{msg_count} messages (#{user_msgs} user, #{asst_msgs} assistant), ~#{chars} chars"
|
|
755
|
+
buf = StringIO.new
|
|
756
|
+
engine.send(:display_conversation_to, buf)
|
|
757
|
+
"```\n#{buf.string}```"
|
|
754
758
|
end
|
|
755
759
|
|
|
756
760
|
def count_bot_messages(channel_id, thread_ts)
|
|
@@ -50,6 +50,20 @@ module RailsConsoleAi
|
|
|
50
50
|
"FAILED to delete memory (#{e.message})."
|
|
51
51
|
end
|
|
52
52
|
|
|
53
|
+
def recall_memory(name:)
|
|
54
|
+
key = memory_key(name)
|
|
55
|
+
memory = load_memory(key)
|
|
56
|
+
# Fall back to case-insensitive name search
|
|
57
|
+
unless memory
|
|
58
|
+
memory = load_all_memories.find { |m| m['name'].to_s.downcase == name.downcase }
|
|
59
|
+
end
|
|
60
|
+
return "No memory found: \"#{name}\"" unless memory
|
|
61
|
+
|
|
62
|
+
line = "**#{memory['name']}**\n#{memory['description']}"
|
|
63
|
+
line += "\nTags: #{memory['tags'].join(', ')}" if memory['tags'] && !memory['tags'].empty?
|
|
64
|
+
line
|
|
65
|
+
end
|
|
66
|
+
|
|
53
67
|
def recall_memories(query: nil, tag: nil)
|
|
54
68
|
memories = load_all_memories
|
|
55
69
|
return "No memories stored yet." if memories.empty?
|
|
@@ -61,11 +75,14 @@ module RailsConsoleAi
|
|
|
61
75
|
}
|
|
62
76
|
end
|
|
63
77
|
if query && !query.empty?
|
|
64
|
-
|
|
78
|
+
words = query.downcase.split(/\s+/)
|
|
65
79
|
results = results.select { |m|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
80
|
+
searchable = [
|
|
81
|
+
m['name'].to_s.downcase,
|
|
82
|
+
m['description'].to_s.downcase,
|
|
83
|
+
Array(m['tags']).map(&:downcase).join(' ')
|
|
84
|
+
].join(' ')
|
|
85
|
+
words.all? { |w| searchable.include?(w) }
|
|
69
86
|
}
|
|
70
87
|
end
|
|
71
88
|
|
|
@@ -75,7 +92,7 @@ module RailsConsoleAi
|
|
|
75
92
|
line = "**#{m['name']}**\n#{m['description']}"
|
|
76
93
|
line += "\nTags: #{m['tags'].join(', ')}" if m['tags'] && !m['tags'].empty?
|
|
77
94
|
line
|
|
78
|
-
}.join("\n\n")
|
|
95
|
+
}.join("\n\n---\n\n")
|
|
79
96
|
end
|
|
80
97
|
|
|
81
98
|
def memory_summaries
|
|
@@ -35,6 +35,34 @@ module RailsConsoleAi
|
|
|
35
35
|
result = "Model: #{model.name}\n"
|
|
36
36
|
result += "Table: #{model.table_name}\n"
|
|
37
37
|
|
|
38
|
+
# Columns and indexes from the database table
|
|
39
|
+
begin
|
|
40
|
+
if ActiveRecord::Base.connected?
|
|
41
|
+
conn = ActiveRecord::Base.connection
|
|
42
|
+
if conn.tables.include?(model.table_name)
|
|
43
|
+
cols = conn.columns(model.table_name).map do |c|
|
|
44
|
+
parts = ["#{c.name}:#{c.type}"]
|
|
45
|
+
parts << "nullable" if c.null
|
|
46
|
+
parts << "default=#{c.default}" unless c.default.nil?
|
|
47
|
+
parts.join(" ")
|
|
48
|
+
end
|
|
49
|
+
result += "Columns:\n"
|
|
50
|
+
cols.each { |c| result += " #{c}\n" }
|
|
51
|
+
|
|
52
|
+
indexes = conn.indexes(model.table_name).map do |idx|
|
|
53
|
+
unique = idx.unique ? "UNIQUE " : ""
|
|
54
|
+
"#{unique}INDEX on (#{idx.columns.join(', ')})"
|
|
55
|
+
end
|
|
56
|
+
unless indexes.empty?
|
|
57
|
+
result += "Indexes:\n"
|
|
58
|
+
indexes.each { |i| result += " #{i}\n" }
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
rescue => e
|
|
63
|
+
# table introspection may fail
|
|
64
|
+
end
|
|
65
|
+
|
|
38
66
|
assocs = model.reflect_on_all_associations.map { |a| "#{a.macro} :#{a.name}" }
|
|
39
67
|
unless assocs.empty?
|
|
40
68
|
result += "Associations:\n"
|
|
@@ -6,7 +6,7 @@ module RailsConsoleAi
|
|
|
6
6
|
attr_reader :definitions
|
|
7
7
|
|
|
8
8
|
# Tools that should never be cached (side effects or user interaction)
|
|
9
|
-
NO_CACHE = %w[ask_user save_memory delete_memory execute_code execute_plan activate_skill].freeze
|
|
9
|
+
NO_CACHE = %w[ask_user save_memory delete_memory recall_memory execute_code execute_plan activate_skill save_skill delete_skill].freeze
|
|
10
10
|
|
|
11
11
|
def initialize(executor: nil, mode: :default, channel: nil)
|
|
12
12
|
@executor = executor
|
|
@@ -111,7 +111,7 @@ module RailsConsoleAi
|
|
|
111
111
|
|
|
112
112
|
register(
|
|
113
113
|
name: 'describe_table',
|
|
114
|
-
description: 'Get column names and
|
|
114
|
+
description: 'Get column names, types, and indexes for a database table. Use this only for tables that have no corresponding ActiveRecord model — for tables with models, use describe_model instead (it includes columns).',
|
|
115
115
|
parameters: {
|
|
116
116
|
'type' => 'object',
|
|
117
117
|
'properties' => {
|
|
@@ -131,7 +131,7 @@ module RailsConsoleAi
|
|
|
131
131
|
|
|
132
132
|
register(
|
|
133
133
|
name: 'describe_model',
|
|
134
|
-
description: 'Get
|
|
134
|
+
description: 'Get full details about a model: columns, indexes, associations, validations, and scopes. This is the primary tool for understanding a model — it includes the table schema.',
|
|
135
135
|
parameters: {
|
|
136
136
|
'type' => 'object',
|
|
137
137
|
'properties' => {
|
|
@@ -270,6 +270,19 @@ module RailsConsoleAi
|
|
|
270
270
|
handler: ->(args) { memory.delete_memory(name: args['name']) }
|
|
271
271
|
)
|
|
272
272
|
|
|
273
|
+
register(
|
|
274
|
+
name: 'recall_memory',
|
|
275
|
+
description: 'Retrieve a specific memory by name. Use this when you know which memory you need (e.g. from the Memories list in the system prompt). For searching across memories, use recall_memories instead.',
|
|
276
|
+
parameters: {
|
|
277
|
+
'type' => 'object',
|
|
278
|
+
'properties' => {
|
|
279
|
+
'name' => { 'type' => 'string', 'description' => 'The exact memory name (e.g. "Sharding architecture")' }
|
|
280
|
+
},
|
|
281
|
+
'required' => ['name']
|
|
282
|
+
},
|
|
283
|
+
handler: ->(args) { memory.recall_memory(name: args['name']) }
|
|
284
|
+
)
|
|
285
|
+
|
|
273
286
|
register(
|
|
274
287
|
name: 'recall_memories',
|
|
275
288
|
description: 'Search your saved memories about this codebase. Call with no args to list all, or pass a query/tag to filter.',
|
|
@@ -312,11 +325,56 @@ module RailsConsoleAi
|
|
|
312
325
|
skill['body']
|
|
313
326
|
}
|
|
314
327
|
)
|
|
328
|
+
|
|
329
|
+
register(
|
|
330
|
+
name: 'save_skill',
|
|
331
|
+
description: 'Create or update a skill — a reusable procedure for a specific operation. Use when the user asks you to create a skill, recipe, or runbook. Skills differ from memories: a skill is a step-by-step procedure to follow, while a memory is a fact or pattern you learned.',
|
|
332
|
+
parameters: {
|
|
333
|
+
'type' => 'object',
|
|
334
|
+
'properties' => {
|
|
335
|
+
'name' => { 'type' => 'string', 'description' => 'Skill name (e.g. "Resurrect deleted BookingPage")' },
|
|
336
|
+
'description' => { 'type' => 'string', 'description' => 'One-line description of when to use this skill' },
|
|
337
|
+
'body' => { 'type' => 'string', 'description' => 'The full skill recipe in markdown. Include: ## When to use, ## Recipe (numbered steps with code blocks), ## Notes (optional).' },
|
|
338
|
+
'tags' => { 'type' => 'array', 'items' => { 'type' => 'string' }, 'description' => 'Optional tags for categorization (e.g. ["booking-page", "admin"])' },
|
|
339
|
+
'bypass_guards_for_methods' => { 'type' => 'array', 'items' => { 'type' => 'string' }, 'description' => 'Methods that should bypass safety guards when this skill is active (e.g. ["BookingPage#save!", "BookingPage#ensure_subdomain_set!"])' }
|
|
340
|
+
},
|
|
341
|
+
'required' => %w[name description body]
|
|
342
|
+
},
|
|
343
|
+
handler: ->(args) {
|
|
344
|
+
loader.save_skill(
|
|
345
|
+
name: args['name'],
|
|
346
|
+
description: args['description'],
|
|
347
|
+
body: args['body'],
|
|
348
|
+
tags: args['tags'] || [],
|
|
349
|
+
bypass_guards_for_methods: args['bypass_guards_for_methods'] || []
|
|
350
|
+
)
|
|
351
|
+
}
|
|
352
|
+
)
|
|
353
|
+
|
|
354
|
+
register(
|
|
355
|
+
name: 'delete_skill',
|
|
356
|
+
description: 'Delete a skill by name.',
|
|
357
|
+
parameters: {
|
|
358
|
+
'type' => 'object',
|
|
359
|
+
'properties' => {
|
|
360
|
+
'name' => { 'type' => 'string', 'description' => 'The skill name to delete' }
|
|
361
|
+
},
|
|
362
|
+
'required' => ['name']
|
|
363
|
+
},
|
|
364
|
+
handler: ->(args) { loader.delete_skill(name: args['name']) }
|
|
365
|
+
)
|
|
315
366
|
end
|
|
316
367
|
|
|
317
368
|
def register_execute_plan
|
|
318
369
|
return unless @executor
|
|
319
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
|
+
|
|
320
378
|
register(
|
|
321
379
|
name: 'execute_code',
|
|
322
380
|
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.',
|