rails_console_ai 0.29.0 → 0.30.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 +40 -0
- data/README.md +48 -0
- data/app/controllers/rails_console_ai/agent_versions_controller.rb +36 -0
- data/app/controllers/rails_console_ai/agents_controller.rb +199 -0
- data/app/controllers/rails_console_ai/application_controller.rb +5 -0
- data/app/controllers/rails_console_ai/memories_controller.rb +159 -0
- data/app/controllers/rails_console_ai/memory_versions_controller.rb +33 -0
- data/app/controllers/rails_console_ai/skill_versions_controller.rb +35 -0
- data/app/controllers/rails_console_ai/skills_controller.rb +200 -0
- data/app/helpers/rails_console_ai/diff_helper.rb +114 -0
- data/app/models/rails_console_ai/agent.rb +175 -0
- data/app/models/rails_console_ai/agent_version.rb +46 -0
- data/app/models/rails_console_ai/memory.rb +98 -0
- data/app/models/rails_console_ai/memory_version.rb +46 -0
- data/app/models/rails_console_ai/session.rb +1 -1
- data/app/models/rails_console_ai/skill.rb +198 -0
- data/app/models/rails_console_ai/skill_version.rb +54 -0
- data/app/views/layouts/rails_console_ai/application.html.erb +78 -1
- data/app/views/rails_console_ai/agent_versions/index.html.erb +28 -0
- data/app/views/rails_console_ai/agent_versions/show.html.erb +25 -0
- data/app/views/rails_console_ai/agents/_form.html.erb +65 -0
- data/app/views/rails_console_ai/agents/diff.html.erb +19 -0
- data/app/views/rails_console_ai/agents/edit.html.erb +7 -0
- data/app/views/rails_console_ai/agents/index.html.erb +80 -0
- data/app/views/rails_console_ai/agents/new.html.erb +24 -0
- data/app/views/rails_console_ai/agents/show.html.erb +108 -0
- data/app/views/rails_console_ai/memories/_form.html.erb +36 -0
- data/app/views/rails_console_ai/memories/diff.html.erb +19 -0
- data/app/views/rails_console_ai/memories/edit.html.erb +7 -0
- data/app/views/rails_console_ai/memories/index.html.erb +67 -0
- data/app/views/rails_console_ai/memories/new.html.erb +23 -0
- data/app/views/rails_console_ai/memories/show.html.erb +65 -0
- data/app/views/rails_console_ai/memory_versions/index.html.erb +26 -0
- data/app/views/rails_console_ai/memory_versions/show.html.erb +21 -0
- data/app/views/rails_console_ai/skill_versions/index.html.erb +28 -0
- data/app/views/rails_console_ai/skill_versions/show.html.erb +23 -0
- data/app/views/rails_console_ai/skills/_form.html.erb +65 -0
- data/app/views/rails_console_ai/skills/diff.html.erb +22 -0
- data/app/views/rails_console_ai/skills/edit.html.erb +7 -0
- data/app/views/rails_console_ai/skills/index.html.erb +79 -0
- data/app/views/rails_console_ai/skills/new.html.erb +25 -0
- data/app/views/rails_console_ai/skills/show.html.erb +94 -0
- data/config/routes.rb +42 -0
- data/lib/rails_console_ai/agent_loader.rb +131 -43
- data/lib/rails_console_ai/agent_runner.rb +158 -0
- data/lib/rails_console_ai/channel/api.rb +139 -0
- data/lib/rails_console_ai/conversation_engine.rb +19 -13
- data/lib/rails_console_ai/session_logger.rb +6 -0
- data/lib/rails_console_ai/skill_loader.rb +119 -27
- data/lib/rails_console_ai/storage/database_storage.rb +201 -0
- data/lib/rails_console_ai/tools/memory_tools.rb +102 -32
- data/lib/rails_console_ai/tools/registry.rb +99 -8
- data/lib/rails_console_ai/version.rb +1 -1
- data/lib/rails_console_ai.rb +256 -0
- data/lib/tasks/rails_console_ai.rake +7 -0
- metadata +55 -1
|
@@ -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 explore_output].freeze
|
|
9
|
+
NO_CACHE = %w[ask_user save_memory delete_memory recall_memory execute_code execute_plan activate_skill save_skill delete_skill save_agent delete_agent delegate_task explore_output].freeze
|
|
10
10
|
|
|
11
11
|
def initialize(executor: nil, mode: :default, channel: nil, allowed_tools: nil)
|
|
12
12
|
@executor = executor
|
|
@@ -254,6 +254,7 @@ module RailsConsoleAi
|
|
|
254
254
|
|
|
255
255
|
register_memory_tools
|
|
256
256
|
register_skill_tools
|
|
257
|
+
register_agent_tools
|
|
257
258
|
register_execute_plan
|
|
258
259
|
register_delegate_task
|
|
259
260
|
end
|
|
@@ -381,9 +382,20 @@ module RailsConsoleAi
|
|
|
381
382
|
loader = AgentLoader.new
|
|
382
383
|
agent_config = loader.find_agent(agent_name)
|
|
383
384
|
unless agent_config
|
|
384
|
-
|
|
385
|
+
# Distinguish "doesn't exist" from "exists but isn't approved yet".
|
|
386
|
+
proposed = loader.find_any_agent(agent_name)
|
|
387
|
+
if proposed && proposed['source'] == :db && proposed['status'] != 'approved'
|
|
388
|
+
return "Agent \"#{agent_name}\" exists but is awaiting human approval and cannot be invoked yet. " \
|
|
389
|
+
"Ask the user to approve it in the web UI at /rails_console_ai/agents."
|
|
390
|
+
end
|
|
391
|
+
available = loader.load_activatable_agents.map { |a| a['name'] }
|
|
385
392
|
return "Agent not found: \"#{agent_name}\". Available agents: #{available.join(', ')}"
|
|
386
393
|
end
|
|
394
|
+
|
|
395
|
+
# Usage tracking — DB-backed agents only.
|
|
396
|
+
if agent_config['source'] == :db && agent_config['id']
|
|
397
|
+
RailsConsoleAi::Agent.record_use!(agent_config['id'])
|
|
398
|
+
end
|
|
387
399
|
end
|
|
388
400
|
|
|
389
401
|
sub = SubAgent.new(
|
|
@@ -406,18 +418,26 @@ module RailsConsoleAi
|
|
|
406
418
|
|
|
407
419
|
register(
|
|
408
420
|
name: 'save_memory',
|
|
409
|
-
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).',
|
|
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.',
|
|
410
422
|
parameters: {
|
|
411
423
|
'type' => 'object',
|
|
412
424
|
'properties' => {
|
|
413
425
|
'name' => { 'type' => 'string', 'description' => 'Short name for this memory (e.g. "Sharding architecture")' },
|
|
414
426
|
'description' => { 'type' => 'string', 'description' => 'Detailed description of what you learned' },
|
|
415
|
-
'tags' => { 'type' => 'array', 'items' => { 'type' => 'string' }, 'description' => 'Optional tags (e.g. ["database", "sharding"])' }
|
|
427
|
+
'tags' => { 'type' => 'array', 'items' => { 'type' => 'string' }, 'description' => 'Optional tags (e.g. ["database", "sharding"])' },
|
|
428
|
+
'target' => { 'type' => 'string', 'enum' => ['db', 'file'], 'description' => 'Where to store this memory. "db" (default) is versioned and editable via the web UI; "file" writes a Markdown file under .rails_console_ai/memories/.' },
|
|
429
|
+
'change_note' => { 'type' => 'string', 'description' => 'Optional one-line note describing this edit (DB store only).' }
|
|
416
430
|
},
|
|
417
431
|
'required' => ['name', 'description']
|
|
418
432
|
},
|
|
419
433
|
handler: ->(args) {
|
|
420
|
-
memory.save_memory(
|
|
434
|
+
memory.save_memory(
|
|
435
|
+
name: args['name'],
|
|
436
|
+
description: args['description'],
|
|
437
|
+
tags: args['tags'] || [],
|
|
438
|
+
target: (args['target'] || 'db').to_sym,
|
|
439
|
+
change_note: args['change_note']
|
|
440
|
+
)
|
|
421
441
|
}
|
|
422
442
|
)
|
|
423
443
|
|
|
@@ -480,19 +500,30 @@ module RailsConsoleAi
|
|
|
480
500
|
handler: ->(args) {
|
|
481
501
|
skill = loader.find_skill(args['name'])
|
|
482
502
|
unless skill
|
|
503
|
+
# Distinguish "doesn't exist" from "exists but isn't approved yet".
|
|
504
|
+
proposed = loader.find_any_skill(args['name'])
|
|
505
|
+
if proposed && proposed['source'] == :db && proposed['status'] != 'approved'
|
|
506
|
+
return "Skill \"#{args['name']}\" exists but is awaiting human approval and cannot be activated yet. " \
|
|
507
|
+
"Ask the user to approve it in the web UI at /rails_console_ai/skills."
|
|
508
|
+
end
|
|
483
509
|
return "Skill not found: \"#{args['name']}\". Use the skills listed in the system prompt."
|
|
484
510
|
end
|
|
485
511
|
|
|
486
512
|
bypass_methods = Array(skill['bypass_guards_for_methods'])
|
|
487
513
|
@executor.activate_skill_bypasses(bypass_methods) unless bypass_methods.empty?
|
|
488
514
|
|
|
515
|
+
# Usage tracking — DB-backed skills only (file skills have no row to update).
|
|
516
|
+
if skill['source'] == :db && skill['id']
|
|
517
|
+
RailsConsoleAi::Skill.record_use!(skill['id'])
|
|
518
|
+
end
|
|
519
|
+
|
|
489
520
|
skill['body']
|
|
490
521
|
}
|
|
491
522
|
)
|
|
492
523
|
|
|
493
524
|
register(
|
|
494
525
|
name: 'save_skill',
|
|
495
|
-
description: 'Create or update a skill — a reusable procedure for a specific operation. Use when the user asks you to create a skill, recipe, or runbook. Skills differ from memories: a skill is a step-by-step procedure to follow, while a memory is a fact or pattern you learned.',
|
|
526
|
+
description: 'Create or update a skill — a reusable procedure for a specific operation. Use when the user asks you to create a skill, recipe, or runbook. Skills differ from memories: a skill is a step-by-step procedure to follow, while a memory is a fact or pattern you learned. Defaults to the versioned DB store; pass target: "file" to write to the on-disk .rails_console_ai/skills directory instead. IMPORTANT: skills saved to the DB start in "proposed" state and must be approved by a human in the web UI before you can activate them. Edits to an approved skill also revert it to proposed. Tell the user to visit /rails_console_ai/skills to approve.',
|
|
496
527
|
parameters: {
|
|
497
528
|
'type' => 'object',
|
|
498
529
|
'properties' => {
|
|
@@ -500,7 +531,9 @@ module RailsConsoleAi
|
|
|
500
531
|
'description' => { 'type' => 'string', 'description' => 'One-line description of when to use this skill' },
|
|
501
532
|
'body' => { 'type' => 'string', 'description' => 'The full skill recipe in markdown. Include: ## When to use, ## Recipe (numbered steps with code blocks), ## Notes (optional).' },
|
|
502
533
|
'tags' => { 'type' => 'array', 'items' => { 'type' => 'string' }, 'description' => 'Optional tags for categorization (e.g. ["booking-page", "admin"])' },
|
|
503
|
-
'bypass_guards_for_methods' => { 'type' => 'array', 'items' => { 'type' => 'string' }, 'description' => 'Methods that should bypass safety guards when this skill is active (e.g. ["BookingPage#save!", "BookingPage#ensure_subdomain_set!"])' }
|
|
534
|
+
'bypass_guards_for_methods' => { 'type' => 'array', 'items' => { 'type' => 'string' }, 'description' => 'Methods that should bypass safety guards when this skill is active (e.g. ["BookingPage#save!", "BookingPage#ensure_subdomain_set!"])' },
|
|
535
|
+
'target' => { 'type' => 'string', 'enum' => ['db', 'file'], 'description' => 'Where to store this skill. "db" (default) is versioned and editable via the web UI; "file" writes a Markdown file under .rails_console_ai/skills/.' },
|
|
536
|
+
'change_note' => { 'type' => 'string', 'description' => 'Optional one-line note describing this edit (DB store only).' }
|
|
504
537
|
},
|
|
505
538
|
'required' => %w[name description body]
|
|
506
539
|
},
|
|
@@ -510,7 +543,9 @@ module RailsConsoleAi
|
|
|
510
543
|
description: args['description'],
|
|
511
544
|
body: args['body'],
|
|
512
545
|
tags: args['tags'] || [],
|
|
513
|
-
bypass_guards_for_methods: args['bypass_guards_for_methods'] || []
|
|
546
|
+
bypass_guards_for_methods: args['bypass_guards_for_methods'] || [],
|
|
547
|
+
target: (args['target'] || 'db').to_sym,
|
|
548
|
+
change_note: args['change_note']
|
|
514
549
|
)
|
|
515
550
|
}
|
|
516
551
|
)
|
|
@@ -529,6 +564,62 @@ module RailsConsoleAi
|
|
|
529
564
|
)
|
|
530
565
|
end
|
|
531
566
|
|
|
567
|
+
def register_agent_tools
|
|
568
|
+
return unless @executor
|
|
569
|
+
|
|
570
|
+
require 'rails_console_ai/agent_loader'
|
|
571
|
+
loader = RailsConsoleAi::AgentLoader.new
|
|
572
|
+
|
|
573
|
+
register(
|
|
574
|
+
name: 'save_agent',
|
|
575
|
+
description: 'Create or update a sub-agent definition — a reusable specialist that the main assistant can invoke via delegate_task. ' \
|
|
576
|
+
'Use when the user asks you to create a new agent, recipe-agent, or sub-task specialist. ' \
|
|
577
|
+
'Agents differ from skills: an agent runs in its own sub-conversation with a constrained tool set, while a skill is a procedure followed inline. ' \
|
|
578
|
+
'Defaults to the versioned DB store; pass target: "file" to write to the on-disk .rails_console_ai/agents directory instead. ' \
|
|
579
|
+
'IMPORTANT: agents saved to the DB start in "proposed" state and must be approved by a human in the web UI before delegate_task can invoke them. ' \
|
|
580
|
+
'Edits to an approved agent also revert it to proposed. Tell the user to visit /rails_console_ai/agents to approve.',
|
|
581
|
+
parameters: {
|
|
582
|
+
'type' => 'object',
|
|
583
|
+
'properties' => {
|
|
584
|
+
'name' => { 'type' => 'string', 'description' => 'Agent name (e.g. "Investigate billing"), shown in the Agents list and used as the delegate_task `agent` parameter' },
|
|
585
|
+
'description' => { 'type' => 'string', 'description' => 'One-line description of what this agent specializes in' },
|
|
586
|
+
'body' => { 'type' => 'string', 'description' => 'The agent\'s system instructions in markdown. Include: persona, strategy, rules, expected output format.' },
|
|
587
|
+
'max_rounds' => { 'type' => 'integer', 'description' => 'Optional: maximum tool-loop iterations for the sub-agent (defaults to global config). Use a smaller number for tightly-scoped agents.' },
|
|
588
|
+
'model' => { 'type' => 'string', 'description' => 'Optional: model override for this agent (e.g. "claude-haiku-4" for cheap fast agents)' },
|
|
589
|
+
'tools' => { 'type' => 'array', 'items' => { 'type' => 'string' }, 'description' => 'Optional: whitelist of tool names the sub-agent is allowed to call. Omit to allow the default set.' },
|
|
590
|
+
'target' => { 'type' => 'string', 'enum' => ['db', 'file'], 'description' => 'Where to store this agent. "db" (default) is versioned and editable via the web UI; "file" writes a Markdown file under .rails_console_ai/agents/.' },
|
|
591
|
+
'change_note' => { 'type' => 'string', 'description' => 'Optional one-line note describing this edit (DB store only).' }
|
|
592
|
+
},
|
|
593
|
+
'required' => %w[name description body]
|
|
594
|
+
},
|
|
595
|
+
handler: ->(args) {
|
|
596
|
+
loader.save_agent(
|
|
597
|
+
name: args['name'],
|
|
598
|
+
description: args['description'],
|
|
599
|
+
body: args['body'],
|
|
600
|
+
max_rounds: args['max_rounds'],
|
|
601
|
+
model: args['model'],
|
|
602
|
+
tools: args['tools'] || [],
|
|
603
|
+
target: (args['target'] || 'db').to_sym,
|
|
604
|
+
change_note: args['change_note']
|
|
605
|
+
)
|
|
606
|
+
}
|
|
607
|
+
)
|
|
608
|
+
|
|
609
|
+
register(
|
|
610
|
+
name: 'delete_agent',
|
|
611
|
+
description: 'Delete a sub-agent by name. Built-in (gem-shipped) agents cannot be deleted; this tool will tell you that and suggest creating a same-named override instead.',
|
|
612
|
+
parameters: {
|
|
613
|
+
'type' => 'object',
|
|
614
|
+
'properties' => {
|
|
615
|
+
'name' => { 'type' => 'string', 'description' => 'The agent name to delete' }
|
|
616
|
+
},
|
|
617
|
+
'required' => ['name']
|
|
618
|
+
},
|
|
619
|
+
handler: ->(args) { loader.delete_agent(name: args['name']) }
|
|
620
|
+
)
|
|
621
|
+
end
|
|
622
|
+
|
|
532
623
|
def register_execute_plan
|
|
533
624
|
return unless @executor
|
|
534
625
|
|
data/lib/rails_console_ai.rb
CHANGED
|
@@ -55,6 +55,39 @@ module RailsConsoleAi
|
|
|
55
55
|
@current_user = name
|
|
56
56
|
end
|
|
57
57
|
|
|
58
|
+
# Enqueue an agent run. Returns the Integer session id immediately;
|
|
59
|
+
# the actual work is picked up by `rake rails_console_ai:agents`.
|
|
60
|
+
def run_agent(query, name: nil, user_name: nil)
|
|
61
|
+
require 'rails_console_ai/session_logger'
|
|
62
|
+
id = SessionLogger.log(
|
|
63
|
+
query: query,
|
|
64
|
+
conversation: [],
|
|
65
|
+
mode: 'agent_api',
|
|
66
|
+
name: name,
|
|
67
|
+
user_name: user_name,
|
|
68
|
+
status: 'queued',
|
|
69
|
+
executed: false
|
|
70
|
+
)
|
|
71
|
+
raise 'Failed to enqueue agent run (session logging disabled or table missing)' unless id
|
|
72
|
+
id
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
# Returns the current status string for an enqueued agent run, or nil
|
|
76
|
+
# if the session id is not found. Status is one of:
|
|
77
|
+
# 'queued' | 'running' | 'ready' | 'failed'.
|
|
78
|
+
def check_agent(session_id)
|
|
79
|
+
Session.where(id: session_id).pluck(:status).first
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
# Returns a hash describing an agent run:
|
|
83
|
+
# { status:, result:, error: }
|
|
84
|
+
# All three keys are nil when the session id is not found.
|
|
85
|
+
def get_agent_response(session_id)
|
|
86
|
+
row = Session.where(id: session_id).select(:status, :result, :error_message).first
|
|
87
|
+
return { status: nil, result: nil, error: nil } unless row
|
|
88
|
+
{ status: row.status, result: row.result, error: row.error_message }
|
|
89
|
+
end
|
|
90
|
+
|
|
58
91
|
def status
|
|
59
92
|
c = configuration
|
|
60
93
|
key = c.resolved_api_key
|
|
@@ -123,11 +156,183 @@ module RailsConsoleAi
|
|
|
123
156
|
$stdout.puts "\e[32mRailsConsoleAi: created #{table} table.\e[0m"
|
|
124
157
|
end
|
|
125
158
|
|
|
159
|
+
setup_skills_tables!(conn)
|
|
160
|
+
setup_memories_tables!(conn)
|
|
161
|
+
setup_agents_tables!(conn)
|
|
162
|
+
|
|
126
163
|
migrate!
|
|
127
164
|
rescue => e
|
|
128
165
|
$stderr.puts "\e[31mRailsConsoleAi setup failed: #{e.class}: #{e.message}\e[0m"
|
|
129
166
|
end
|
|
130
167
|
|
|
168
|
+
def setup_skills_tables!(conn)
|
|
169
|
+
skills_table = 'rails_console_ai_skills'
|
|
170
|
+
versions_table = 'rails_console_ai_skill_versions'
|
|
171
|
+
|
|
172
|
+
unless conn.table_exists?(skills_table)
|
|
173
|
+
conn.create_table(skills_table) do |t|
|
|
174
|
+
t.string :name, limit: 255, null: false
|
|
175
|
+
t.text :description
|
|
176
|
+
t.text :body
|
|
177
|
+
t.text :tags
|
|
178
|
+
t.text :bypass_guards_for_methods
|
|
179
|
+
t.string :status, limit: 20, default: 'proposed', null: false
|
|
180
|
+
t.string :approved_by, limit: 255
|
|
181
|
+
t.datetime :approved_at
|
|
182
|
+
t.integer :use_count, default: 0, null: false
|
|
183
|
+
t.datetime :last_used_at
|
|
184
|
+
t.datetime :created_at, null: false
|
|
185
|
+
t.datetime :updated_at, null: false
|
|
186
|
+
end
|
|
187
|
+
conn.add_index(skills_table, :name, unique: true)
|
|
188
|
+
conn.add_index(skills_table, :status)
|
|
189
|
+
$stdout.puts "\e[32mRailsConsoleAi: created #{skills_table} table.\e[0m"
|
|
190
|
+
else
|
|
191
|
+
# Idempotent column-add probes for existing installs.
|
|
192
|
+
unless conn.column_exists?(skills_table, :status)
|
|
193
|
+
conn.add_column(skills_table, :status, :string, limit: 20, default: 'proposed', null: false)
|
|
194
|
+
conn.add_index(skills_table, :status) unless conn.index_exists?(skills_table, :status)
|
|
195
|
+
end
|
|
196
|
+
unless conn.column_exists?(skills_table, :approved_by)
|
|
197
|
+
conn.add_column(skills_table, :approved_by, :string, limit: 255)
|
|
198
|
+
end
|
|
199
|
+
unless conn.column_exists?(skills_table, :approved_at)
|
|
200
|
+
conn.add_column(skills_table, :approved_at, :datetime)
|
|
201
|
+
end
|
|
202
|
+
unless conn.column_exists?(skills_table, :use_count)
|
|
203
|
+
conn.add_column(skills_table, :use_count, :integer, default: 0, null: false)
|
|
204
|
+
end
|
|
205
|
+
unless conn.column_exists?(skills_table, :last_used_at)
|
|
206
|
+
conn.add_column(skills_table, :last_used_at, :datetime)
|
|
207
|
+
end
|
|
208
|
+
end
|
|
209
|
+
|
|
210
|
+
unless conn.table_exists?(versions_table)
|
|
211
|
+
conn.create_table(versions_table) do |t|
|
|
212
|
+
t.integer :skill_id
|
|
213
|
+
t.string :name, limit: 255
|
|
214
|
+
t.text :description
|
|
215
|
+
t.text :body
|
|
216
|
+
t.text :tags
|
|
217
|
+
t.text :bypass_guards_for_methods
|
|
218
|
+
t.string :status, limit: 20
|
|
219
|
+
t.string :edited_by, limit: 255
|
|
220
|
+
t.text :change_note
|
|
221
|
+
t.datetime :created_at, null: false
|
|
222
|
+
end
|
|
223
|
+
conn.add_index(versions_table, :skill_id)
|
|
224
|
+
conn.add_index(versions_table, :created_at)
|
|
225
|
+
$stdout.puts "\e[32mRailsConsoleAi: created #{versions_table} table.\e[0m"
|
|
226
|
+
else
|
|
227
|
+
unless conn.column_exists?(versions_table, :status)
|
|
228
|
+
conn.add_column(versions_table, :status, :string, limit: 20)
|
|
229
|
+
end
|
|
230
|
+
end
|
|
231
|
+
end
|
|
232
|
+
|
|
233
|
+
def setup_memories_tables!(conn)
|
|
234
|
+
memories_table = 'rails_console_ai_memories'
|
|
235
|
+
versions_table = 'rails_console_ai_memory_versions'
|
|
236
|
+
|
|
237
|
+
unless conn.table_exists?(memories_table)
|
|
238
|
+
conn.create_table(memories_table) do |t|
|
|
239
|
+
t.string :name, limit: 255, null: false
|
|
240
|
+
t.text :description
|
|
241
|
+
t.text :tags
|
|
242
|
+
t.integer :use_count, default: 0, null: false
|
|
243
|
+
t.datetime :last_used_at
|
|
244
|
+
t.datetime :created_at, null: false
|
|
245
|
+
t.datetime :updated_at, null: false
|
|
246
|
+
end
|
|
247
|
+
conn.add_index(memories_table, :name, unique: true)
|
|
248
|
+
$stdout.puts "\e[32mRailsConsoleAi: created #{memories_table} table.\e[0m"
|
|
249
|
+
else
|
|
250
|
+
unless conn.column_exists?(memories_table, :use_count)
|
|
251
|
+
conn.add_column(memories_table, :use_count, :integer, default: 0, null: false)
|
|
252
|
+
end
|
|
253
|
+
unless conn.column_exists?(memories_table, :last_used_at)
|
|
254
|
+
conn.add_column(memories_table, :last_used_at, :datetime)
|
|
255
|
+
end
|
|
256
|
+
end
|
|
257
|
+
|
|
258
|
+
unless conn.table_exists?(versions_table)
|
|
259
|
+
conn.create_table(versions_table) do |t|
|
|
260
|
+
t.integer :memory_id
|
|
261
|
+
t.string :name, limit: 255
|
|
262
|
+
t.text :description
|
|
263
|
+
t.text :tags
|
|
264
|
+
t.string :edited_by, limit: 255
|
|
265
|
+
t.text :change_note
|
|
266
|
+
t.datetime :created_at, null: false
|
|
267
|
+
end
|
|
268
|
+
conn.add_index(versions_table, :memory_id)
|
|
269
|
+
conn.add_index(versions_table, :created_at)
|
|
270
|
+
$stdout.puts "\e[32mRailsConsoleAi: created #{versions_table} table.\e[0m"
|
|
271
|
+
end
|
|
272
|
+
end
|
|
273
|
+
|
|
274
|
+
def setup_agents_tables!(conn)
|
|
275
|
+
agents_table = 'rails_console_ai_agents'
|
|
276
|
+
versions_table = 'rails_console_ai_agent_versions'
|
|
277
|
+
|
|
278
|
+
unless conn.table_exists?(agents_table)
|
|
279
|
+
conn.create_table(agents_table) do |t|
|
|
280
|
+
t.string :name, limit: 255, null: false
|
|
281
|
+
t.text :description
|
|
282
|
+
t.text :body
|
|
283
|
+
t.integer :max_rounds
|
|
284
|
+
t.string :model, limit: 100
|
|
285
|
+
t.text :tools
|
|
286
|
+
t.string :status, limit: 20, default: 'proposed', null: false
|
|
287
|
+
t.string :approved_by, limit: 255
|
|
288
|
+
t.datetime :approved_at
|
|
289
|
+
t.integer :use_count, default: 0, null: false
|
|
290
|
+
t.datetime :last_used_at
|
|
291
|
+
t.datetime :created_at, null: false
|
|
292
|
+
t.datetime :updated_at, null: false
|
|
293
|
+
end
|
|
294
|
+
conn.add_index(agents_table, :name, unique: true)
|
|
295
|
+
conn.add_index(agents_table, :status)
|
|
296
|
+
$stdout.puts "\e[32mRailsConsoleAi: created #{agents_table} table.\e[0m"
|
|
297
|
+
else
|
|
298
|
+
unless conn.column_exists?(agents_table, :status)
|
|
299
|
+
conn.add_column(agents_table, :status, :string, limit: 20, default: 'proposed', null: false)
|
|
300
|
+
conn.add_index(agents_table, :status) unless conn.index_exists?(agents_table, :status)
|
|
301
|
+
end
|
|
302
|
+
unless conn.column_exists?(agents_table, :approved_by)
|
|
303
|
+
conn.add_column(agents_table, :approved_by, :string, limit: 255)
|
|
304
|
+
end
|
|
305
|
+
unless conn.column_exists?(agents_table, :approved_at)
|
|
306
|
+
conn.add_column(agents_table, :approved_at, :datetime)
|
|
307
|
+
end
|
|
308
|
+
unless conn.column_exists?(agents_table, :use_count)
|
|
309
|
+
conn.add_column(agents_table, :use_count, :integer, default: 0, null: false)
|
|
310
|
+
end
|
|
311
|
+
unless conn.column_exists?(agents_table, :last_used_at)
|
|
312
|
+
conn.add_column(agents_table, :last_used_at, :datetime)
|
|
313
|
+
end
|
|
314
|
+
end
|
|
315
|
+
|
|
316
|
+
unless conn.table_exists?(versions_table)
|
|
317
|
+
conn.create_table(versions_table) do |t|
|
|
318
|
+
t.integer :agent_id
|
|
319
|
+
t.string :name, limit: 255
|
|
320
|
+
t.text :description
|
|
321
|
+
t.text :body
|
|
322
|
+
t.integer :max_rounds
|
|
323
|
+
t.string :model, limit: 100
|
|
324
|
+
t.text :tools
|
|
325
|
+
t.string :status, limit: 20
|
|
326
|
+
t.string :edited_by, limit: 255
|
|
327
|
+
t.text :change_note
|
|
328
|
+
t.datetime :created_at, null: false
|
|
329
|
+
end
|
|
330
|
+
conn.add_index(versions_table, :agent_id)
|
|
331
|
+
conn.add_index(versions_table, :created_at)
|
|
332
|
+
$stdout.puts "\e[32mRailsConsoleAi: created #{versions_table} table.\e[0m"
|
|
333
|
+
end
|
|
334
|
+
end
|
|
335
|
+
|
|
131
336
|
def migrate!
|
|
132
337
|
conn = session_connection
|
|
133
338
|
table = 'rails_console_ai_sessions'
|
|
@@ -156,6 +361,50 @@ module RailsConsoleAi
|
|
|
156
361
|
migrations << 'slack_channel_name'
|
|
157
362
|
end
|
|
158
363
|
|
|
364
|
+
unless conn.column_exists?(table, :status)
|
|
365
|
+
conn.add_column(table, :status, :string, limit: 20)
|
|
366
|
+
migrations << 'status'
|
|
367
|
+
end
|
|
368
|
+
|
|
369
|
+
unless conn.column_exists?(table, :result)
|
|
370
|
+
conn.add_column(table, :result, :text)
|
|
371
|
+
migrations << 'result'
|
|
372
|
+
end
|
|
373
|
+
|
|
374
|
+
unless conn.column_exists?(table, :error_message)
|
|
375
|
+
conn.add_column(table, :error_message, :text)
|
|
376
|
+
migrations << 'error_message'
|
|
377
|
+
end
|
|
378
|
+
|
|
379
|
+
unless conn.index_exists?(table, [:mode, :status], name: 'idx_rca_sessions_mode_status')
|
|
380
|
+
conn.add_index(table, [:mode, :status], name: 'idx_rca_sessions_mode_status')
|
|
381
|
+
migrations << 'idx_rca_sessions_mode_status'
|
|
382
|
+
end
|
|
383
|
+
|
|
384
|
+
# Bring skills/memories/agents tables fully up to date. Each setup_* method is
|
|
385
|
+
# internally idempotent (guards both `create_table` and every `add_column` /
|
|
386
|
+
# `add_index`), so running it on an existing install adds any missing columns
|
|
387
|
+
# (e.g. `status`, `approved_by`, `approved_at`) and indexes without disturbing
|
|
388
|
+
# data. Note: we always call these — the previous version skipped them when
|
|
389
|
+
# the base table already existed, which meant column probes never ran on
|
|
390
|
+
# upgrade and methods like Skill#status hit NameError. See:
|
|
391
|
+
# https://github.com/cortfr/rails_console_ai/issues (whichever issue you file)
|
|
392
|
+
pre_columns = {
|
|
393
|
+
skills: table_columns(conn, 'rails_console_ai_skills'),
|
|
394
|
+
memories: table_columns(conn, 'rails_console_ai_memories'),
|
|
395
|
+
agents: table_columns(conn, 'rails_console_ai_agents')
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
setup_skills_tables!(conn)
|
|
399
|
+
setup_memories_tables!(conn)
|
|
400
|
+
setup_agents_tables!(conn)
|
|
401
|
+
|
|
402
|
+
[[:skills, 'rails_console_ai_skills'], [:memories, 'rails_console_ai_memories'], [:agents, 'rails_console_ai_agents']].each do |key, name|
|
|
403
|
+
post = table_columns(conn, name)
|
|
404
|
+
added = post - pre_columns[key]
|
|
405
|
+
migrations.concat(added.map { |c| "#{name}.#{c}" }) unless added.empty?
|
|
406
|
+
end
|
|
407
|
+
|
|
159
408
|
if migrations.empty?
|
|
160
409
|
$stdout.puts "\e[32mRailsConsoleAi: #{table} is up to date.\e[0m"
|
|
161
410
|
else
|
|
@@ -214,6 +463,13 @@ module RailsConsoleAi
|
|
|
214
463
|
ActiveRecord::Base.connection
|
|
215
464
|
end
|
|
216
465
|
end
|
|
466
|
+
|
|
467
|
+
def table_columns(conn, table_name)
|
|
468
|
+
return [] unless conn.table_exists?(table_name)
|
|
469
|
+
conn.columns(table_name).map { |c| c.name }
|
|
470
|
+
rescue
|
|
471
|
+
[]
|
|
472
|
+
end
|
|
217
473
|
end
|
|
218
474
|
end
|
|
219
475
|
|
|
@@ -4,4 +4,11 @@ namespace :rails_console_ai do
|
|
|
4
4
|
require 'rails_console_ai/slack_bot'
|
|
5
5
|
RailsConsoleAi::SlackBot.new.start
|
|
6
6
|
end
|
|
7
|
+
|
|
8
|
+
desc "Run the RailsConsoleAi agent runner (polls DB for queued agent runs)"
|
|
9
|
+
task agents: :environment do
|
|
10
|
+
require 'rails_console_ai/agent_runner'
|
|
11
|
+
concurrency = Integer(ENV['AGENT_CONCURRENCY'] || RailsConsoleAi::AgentRunner::DEFAULT_CONCURRENCY)
|
|
12
|
+
RailsConsoleAi::AgentRunner.new(concurrency: concurrency).start
|
|
13
|
+
end
|
|
7
14
|
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.
|
|
4
|
+
version: 0.30.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Cortfr
|
|
@@ -79,6 +79,20 @@ dependencies:
|
|
|
79
79
|
- - ">="
|
|
80
80
|
- !ruby/object:Gem::Version
|
|
81
81
|
version: '12.0'
|
|
82
|
+
- !ruby/object:Gem::Dependency
|
|
83
|
+
name: sqlite3
|
|
84
|
+
requirement: !ruby/object:Gem::Requirement
|
|
85
|
+
requirements:
|
|
86
|
+
- - ">="
|
|
87
|
+
- !ruby/object:Gem::Version
|
|
88
|
+
version: '1.4'
|
|
89
|
+
type: :development
|
|
90
|
+
prerelease: false
|
|
91
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
92
|
+
requirements:
|
|
93
|
+
- - ">="
|
|
94
|
+
- !ruby/object:Gem::Version
|
|
95
|
+
version: '1.4'
|
|
82
96
|
description: An LLM-powered agent for your Rails console. Ask questions in natural
|
|
83
97
|
language, get executable Ruby code.
|
|
84
98
|
email: cortfr@gmail.com
|
|
@@ -89,20 +103,59 @@ files:
|
|
|
89
103
|
- CHANGELOG.md
|
|
90
104
|
- LICENSE
|
|
91
105
|
- README.md
|
|
106
|
+
- app/controllers/rails_console_ai/agent_versions_controller.rb
|
|
107
|
+
- app/controllers/rails_console_ai/agents_controller.rb
|
|
92
108
|
- app/controllers/rails_console_ai/application_controller.rb
|
|
109
|
+
- app/controllers/rails_console_ai/memories_controller.rb
|
|
110
|
+
- app/controllers/rails_console_ai/memory_versions_controller.rb
|
|
93
111
|
- app/controllers/rails_console_ai/sessions_controller.rb
|
|
112
|
+
- app/controllers/rails_console_ai/skill_versions_controller.rb
|
|
113
|
+
- app/controllers/rails_console_ai/skills_controller.rb
|
|
114
|
+
- app/helpers/rails_console_ai/diff_helper.rb
|
|
94
115
|
- app/helpers/rails_console_ai/sessions_helper.rb
|
|
116
|
+
- app/models/rails_console_ai/agent.rb
|
|
117
|
+
- app/models/rails_console_ai/agent_version.rb
|
|
118
|
+
- app/models/rails_console_ai/memory.rb
|
|
119
|
+
- app/models/rails_console_ai/memory_version.rb
|
|
95
120
|
- app/models/rails_console_ai/session.rb
|
|
121
|
+
- app/models/rails_console_ai/skill.rb
|
|
122
|
+
- app/models/rails_console_ai/skill_version.rb
|
|
96
123
|
- app/views/layouts/rails_console_ai/application.html.erb
|
|
124
|
+
- app/views/rails_console_ai/agent_versions/index.html.erb
|
|
125
|
+
- app/views/rails_console_ai/agent_versions/show.html.erb
|
|
126
|
+
- app/views/rails_console_ai/agents/_form.html.erb
|
|
127
|
+
- app/views/rails_console_ai/agents/diff.html.erb
|
|
128
|
+
- app/views/rails_console_ai/agents/edit.html.erb
|
|
129
|
+
- app/views/rails_console_ai/agents/index.html.erb
|
|
130
|
+
- app/views/rails_console_ai/agents/new.html.erb
|
|
131
|
+
- app/views/rails_console_ai/agents/show.html.erb
|
|
132
|
+
- app/views/rails_console_ai/memories/_form.html.erb
|
|
133
|
+
- app/views/rails_console_ai/memories/diff.html.erb
|
|
134
|
+
- app/views/rails_console_ai/memories/edit.html.erb
|
|
135
|
+
- app/views/rails_console_ai/memories/index.html.erb
|
|
136
|
+
- app/views/rails_console_ai/memories/new.html.erb
|
|
137
|
+
- app/views/rails_console_ai/memories/show.html.erb
|
|
138
|
+
- app/views/rails_console_ai/memory_versions/index.html.erb
|
|
139
|
+
- app/views/rails_console_ai/memory_versions/show.html.erb
|
|
97
140
|
- app/views/rails_console_ai/sessions/index.html.erb
|
|
98
141
|
- app/views/rails_console_ai/sessions/show.html.erb
|
|
142
|
+
- app/views/rails_console_ai/skill_versions/index.html.erb
|
|
143
|
+
- app/views/rails_console_ai/skill_versions/show.html.erb
|
|
144
|
+
- app/views/rails_console_ai/skills/_form.html.erb
|
|
145
|
+
- app/views/rails_console_ai/skills/diff.html.erb
|
|
146
|
+
- app/views/rails_console_ai/skills/edit.html.erb
|
|
147
|
+
- app/views/rails_console_ai/skills/index.html.erb
|
|
148
|
+
- app/views/rails_console_ai/skills/new.html.erb
|
|
149
|
+
- app/views/rails_console_ai/skills/show.html.erb
|
|
99
150
|
- config/routes.rb
|
|
100
151
|
- lib/generators/rails_console_ai/install_generator.rb
|
|
101
152
|
- lib/generators/rails_console_ai/templates/initializer.rb
|
|
102
153
|
- lib/rails_console_ai.rb
|
|
103
154
|
- lib/rails_console_ai/agent_loader.rb
|
|
155
|
+
- lib/rails_console_ai/agent_runner.rb
|
|
104
156
|
- lib/rails_console_ai/agents/explore-data.md
|
|
105
157
|
- lib/rails_console_ai/agents/investigate-code.md
|
|
158
|
+
- lib/rails_console_ai/channel/api.rb
|
|
106
159
|
- lib/rails_console_ai/channel/base.rb
|
|
107
160
|
- lib/rails_console_ai/channel/console.rb
|
|
108
161
|
- lib/rails_console_ai/channel/slack.rb
|
|
@@ -126,6 +179,7 @@ files:
|
|
|
126
179
|
- lib/rails_console_ai/skill_loader.rb
|
|
127
180
|
- lib/rails_console_ai/slack_bot.rb
|
|
128
181
|
- lib/rails_console_ai/storage/base.rb
|
|
182
|
+
- lib/rails_console_ai/storage/database_storage.rb
|
|
129
183
|
- lib/rails_console_ai/storage/file_storage.rb
|
|
130
184
|
- lib/rails_console_ai/sub_agent.rb
|
|
131
185
|
- lib/rails_console_ai/tools/code_tools.rb
|