rails_console_ai 0.31.0 → 0.33.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.
@@ -338,19 +338,27 @@ module RailsConsoleAi
338
338
  'name' => 'output-explorer',
339
339
  'tools' => ['execute_code'],
340
340
  'max_rounds' => 8,
341
+ 'skip_base_instructions' => true,
341
342
  'body' => <<~PROMPT.freeze
342
- You are exploring a single chunk of captured tool output on behalf of the main assistant.
343
+ You are exploring a single chunk of already-captured tool output on behalf of the
344
+ main assistant. Your ONLY job is to answer a question about that captured text.
343
345
 
344
346
  The full output is bound to the local variable `output` (a String). You do NOT see it
345
- directly — it lives in Ruby memory. Use `execute_code` with Ruby to query it:
347
+ directly — it lives in Ruby memory. Use `execute_code` with Ruby to examine it:
346
348
  - `output.length`, `output.lines.count`
347
349
  - `output[start, len]`, `output.lines[n]`
348
350
  - `output.scan(/pattern/)`, `output.include?("...")`
349
351
  - `JSON.parse(output)` if it looks like JSON, then drill in
350
352
  - any other Ruby string/collection methods
351
353
 
352
- Print only the specific slice or summary the task requires — never dump the whole `output`.
353
- Return a concise factual answer. No preamble.
354
+ Database / ActiveRecord access is DISABLED in this context — any DB query (e.g.
355
+ `SomeModel.where(...)`, `.find`, raw SQL) will be blocked and raise an error. The data
356
+ you need exists ONLY in the `output` String already in memory. Do not load models, do
357
+ not run queries, and do not try to "look up" anything — every fact must come from
358
+ examining `output`.
359
+
360
+ Print only the specific slice or summary the task requires — never dump the whole
361
+ `output`. Return a concise factual answer. No preamble.
354
362
  PROMPT
355
363
  }.freeze
356
364
 
@@ -368,7 +376,11 @@ module RailsConsoleAi
368
376
  executor: @executor,
369
377
  output_payload: payload.dup
370
378
  )
371
- result = sub.run
379
+ # Hard sandbox: the output-explorer only examines the in-memory `output` string.
380
+ # Block all DB access for the duration of its run so a stray query can't escape to
381
+ # the live database. Scoped via thread-local (explore_output runs synchronously),
382
+ # so the parent session's guard state is untouched.
383
+ result = RailsConsoleAi.configuration.safety_guards.with_database_blocked { sub.run }
372
384
  @last_sub_agent_usage = { input: sub.input_tokens, output: sub.output_tokens, model: sub.model_used }
373
385
  "Exploration result (#{sub.input_tokens + sub.output_tokens} tokens used, #{payload.length} chars explored):\n#{result}"
374
386
  end
@@ -418,7 +430,7 @@ module RailsConsoleAi
418
430
 
