rails_console_ai 0.17.0 → 0.18.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 +7 -0
- data/lib/rails_console_ai/slack_bot.rb +89 -1
- data/lib/rails_console_ai/version.rb +1 -1
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: f6fc753a055c35cdf2aec7ffa98dff0cf5b0dd649e9fd44048d5f43577a69a9a
|
|
4
|
+
data.tar.gz: c9ce948130dae8a58ee8eaaa6d348f00369a9c2f63cb253a9728323493df04e6
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: c20f24123aafbef6c1e2dd218ba47f63c3773e0bae332081422aba1f52a821bf5e94baf09a5db13919e61a12cdb40124de139739a352fb9b8486f3cf3de9be77
|
|
7
|
+
data.tar.gz: 62808844bd51a0a5f3aadae221d2a9641b47dc497a548c0edbc1ccda247eaf0e24785920621850b387165d20698f9ccf77aa1f14b59f9432f0c9dcc17c034aac
|
data/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,13 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to this project will be documented in this file.
|
|
4
4
|
|
|
5
|
+
## [0.18.0]
|
|
6
|
+
|
|
7
|
+
- Handle "smart" quotes coming from Slack
|
|
8
|
+
- Eager load when Slack bot starts up
|
|
9
|
+
- Handle `>` in Slack for direct code execution
|
|
10
|
+
- Handle stopping of sessions in Slack bot by recording the stop
|
|
11
|
+
|
|
5
12
|
## [0.17.0]
|
|
6
13
|
|
|
7
14
|
- Add `/retry` command
|
|
@@ -35,6 +35,13 @@ module RailsConsoleAi
|
|
|
35
35
|
$stdout = RailsConsoleAi::PrefixedIO.new($stdout) unless $stdout.is_a?(RailsConsoleAi::PrefixedIO)
|
|
36
36
|
$stderr = RailsConsoleAi::PrefixedIO.new($stderr) unless $stderr.is_a?(RailsConsoleAi::PrefixedIO)
|
|
37
37
|
|
|
38
|
+
# Eager load the Rails app so class-level initializers (e.g. Secret.get)
|
|
39
|
+
# run before safety guards are active during user code execution.
|
|
40
|
+
if defined?(Rails) && Rails.application.respond_to?(:eager_load!)
|
|
41
|
+
puts "Eager loading application..."
|
|
42
|
+
Rails.application.eager_load!
|
|
43
|
+
end
|
|
44
|
+
|
|
38
45
|
@bot_user_id = slack_api("auth.test", token: @bot_token).dig("user_id")
|
|
39
46
|
log_startup
|
|
40
47
|
|
|
@@ -272,7 +279,7 @@ module RailsConsoleAi
|
|
|
272
279
|
return if event[:user] == @bot_user_id
|
|
273
280
|
return if event[:subtype]
|
|
274
281
|
|
|
275
|
-
text = event[:text]
|
|
282
|
+
text = unescape_slack(event[:text])
|
|
276
283
|
return unless text && !text.strip.empty?
|
|
277
284
|
|
|
278
285
|
channel_id = event[:channel]
|
|
@@ -316,6 +323,15 @@ module RailsConsoleAi
|
|
|
316
323
|
return
|
|
317
324
|
end
|
|
318
325
|
|
|
326
|
+
# Direct code execution: "> User.count" runs code without LLM
|
|
327
|
+
if text.strip.start_with?('>')
|
|
328
|
+
raw_code = text.strip.sub(/\A>\s*/, '')
|
|
329
|
+
unless raw_code.empty?
|
|
330
|
+
handle_direct_code(session, channel_id, thread_ts, raw_code, user_name)
|
|
331
|
+
return
|
|
332
|
+
end
|
|
333
|
+
end
|
|
334
|
+
|
|
319
335
|
if session
|
|
320
336
|
handle_thread_reply(session, text.strip)
|
|
321
337
|
else
|
|
@@ -404,11 +420,76 @@ module RailsConsoleAi
|
|
|
404
420
|
end
|
|
405
421
|
end
|
|
406
422
|
|
|
423
|
+
def handle_direct_code(session, channel_id, thread_ts, raw_code, user_name)
|
|
424
|
+
# Ensure a session exists for this thread
|
|
425
|
+
unless session
|
|
426
|
+
start_direct_session(channel_id, thread_ts, user_name)
|
|
427
|
+
session = @mutex.synchronize { @sessions[thread_ts] }
|
|
428
|
+
end
|
|
429
|
+
|
|
430
|
+
channel = session[:channel]
|
|
431
|
+
engine = session[:engine]
|
|
432
|
+
|
|
433
|
+
session[:thread] = Thread.new do
|
|
434
|
+
Thread.current.report_on_exception = false
|
|
435
|
+
Thread.current[:log_prefix] = channel.instance_variable_get(:@log_prefix)
|
|
436
|
+
begin
|
|
437
|
+
engine.execute_direct(raw_code)
|
|
438
|
+
# Post return value to Slack (execute_direct suppresses it via display_result no-op)
|
|
439
|
+
result_value = engine.instance_variable_get(:@last_interactive_result)
|
|
440
|
+
unless result_value.nil?
|
|
441
|
+
display_text = "=> #{result_value}"
|
|
442
|
+
display_text = display_text[0, 3000] + "\n... (truncated)" if display_text.length > 3000
|
|
443
|
+
post_message(channel: channel_id, thread_ts: thread_ts, text: "```#{display_text}```")
|
|
444
|
+
end
|
|
445
|
+
engine.send(:log_interactive_turn)
|
|
446
|
+
rescue => e
|
|
447
|
+
channel.display_error("Error: #{e.class}: #{e.message}")
|
|
448
|
+
RailsConsoleAi.logger.error("SlackBot direct code error: #{e.class}: #{e.message}\n#{e.backtrace.first(5).join("\n")}")
|
|
449
|
+
ensure
|
|
450
|
+
ActiveRecord::Base.clear_active_connections! if defined?(ActiveRecord::Base)
|
|
451
|
+
end
|
|
452
|
+
end
|
|
453
|
+
end
|
|
454
|
+
|
|
455
|
+
def start_direct_session(channel_id, thread_ts, user_name)
|
|
456
|
+
channel = Channel::Slack.new(
|
|
457
|
+
slack_bot: self,
|
|
458
|
+
channel_id: channel_id,
|
|
459
|
+
thread_ts: thread_ts,
|
|
460
|
+
user_name: user_name
|
|
461
|
+
)
|
|
462
|
+
|
|
463
|
+
sandbox_binding = Object.new.instance_eval { binding }
|
|
464
|
+
engine = ConversationEngine.new(
|
|
465
|
+
binding_context: sandbox_binding,
|
|
466
|
+
channel: channel,
|
|
467
|
+
slack_thread_ts: thread_ts
|
|
468
|
+
)
|
|
469
|
+
|
|
470
|
+
restore_from_db(engine, thread_ts)
|
|
471
|
+
engine.init_interactive unless engine.instance_variable_get(:@interactive_start)
|
|
472
|
+
|
|
473
|
+
session = { channel: channel, engine: engine, thread: nil }
|
|
474
|
+
@mutex.synchronize { @sessions[thread_ts] = session }
|
|
475
|
+
end
|
|
476
|
+
|
|
407
477
|
def cancel_session(session, channel_id, thread_ts)
|
|
408
478
|
if session
|
|
409
479
|
session[:channel].cancel!
|
|
410
480
|
session[:channel].display("Stopped.")
|
|
411
481
|
puts "[#{channel_id}/#{thread_ts}] cancel requested"
|
|
482
|
+
|
|
483
|
+
# Record stop in conversation history so restored sessions know
|
|
484
|
+
# the previous topic was abandoned by the user
|
|
485
|
+
engine = session[:engine]
|
|
486
|
+
engine.history << { role: :user, content: "stop" }
|
|
487
|
+
engine.history << { role: :assistant, content: "Stopped. Awaiting new instructions." }
|
|
488
|
+
begin
|
|
489
|
+
engine.send(:log_interactive_turn)
|
|
490
|
+
rescue => e
|
|
491
|
+
RailsConsoleAi.logger.warn("SlackBot: failed to save cancel state: #{e.message}")
|
|
492
|
+
end
|
|
412
493
|
else
|
|
413
494
|
post_message(channel: channel_id, thread_ts: thread_ts, text: "No active session to stop.")
|
|
414
495
|
puts "[#{channel_id}/#{thread_ts}] cancel: no session"
|
|
@@ -454,6 +535,13 @@ module RailsConsoleAi
|
|
|
454
535
|
{ "ok" => false, "error" => e.message }
|
|
455
536
|
end
|
|
456
537
|
|
|
538
|
+
def unescape_slack(text)
|
|
539
|
+
return text unless text
|
|
540
|
+
text.gsub("&", "&").gsub("<", "<").gsub(">", ">")
|
|
541
|
+
.gsub("\u2018", "'").gsub("\u2019", "'") # smart single quotes → straight
|
|
542
|
+
.gsub("\u201C", '"').gsub("\u201D", '"') # smart double quotes → straight
|
|
543
|
+
end
|
|
544
|
+
|
|
457
545
|
def waiting_for_reply?(channel)
|
|
458
546
|
channel.instance_variable_get(:@reply_queue).num_waiting > 0
|
|
459
547
|
end
|