rails_console_ai 0.26.0 → 0.27.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: a2194416a93ce8522de376169eb627b90c8b2477ba253f0f1b877144251f9ee6
4
- data.tar.gz: 65dcbeb6eef9dd2641181529aa671058e85a6cd1c5263acd51678c52c89d3567
3
+ metadata.gz: db0c0b3b95cc349906845d4058e1eadc6be4ab67e25f32a4b8e4262e61340a87
4
+ data.tar.gz: 0ba79ae87d8975193b2b6033279dc41a8841cf71e7f015ce8f36dafa6d62d463
5
5
  SHA512:
6
- metadata.gz: e4d5a1f2fe8ef6ae4593829ac1a8997eeeac652ea0d0a491f91a64b8e45a246d024a10aa269e853949e0d106779d4f1b9d671af21057bd6af802ff6a541ac15d
7
- data.tar.gz: ec0e0d25f7bb2605a7c4180ebc56ab56d8f590d3632c98310ec65c3aecd987d04118e7199534a8ea5a1a8023a505de9ac467ce05d01393df667c2436bcebc68c
6
+ metadata.gz: 615da46e5aa24149783309d69a3b739db1170459c1bf6d929c8ecfc875fbc0654117a7c5d15057aaf7d421660afa9959192f4112b25e722e5e224ce3de768fb0
7
+ data.tar.gz: cf807c3dfc0fae08a79013606c5e00383bc26994461c02e4135f8efb495540a0f677bff62eae01c2f396e9ce2de8ede7c82a95415b69831425447aa93361dbdb
@@ -207,6 +207,7 @@ module RailsConsoleAi
207
207
  break if input.nil?
208
208
 
209
209
  input = input.strip
210
+ input = input.force_encoding('UTF-8') if input.encoding == Encoding::ASCII_8BIT
210
211
  break if input.downcase == 'exit' || input.downcase == 'quit'
211
212
  next if input.empty?
212
213
 
@@ -249,7 +249,7 @@ module RailsConsoleAi
249
249
  output_id = @executor.store_output(result_str)
250
250
  if result_str.length > LARGE_OUTPUT_THRESHOLD
251
251
  preview = result_str[0, LARGE_OUTPUT_PREVIEW_CHARS]
252
- context_msg += "\n#{preview}\n\n[Output truncated at #{LARGE_OUTPUT_PREVIEW_CHARS} of #{result_str.length} chars — use recall_output tool with id #{output_id} to retrieve the full output]"
252
+ context_msg += "\n#{preview}\n\n[Output truncated at #{LARGE_OUTPUT_PREVIEW_CHARS} of #{result_str.length} chars — use explore_output with output_id=#{output_id} for focused queries, or recall_output to expand in place]"
253
253
  elsif !output_parts.empty?
254
254
  context_msg += "\n#{result_str}"
255
255
  end
@@ -332,7 +332,7 @@ module RailsConsoleAi
332
332
  context_msg = "Code was executed (safety override). "
333
333
  if result_str.length > LARGE_OUTPUT_THRESHOLD
334
334
  context_msg += result_str[0, LARGE_OUTPUT_PREVIEW_CHARS]
335
- context_msg += "\n\n[Output truncated at #{LARGE_OUTPUT_PREVIEW_CHARS} of #{result_str.length} chars — use recall_output tool with id #{output_id} to retrieve the full output]"
335
+ context_msg += "\n\n[Output truncated at #{LARGE_OUTPUT_PREVIEW_CHARS} of #{result_str.length} chars — use explore_output with output_id=#{output_id} for focused queries, or recall_output to expand in place]"
336
336
  else
337
337
  context_msg += result_str
338
338
  end
@@ -360,7 +360,7 @@ module RailsConsoleAi
360
360
  context_msg = "Code was executed. "
361
361
  if result_str.length > LARGE_OUTPUT_THRESHOLD
362
362
  context_msg += result_str[0, LARGE_OUTPUT_PREVIEW_CHARS]
363
- context_msg += "\n\n[Output truncated at #{LARGE_OUTPUT_PREVIEW_CHARS} of #{result_str.length} chars — use recall_output tool with id #{output_id} to retrieve the full output]"
363
+ context_msg += "\n\n[Output truncated at #{LARGE_OUTPUT_PREVIEW_CHARS} of #{result_str.length} chars — use explore_output with output_id=#{output_id} for focused queries, or recall_output to expand in place]"
364
364
  else
365
365
  context_msg += result_str
366
366
  end
@@ -903,7 +903,7 @@ module RailsConsoleAi
903
903
  tool_msg[:output_id] = output_id
904
904
  if full_text.length > LARGE_OUTPUT_THRESHOLD
