Class: RailsConsoleAi::AgentLoader
- Inherits:
-
Object
- Object
- RailsConsoleAi::AgentLoader
- Defined in:
- lib/rails_console_ai/agent_loader.rb
Constant Summary collapse
- AGENTS_DIR =
'agents'- BUILTIN_DIR =
File.('../agents', __FILE__)
Class Method Summary collapse
-
.parse(content) ⇒ Object
Public: parse a raw .md (YAML frontmatter + body) string into a hash.
Instance Method Summary collapse
- #agent_summaries ⇒ Object
-
#delete_agent(name:) ⇒ Object
Tries DB first, then file.
-
#find_agent(name) ⇒ Object
AI-facing — returns nil for proposed DB agents so delegate_task can’t reach them.
-
#find_any_agent(name) ⇒ Object
UI-facing — includes proposed DB agents.
-
#initialize(storage = nil) ⇒ AgentLoader
constructor
A new instance of AgentLoader.
-
#load_activatable_agents ⇒ Object
AI-facing: hides proposed DB agents.
-
#load_all_agents ⇒ Object
Three-source union: DB > file > built-in.
-
#save_agent(name:, description:, body:, max_rounds: nil, model: nil, tools: nil, target: :db, edited_by: nil, change_note: nil) ⇒ Object
target: :db (default) | :file Falls back to :file (with a notice in the return string) if DB tables aren’t set up.
Constructor Details
#initialize(storage = nil) ⇒ AgentLoader
Returns a new instance of AgentLoader.
9 10 11 |
# File 'lib/rails_console_ai/agent_loader.rb', line 9 def initialize(storage = nil) @storage = storage || RailsConsoleAi.storage end |
Class Method Details
.parse(content) ⇒ Object
Public: parse a raw .md (YAML frontmatter + body) string into a hash.
205 206 207 208 209 210 211 212 |
# File 'lib/rails_console_ai/agent_loader.rb', line 205 def self.parse(content) return nil unless content =~ /\A---\s*\n(.*?\n)---\s*\n(.*)/m frontmatter = YAML.safe_load($1, permitted_classes: [Time, Date]) || {} body = $2.strip frontmatter.merge('body' => body) rescue Psych::SyntaxError nil end |
Instance Method Details
#agent_summaries ⇒ Object
37 38 39 40 41 42 43 44 |
# File 'lib/rails_console_ai/agent_loader.rb', line 37 def agent_summaries agents = load_activatable_agents return nil if agents.empty? agents.map { |a| "- **#{a['name']}**: #{a['description']}" } end |
#delete_agent(name:) ⇒ Object
Tries DB first, then file. Built-in agents can’t be deleted.
99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 |
# File 'lib/rails_console_ai/agent_loader.rb', line 99 def delete_agent(name:) if Storage::DatabaseStorage.delete_agent_by_name(name) return "Agent deleted (db): \"#{name}\"" end # Built-in agents are gem-shipped and not deletable. builtin = safe_load_builtin_agents.find { |a| a['name'].to_s.downcase == name.to_s.downcase } if builtin return "Cannot delete built-in agent \"#{builtin['name']}\". Built-in agents ship with the gem. " \ "Create a same-named DB agent to override it instead." end key = agent_key(name) unless @storage.exists?(key) found = safe_load_file_agents.find { |a| a['name'].to_s.downcase == name.to_s.downcase } return "No agent found: \"#{name}\"" unless found key = agent_key(found['name']) end agent = load_agent_file(key) @storage.delete(key) "Agent deleted: \"#{agent ? agent['name'] : name}\"" rescue Storage::StorageError => e "FAILED to delete agent (#{e.})." end |
#find_agent(name) ⇒ Object
AI-facing — returns nil for proposed DB agents so delegate_task can’t reach them.
47 48 49 |
# File 'lib/rails_console_ai/agent_loader.rb', line 47 def find_agent(name) load_activatable_agents.find { |a| a['name'].to_s.downcase == name.to_s.downcase } end |
#find_any_agent(name) ⇒ Object
UI-facing — includes proposed DB agents.
52 53 54 |
# File 'lib/rails_console_ai/agent_loader.rb', line 52 def find_any_agent(name) load_all_agents.find { |a| a['name'].to_s.downcase == name.to_s.downcase } end |
#load_activatable_agents ⇒ Object
AI-facing: hides proposed DB agents. File + built-in are pre-approved.
33 34 35 |
# File 'lib/rails_console_ai/agent_loader.rb', line 33 def load_activatable_agents load_all_agents.reject { |a| a['source'] == :db && a['status'] != 'approved' } end |
#load_all_agents ⇒ Object
Three-source union: DB > file > built-in. Each record is tagged with ‘source: :db | :file | :builtin`. DB records also carry `status`, `approved_by`, `approved_at`. Proposed (unapproved) DB agents are surfaced here so the admin UI can render them — use #load_activatable_agents on AI-facing paths to filter them out.
18 19 20 21 22 23 24 25 26 27 28 29 30 |
# File 'lib/rails_console_ai/agent_loader.rb', line 18 def load_all_agents db = safe_load_db_agents file = safe_load_file_agents builtin = safe_load_builtin_agents db_names = db.map { |a| a['name'].to_s.downcase } file.reject! { |a| db_names.include?(a['name'].to_s.downcase) } file_names = file.map { |a| a['name'].to_s.downcase } builtin.reject! { |a| db_names.include?(a['name'].to_s.downcase) || file_names.include?(a['name'].to_s.downcase) } (db + file + builtin).sort_by { |a| a['name'].to_s.downcase } end |
#save_agent(name:, description:, body:, max_rounds: nil, model: nil, tools: nil, target: :db, edited_by: nil, change_note: nil) ⇒ Object
target: :db (default) | :file Falls back to :file (with a notice in the return string) if DB tables aren’t set up.
58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 |
# File 'lib/rails_console_ai/agent_loader.rb', line 58 def save_agent(name:, description:, body:, max_rounds: nil, model: nil, tools: nil, target: :db, edited_by: nil, change_note: nil) target = (target || :db).to_sym db_fell_back = false if target == :db && !Storage::DatabaseStorage.agents_available? target = :file db_fell_back = true end if target == :file result = save_agent_to_file( name: name, description: description, body: body, max_rounds: max_rounds, model: model, tools: tools ) if db_fell_back result += "\nNOTE: DB storage was requested but the rails_console_ai_agents table does not exist. " \ "Run `ai_db_setup` in your Rails console to enable the versioned DB store. " \ "Saved to a file instead." end result else record, was_new = Storage::DatabaseStorage.save_agent( name: name, description: description, body: body, max_rounds: max_rounds, model: model, tools: Array(tools), edited_by: edited_by || 'ai', change_note: change_note ) status_note = if record.respond_to?(:proposed?) && record.proposed? ' — status: PROPOSED. A human must approve it at /rails_console_ai/agents before delegate_task can invoke it.' else '' end if was_new "Agent created (db): \"#{record.name}\" (id=#{record.id})#{status_note}" else "Agent updated (db): \"#{record.name}\" (id=#{record.id})#{status_note}" end end rescue Storage::StorageError, ::ActiveRecord::RecordInvalid => e "FAILED to save agent (#{e.})." end |