rails_console_ai 0.28.0 → 0.29.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: 50a2c9dce686cffa315e1fb5004f0ad0a9cbc67459d3a546b28ad1b95d0d1798
4
- data.tar.gz: 7eea0529e3a3e4f9a4d60cf8e6b882e5dc825052c3793817013cf5cc6e617d22
3
+ metadata.gz: 30c7955113bc3e4ce45989fcc0e93034185fbabc0e95acfa01df56ec397900b5
4
+ data.tar.gz: 87b0399d973e448404b234820067ed2f20d4148f78613963904c9e288a17d87a
5
5
  SHA512:
6
- metadata.gz: 4ed2a4ad47456f7e28682e0302d346fd04e3778433015855fc2199d4b1dc3077e8ec96efec5eb1b9ae049f6ed5f8c5ac6944e354488313b1f20e8f4354a0bdd1
7
- data.tar.gz: c3a9d0faaada962952b9995ac0e07b79384536dc03970c0da17fe0e2eef843edea799d2dfa8698363a869d74e2aa7d86f2dbe80088234206693d86dd29ef45b4
6
+ metadata.gz: ad1091c711239e286408f0133378c3597c83075b3f9435a40f43bf2df4fa279cf459cf3af84c58027842d2da4c8bccc5d11bfbec616c6e3ec1212784ad59b764
7
+ data.tar.gz: 61ea9cc3afd50b35ec3d0740b723a076175731d1289c4a98ee5727aaec78c993abb0b78637cb6ac7ef4b550b81b473ff287d83c32405c8fbd345dbf05b0b3b72
data/CHANGELOG.md CHANGED
@@ -2,6 +2,11 @@
2
2
 
3
3
  All notable changes to this project will be documented in this file.
4
4
 
5
+ ## [0.29.0]
6
+
7
+ - Allow steering Slack conversations mid-run by sending follow-up messages that are folded in as user guidance at the next tool-loop boundary
8
+ - Propagate steering guidance into sub-agent runs so interruptions are seen by both the main engine and any active sub-agent
9
+
5
10
  ## [0.28.0]
6
11
 
7
12
  - Add `bin/smoke_model.rb` to smoke-test new models (plain, tool, parallel, cache checks)
@@ -11,6 +11,9 @@ module RailsConsoleAi
11
11
  @thread_ts = thread_ts
12
12
  @user_name = user_name
13
13
  @reply_queue = Queue.new
14
+ @guidance_main = []
15
+ @guidance_sub = []
16
+ @guidance_mutex = Mutex.new
14
17
  @cancelled = false
15
18
  @log_prefix = "[#{@channel_id}/#{@thread_ts}] @#{@user_name}"
16
19
  @output_log = StringIO.new
@@ -18,6 +21,36 @@ module RailsConsoleAi
18
21
 
19
22
  def cancel!
20
23
  @cancelled = true
24
+ @guidance_mutex.synchronize do
25
+ @guidance_main.clear
26
+ @guidance_sub.clear
27
+ end
28
+ end
29
+
30
+ # Guidance is broadcast to both the main-engine queue and the sub-agent queue
31
+ # so a steering message arriving during a sub-agent run is seen by both layers
32
+ # (sub-agent reacts immediately; main engine reacts after delegate_task returns).
33
+ def add_guidance(text)
34
+ @guidance_mutex.synchronize do
35
+ @guidance_main << text
36
+ @guidance_sub << text
37
+ end
38
+ end
39
+
40
+ def drain_guidance(scope: :main)
41
+ @guidance_mutex.synchronize do
42
+ arr = scope == :sub ? @guidance_sub : @guidance_main
43
+ pending = arr.dup
44
+ arr.clear
45
+ pending
46
+ end
47
+ end
48
+
49
+ def pending_guidance?(scope: :main)
50
+ @guidance_mutex.synchronize do
51
+ arr = scope == :sub ? @guidance_sub : @guidance_main
52
+ !arr.empty?
53
+ end
21
54
  end
22
55
 
23
56
  def cancelled?
@@ -64,6 +64,18 @@ module RailsConsoleAi
64
64
  @parent.cancelled?
65
65
  end
66
66
 
67
+ def pending_guidance?
68
+ @parent.respond_to?(:pending_guidance?) && @parent.pending_guidance?(scope: :sub)
69
+ end
70
+
71
+ def drain_guidance
72
+ @parent.respond_to?(:drain_guidance) ? @parent.drain_guidance(scope: :sub) : []
73
+ end
74
+
75
+ def add_guidance(text)
76
+ @parent.add_guidance(text) if @parent.respond_to?(:add_guidance)
77
+ end
78
+
67
79
  def supports_danger?
68
80
  false # Sub-agents must never silently bypass safety guards
69
81
  end