905
905
  truncated = full_text[0, LARGE_OUTPUT_PREVIEW_CHARS]
906
- truncated += "\n\n[Output truncated at #{LARGE_OUTPUT_PREVIEW_CHARS} of #{full_text.length} chars — use recall_output tool with id #{output_id} to retrieve the full output]"
906
+ truncated += "\n\n[Output truncated at #{LARGE_OUTPUT_PREVIEW_CHARS} of #{full_text.length} chars — use explore_output with output_id=#{output_id} for focused queries, or recall_output to expand in place]"
907
907
  tool_msg = provider.format_tool_result(tc[:id], truncated)
908
908
  tool_msg[:output_id] = output_id
909
909
  end
@@ -1041,6 +1041,10 @@ module RailsConsoleAi
1041
1041
  when 'save_skill' then "(\"#{args['name']}\")"
1042
1042
  when 'delete_skill' then "(\"#{args['name']}\")"
1043
1043
  when 'recall_output' then "(#{args['id']})"
1044
+ when 'explore_output'
1045
+ task_preview = args['task'].to_s[0, 80]
1046
+ task_preview += '...' if args['task'].to_s.length > 80
1047
+ "(id: #{args['output_id']}, \"#{task_preview}\")"
1044
1048
  when 'execute_plan'
1045
1049
  steps = args['steps']
1046
1050
  steps ? "(#{steps.length} steps)" : ''
@@ -1409,7 +1413,7 @@ module RailsConsoleAi
1409
1413
  end
1410
1414
 
1411
1415
  def trim_message(msg)
1412
- ref = "[Output omitted — use recall_output tool with id #{msg[:output_id]} to retrieve]"
1416
+ ref = "[Output omitted — use explore_output with output_id=#{msg[:output_id]} for focused queries, or recall_output to expand in place]"
1413
1417
 
1414
1418
  if msg[:content].is_a?(Array)
1415
1419
  trimmed_content = msg[:content].map do |block|
@@ -12,12 +12,15 @@ module RailsConsoleAi
12
12
 
13
13
  attr_reader :input_tokens, :output_tokens, :model_used
14
14
 
15
- def initialize(task:, agent_config:, binding_context:, parent_channel:, executor:)
15
+ def initialize(task:, agent_config:, binding_context:, parent_channel:, executor:,
16
+ output_payload: nil, output_local_name: :output)
16
17
  @task = task
17
18
  @agent_config = agent_config || {}
18
19
  @binding_context = binding_context
19
20
  @parent_channel = parent_channel
20
21
  @parent_executor = executor
22
+ @output_payload = output_payload
23
+ @output_local_name = output_local_name
21
24
  @input_tokens = 0
22
25
  @output_tokens = 0
23
26
  @model_used = nil
@@ -29,7 +32,16 @@ module RailsConsoleAi
29
32
  task_label: @agent_config['name']
30
33
  )
31
34
 
32
- executor = Executor.new(@binding_context, channel: channel)
35
+ effective_binding =
36
+ if @output_payload
37
+ b = @binding_context.eval("proc { binding }.call")
38
+ b.local_variable_set(@output_local_name, @output_payload)
39
+ b
40
+ else
41
+ @binding_context
42
+ end
43
+
44
+ executor = Executor.new(effective_binding, channel: channel)
33
45
  allowed_tools = @agent_config['tools'] ? Array(@agent_config['tools']) : nil
34
46
  tools = Tools::Registry.new(executor: executor, mode: :sub_agent, channel: channel, allowed_tools: allowed_tools)
35
47
  provider = build_provider
@@ -6,7 +6,7 @@ module RailsConsoleAi
6
6
  attr_reader :definitions, :last_sub_agent_usage
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 recall_memory execute_code execute_plan activate_skill save_skill delete_skill delegate_task].freeze
9
+ NO_CACHE = %w[ask_user save_memory delete_memory recall_memory execute_code execute_plan activate_skill save_skill delete_skill delegate_task explore_output].freeze
10
10
 
11
11
  def initialize(executor: nil, mode: :default, channel: nil, allowed_tools: nil)
12
12
  @executor = executor
@@ -188,7 +188,7 @@ module RailsConsoleAi
188
188
  if @executor