419
431
  register(
420
432
  name: 'save_memory',
421
- description: 'Save a fact or pattern you learned about this codebase for future sessions. Use after discovering how something works (e.g. sharding, auth, custom business logic). Defaults to the versioned DB store; pass target: "file" to write to the on-disk .rails_console_ai/memories directory instead.',
433
+ description: 'Save a fact or pattern you learned about this codebase for future sessions. Use after discovering how something works (e.g. sharding, auth, custom business logic). Defaults to the versioned DB store; pass target: "file" to write to the on-disk .rails_console_ai/memories directory instead. IMPORTANT: memories saved to the DB start in "proposed" state and must be approved by a human in the web UI before you can recall them. Edits to an approved memory also revert it to proposed. Tell the user to visit /rails_console_ai/memories to approve.',
422
434
  parameters: {
423
435
  'type' => 'object',
424
436
  'properties' => {
@@ -686,13 +698,16 @@ module RailsConsoleAi
686
698
  end
687
699
 
688
700
  if @executor.last_error
689
- return "ERROR: #{@executor.last_error}"
701
+ result = "ERROR: #{@executor.last_error}"
702
+ result += "\n\n#{@executor.last_error_hint}" if @executor.last_error_hint
703
+ return result
690
704
  end
691
705
 
692
706
  output = @executor.last_output
693
707
  parts = []
694
708
  parts << "Output:\n#{output.strip}" if output && !output.strip.empty?
695
709
  parts << "Return value: #{exec_result.inspect}"
710
+ parts << @executor.last_error_hint if @executor.last_error_hint
696
711
  parts.join("\n\n")
697
712
  end
698
713
 
@@ -796,6 +811,7 @@ module RailsConsoleAi
796
811
  step_report = "Step #{i + 1} (#{step['description']}):\n"
797
812
  if error
798
813
  step_report += "ERROR: #{error}\n"
814
+ step_report += "#{@executor.last_error_hint}\n" if @executor.last_error_hint
799
815
  end
800
816
  if output && !output.strip.empty?
801
817
  step_report += "Output: #{output.strip}\n"
@@ -1,3 +1,3 @@
1
1
  module RailsConsoleAi
2
- VERSION = '0.31.0'.freeze
2
+ VERSION = '0.33.0'.freeze
3
3
  end
@@ -1,3 +1,5 @@
1
+ require 'set'
2
+
1
3
  require 'rails_console_ai/version'
2
4
  require 'rails_console_ai/configuration'
3
5
 
@@ -84,7 +86,7 @@ module RailsConsoleAi
84
86
 
85
87
  # Returns the current status string for an enqueued agent run, or nil
86
88
  # if the session id is not found. Status is one of:
87
- # 'queued' | 'running' | 'ready' | 'failed'.
89
+ # 'queued' | 'running' | 'ready' | 'failed' | 'aborted'.
88
90
  def check_agent(session_id)
89
91
  Session.where(id: session_id).pluck(:status).first
90
92
  end
@@ -98,6 +100,16 @@ module RailsConsoleAi
98
100
  { status: row.status, result: row.result, error: row.error_message }
99
101
  end
100
102
 
103
+ # Abort a queued or running agent run. Returns true if the run was
104
+ # aborted, false if it had already finished (or doesn't exist).
105
+ # Queued runs are never picked up; a run already executing keeps
106
+ # going but its result is discarded when it completes.
107
+ def abort_agent(session_id)
108
+ n = Session.where(id: session_id, status: %w[queued running])
109
+ .update_all(status: 'aborted', error_message: 'Aborted')
110
+ n == 1
111
+ end
112
+
101
113
  def status
102
114
  c = configuration
103
115
  key = c.resolved_api_key
@@ -242,20 +254,41 @@ module RailsConsoleAi
242
254
  conn.create_table(memories_table) do |t|
243
255
  t.string :name, limit: 255, null: false
244
256
  t.text :content, null: false
257
+ t.string :status, limit: 20, default: 'proposed', null: false
258
+ t.string :approved_by, limit: 255
259
+ t.datetime :approved_at
245
260
  t.integer :use_count, default: 0, null: false
246
261
  t.datetime :last_used_at
247
262
  t.datetime :created_at, null: false
248
263
  t.datetime :updated_at, null: false
249
264
  end
250
265
  conn.add_index(memories_table, :name, unique: true)
266
+ conn.add_index(memories_table, :status)
251
267
  $stdout.puts "\e[32mRailsConsoleAi: created #{memories_table} table.\e[0m"
252
268
  end
253
269
 
270
+ # Existing installs have the content-based memories table but predate the
271
+ # approval columns — the drop-on-missing-content guard above won't fire for
272
+ # them, so add the columns in place. Memories created before approval existed
273
+ # were trusted under the old no-approval regime, so grandfather them to
274
+ # "approved" rather than yanking them out from under the AI; only memories
275
+ # created from now on start in "proposed".
276
+ if conn.table_exists?(memories_table) && !conn.column_exists?(memories_table, :status)
277
+ conn.add_column(memories_table, :status, :string, limit: 20, default: 'proposed', null: false)
278
+ conn.execute("UPDATE #{conn.quote_table_name(memories_table)} SET status = 'approved'")
279
+ end
280
+ if conn.table_exists?(memories_table)
281
+ conn.add_column(memories_table, :approved_by, :string, limit: 255) unless conn.column_exists?(memories_table, :approved_by)
282
+ conn.add_column(memories_table, :approved_at, :datetime) unless conn.column_exists?(memories_table, :approved_at)
283
+ conn.add_index(memories_table, :status) unless conn.index_exists?(memories_table, :status)
284
+ end
285
+
254
286
  unless conn.table_exists?(versions_table)
255
287
  conn.create_table(versions_table) do |t|
256
288
  t.integer :memory_id
257
289
  t.string :name, limit: 255
258
290
  t.text :content
291
+ t.string :status, limit: 20
259
292
  t.string :edited_by, limit: 255
260
293
  t.text :change_note
261
294
  t.datetime :created_at, null: false
@@ -264,6 +297,10 @@ module RailsConsoleAi
264
297
  conn.add_index(versions_table, :created_at)
265
298
  $stdout.puts "\e[32mRailsConsoleAi: created #{versions_table} table.\e[0m"
266
299
  end
300
+
301
+ if conn.table_exists?(versions_table) && !conn.column_exists?(versions_table, :status)
302
+ conn.add_column(versions_table, :status, :string, limit: 20)
303
+ end
267
304
  end
268
305
 
269
306
  def setup_agents_tables!(conn)
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.31.0
4
+ version: 0.33.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Cortfr