@@ -803,6 +803,15 @@ module RailsConsoleAi
803
803
  break
804
804
  end
805
805
 
806
+ if round > 0 && @channel.respond_to?(:pending_guidance?) && @channel.pending_guidance?
807
+ pending = @channel.drain_guidance
808
+ guidance_text = format_user_interruption(pending)
809
+ guidance_msg = { role: :user, content: guidance_text }
810
+ messages << guidance_msg
811
+ new_messages << guidance_msg
812
+ @channel.display_status(" Steering: incorporating user guidance.")
813
+ end
814
+
806
815
  if round == 0
807
816
  @channel.display_status(" Thinking...")
808
817
  elsif last_thinking
@@ -1152,6 +1161,28 @@ module RailsConsoleAi
1152
1161
  str.length > max ? str[0..max] + '...' : str
1153
1162
  end
1154
1163
 
1164
+ # Wraps mid-task user messages with explicit framing so the model treats them
1165
+ # as a real-time interruption that supersedes the prior task, rather than as
1166
+ # a reply to the most recent tool result.
1167
+ def format_user_interruption(messages)
1168
+ joined = messages.map { |t| t.to_s.strip }.reject(&:empty?).join("\n\n")
1169
+ <<~MSG.strip
1170
+ [INTERRUPTION FROM USER — REAL-TIME MESSAGE]
1171
+
1172
+ The user sent the following message while you were working on the previous step.
1173
+ They sent it before seeing the result of your last tool call, so it is NOT a
1174
+ reply to that result. It is your most recent direction from the user and
1175
+ supersedes the prior task.
1176
+
1177
+ If they are telling you to stop, halt completely and acknowledge — do not
1178
+ autonomously switch to a different method to accomplish the original task.
1179
+ If their instruction is unclear, ask them what they want before continuing.
1180
+
1181
+ User message:
1182
+ "#{joined}"
1183
+ MSG
1184
+ end
1185
+
1155
1186
  def llm_status(round, messages, req_tokens, total_billed, last_thinking = nil, last_tool_names = [])
1156
1187
  status = "Calling LLM (round #{round + 1}, #{messages.length} msgs"
1157
1188
  status += ", ~#{format_tokens(req_tokens)} ctx" if req_tokens > 0
@@ -491,6 +491,14 @@ module RailsConsoleAi
491
491
  return
492
492
  end
493
493
 
494
+ # If the engine is mid-run, treat the message as steering guidance to be
495
+ # folded in at the next tool-loop boundary instead of restarting.
496
+ if session[:thread]&.alive? && channel.respond_to?(:add_guidance)
497
+ channel.add_guidance(text)
498
+ channel.display("Got it. One moment.")
499
+ return
500
+ end
501
+
494
502
  # Otherwise treat as a new message in the conversation
495
503
  replace_session_thread(session) do
496
504
  Thread.current.report_on_exception = false
@@ -66,6 +66,12 @@ module RailsConsoleAi
66
66
  max_rounds.times do |round|
67
67
  break if channel.cancelled?
68
68
 
69
+ if round > 0 && channel.respond_to?(:pending_guidance?) && channel.pending_guidance?
70
+ pending = channel.drain_guidance
71
+ messages << { role: :user, content: format_user_interruption(pending) }
72
+ channel.display_status(" Steering: incorporating user guidance.")
73
+ end
74
+
69
75
  if round == 0
70
76
  channel.display_status("Thinking...")
71
77
  end
@@ -147,6 +153,25 @@ module RailsConsoleAi
147
153
  result&.text || '(sub-agent returned no result)'
148
154
  end
149
155
 
156
+ def format_user_interruption(messages)
157
+ joined = messages.map { |t| t.to_s.strip }.reject(&:empty?).join("\n\n")
158
+ <<~MSG.strip
159
+ [INTERRUPTION FROM USER — REAL-TIME MESSAGE]
160
+
161
+ The user sent the following message while you were working. They sent it
162
+ before seeing your latest tool result, so it is NOT a reply to that result.
163
+ It is your most recent direction from the user and supersedes the prior task.
164
+
165
+ If they are telling you to stop, halt immediately and finish with a brief
166
+ acknowledgement — do not switch to a different method to accomplish the
167
+ original task on your own. If unclear, return what you have so far and let
168
+ the parent agent ask the user.
169
+
170
+ User message:
171
+ "#{joined}"
172
+ MSG
173
+ end
174
+
150
175
  def build_provider
151
176
  config = RailsConsoleAi.configuration
152
177
  model_override = @agent_config['model'] || config.sub_agent_model
@@ -1,3 +1,3 @@
1
1
  module RailsConsoleAi
2
- VERSION = '0.28.0'.freeze
2
+ VERSION = '0.29.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.28.0
4
+ version: 0.29.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Cortfr