189
189
  register(
190
190
  name: 'recall_output',
191
- description: 'Retrieve a previous code execution output that was omitted or truncated. The output will be expanded in place in the conversation. Use the output id shown in the "[Output omitted]" or "[Output truncated]" placeholder.',
191
+ description: 'Expand a previously omitted/truncated output back into this conversation\'s context, where it will persist for the rest of the session. Prefer `explore_output` if you only need a specific answer about the output — that keeps this conversation lean. Use `recall_output` only when you need the full content alongside other context here. Use the output id shown in the "[Output omitted]" or "[Output truncated]" placeholder.',
192
192
  parameters: {
193
193
  'type' => 'object',
194
194
  'properties' => {
@@ -204,7 +204,7 @@ module RailsConsoleAi
204
204
 
205
205
  register(
206
206
  name: 'recall_outputs',
207
- description: 'Retrieve multiple previous code execution outputs that were omitted from the conversation. Use the output ids shown in "[Output omitted]" or "[Output truncated]" placeholders.',
207
+ description: 'Expand multiple previously omitted outputs back into this conversation. Prefer `explore_output` per-id for focused queries. Use the output ids shown in "[Output omitted]" or "[Output truncated]" placeholders.',
208
208
  parameters: {
209
209
  'type' => 'object',
210
210
  'properties' => {
@@ -214,6 +214,22 @@ module RailsConsoleAi
214
214
  },
215
215
  handler: ->(args) { "recall_outputs handled by conversation engine" }
216
216
  )
217
+
218
+ if @mode != :sub_agent
219
+ register(
220
+ name: 'explore_output',
221
+ description: 'Prefer this over recall_output when you have a specific question about a large omitted/truncated output (e.g. "find the item where X", "how many match Y", "what is the value at index N", "parse the JSON and return field Z"). Spawns a sub-agent with the full output bound to the local Ruby variable `output` (a String); the sub-agent runs execute_code against it and returns a concise answer. The full output does NOT enter this conversation.',
222
+ parameters: {
223
+ 'type' => 'object',
224
+ 'properties' => {
225
+ 'output_id' => { 'type' => 'integer', 'description' => 'The output id shown in the "[Output omitted]" or "[Output truncated]" placeholder.' },
226
+ 'task' => { 'type' => 'string', 'description' => 'The specific question or task. Be concrete — the sub-agent only sees this task and the output.' }
227
+ },
228
+ 'required' => ['output_id', 'task']
229
+ },
230
+ handler: ->(args) { explore_output(args['output_id'].to_i, args['task']) }
231
+ )
232
+ end
217
233
  end
218
234
 
219
235
  unless @mode == :init
@@ -317,6 +333,45 @@ module RailsConsoleAi
317
333
  )
318
334
  end
319
335
 
336
+ EXPLORE_OUTPUT_AGENT_CONFIG = {
337
+ 'name' => 'output-explorer',
338
+ 'tools' => ['execute_code'],
339
+ 'max_rounds' => 8,
340
+ 'body' => <<~PROMPT.freeze
341
+ You are exploring a single chunk of captured tool output on behalf of the main assistant.
342
+
343
+ The full output is bound to the local variable `output` (a String). You do NOT see it
344
+ directly — it lives in Ruby memory. Use `execute_code` with Ruby to query it:
345
+ - `output.length`, `output.lines.count`
346
+ - `output[start, len]`, `output.lines[n]`
347
+ - `output.scan(/pattern/)`, `output.include?("...")`
348
+ - `JSON.parse(output)` if it looks like JSON, then drill in
349
+ - any other Ruby string/collection methods
350
+
351
+ Print only the specific slice or summary the task requires — never dump the whole `output`.
352
+ Return a concise factual answer. No preamble.
353
+ PROMPT
354
+ }.freeze
355
+
356
+ def explore_output(output_id, task)
357
+ require 'rails_console_ai/sub_agent'
358
+
359
+ payload = @executor.recall_output(output_id)
360
+ return "No output found with id #{output_id}" unless payload
361
+
362
+ sub = SubAgent.new(
363
+ task: task,
364
+ agent_config: EXPLORE_OUTPUT_AGENT_CONFIG,
365
+ binding_context: @executor.binding_context,
366
+ parent_channel: @channel,
367
+ executor: @executor,
368
+ output_payload: payload.dup
369
+ )
370
+ result = sub.run
371
+ @last_sub_agent_usage = { input: sub.input_tokens, output: sub.output_tokens, model: sub.model_used }
372
+ "Exploration result (#{sub.input_tokens + sub.output_tokens} tokens used, #{payload.length} chars explored):\n#{result}"
373
+ end
374
+
320
375
  def delegate_task(task, agent_name = nil)
321
376
  require 'rails_console_ai/sub_agent'
322
377
  require 'rails_console_ai/agent_loader'
@@ -1,3 +1,3 @@
1
1
  module RailsConsoleAi
2
- VERSION = '0.26.0'.freeze
2
+ VERSION = '0.27.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.26.0
4
+ version: 0.27.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Cortfr