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 +4 -4
- data/CHANGELOG.md +5 -0
- data/lib/rails_console_ai/channel/slack.rb +33 -0
- data/lib/rails_console_ai/channel/sub_agent.rb +12 -0
- data/lib/rails_console_ai/conversation_engine.rb +31 -0
- data/lib/rails_console_ai/slack_bot.rb +8 -0
- data/lib/rails_console_ai/sub_agent.rb +25 -0
- 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: 30c7955113bc3e4ce45989fcc0e93034185fbabc0e95acfa01df56ec397900b5
|
|
4
|
+
data.tar.gz: 87b0399d973e448404b234820067ed2f20d4148f78613963904c9e288a17d87a
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
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
|