aia 1.1.1 → 2.0.0.0.pre.beta2

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.
Files changed (175) hide show
  1. checksums.yaml +4 -4
  2. data/.envrc +9 -1
  3. data/.loki +11 -0
  4. data/.reek.yml +160 -0
  5. data/.rubocop.yml +116 -0
  6. data/.rubocop_strict.yml +15 -0
  7. data/.version +1 -1
  8. data/CHANGELOG.md +269 -55
  9. data/IMPLEMENTATION_PLAN.md +506 -0
  10. data/README.md +267 -239
  11. data/Rakefile +5 -5
  12. data/_typos.toml +10 -0
  13. data/aia.gemspec +92 -0
  14. data/architecture_review.md +314 -0
  15. data/bin/aia +16 -0
  16. data/config/aia.yml +13 -0
  17. data/docs/AGENTS.md +40 -0
  18. data/docs/advanced-prompting.md +67 -3
  19. data/docs/cli-reference.md +312 -56
  20. data/docs/configuration.md +130 -19
  21. data/docs/contributing.md +56 -2
  22. data/docs/directives-reference.md +593 -78
  23. data/docs/faq.md +85 -3
  24. data/docs/guides/available-models.md +1 -1
  25. data/docs/guides/basic-usage.md +6 -6
  26. data/docs/guides/chat.md +40 -16
  27. data/docs/guides/crew.md +239 -0
  28. data/docs/guides/executable-prompts.md +1 -1
  29. data/docs/guides/index.md +1 -0
  30. data/docs/guides/models.md +15 -0
  31. data/docs/index.md +29 -2
  32. data/docs/installation.md +44 -17
  33. data/docs/mcp-integration.md +40 -0
  34. data/docs/prompt_management.md +85 -86
  35. data/docs/security.md +47 -0
  36. data/docs/special_projects_guide.md +386 -0
  37. data/docs/tools-and-mcp-examples.md +23 -0
  38. data/docs/workflows-and-pipelines.md +84 -7
  39. data/examples/.gitignore +1 -0
  40. data/examples/00_setup_aia.sh +27 -44
  41. data/examples/11_multi_model.sh +4 -14
  42. data/examples/12_token_usage.sh +3 -12
  43. data/examples/18_tools.sh +10 -2
  44. data/examples/22_chat_mode.sh +0 -10
  45. data/examples/23_verify.sh +139 -0
  46. data/examples/24_decompose.sh +139 -0
  47. data/examples/25_spawn.sh +139 -0
  48. data/examples/26_debate.sh +97 -0
  49. data/examples/27_mention_routing.sh +157 -0
  50. data/examples/28_model_switching.sh +106 -0
  51. data/examples/29_agent_harness.sh +177 -0
  52. data/examples/README.md +65 -0
  53. data/examples/advanced_multi_robot_capabilities_without_examples.md +106 -0
  54. data/examples/aia_config.yml +1 -1
  55. data/examples/aia_config_orchestrator.yml +45 -0
  56. data/examples/common.sh +18 -6
  57. data/examples/context/tech_stack.md +2 -2
  58. data/examples/prompts_dir/roles/orchestrator.md +21 -0
  59. data/examples/requirements/sinatra_taskflow_app.md +139 -0
  60. data/examples/rules/01_classify_ruby.rb +16 -0
  61. data/examples/rules/02_prefer_claude_for_code.rb +19 -0
  62. data/examples/rules/03_gate_prompt_length.rb +19 -0
  63. data/examples/rules/04_tool_selection.rb +41 -0
  64. data/examples/rules/README.md +30 -0
  65. data/examples/run_all.sh +48 -15
  66. data/examples/tools/word_count_tool.rb +1 -1
  67. data/lib/AGENTS.md +57 -0
  68. data/lib/aia/chat_loop.rb +263 -167
  69. data/lib/aia/config/cli_parser.rb +217 -145
  70. data/lib/aia/config/defaults.yml +62 -33
  71. data/lib/aia/config/mcp_parser.rb +52 -51
  72. data/lib/aia/config/model_spec.rb +34 -2
  73. data/lib/aia/config/validator.rb +171 -216
  74. data/lib/aia/config.rb +111 -145
  75. data/lib/aia/content_extractor.rb +155 -0
  76. data/lib/aia/cost_calculator.rb +39 -0
  77. data/lib/aia/crew.rb +164 -0
  78. data/lib/aia/debate_handler.rb +174 -0
  79. data/lib/aia/delegate_handler.rb +116 -0
  80. data/lib/aia/directive.rb +43 -26
  81. data/lib/aia/directive_processor.rb +16 -7
  82. data/lib/aia/directives/configuration_directives.rb +214 -60
  83. data/lib/aia/directives/context_directives.rb +67 -52
  84. data/lib/aia/directives/execution_directives.rb +141 -4
  85. data/lib/aia/directives/model_directives.rb +163 -141
  86. data/lib/aia/directives/trakflow_directives.rb +62 -0
  87. data/lib/aia/directives/utility_directives.rb +227 -30
  88. data/lib/aia/directives/web_and_file_directives.rb +126 -77
  89. data/lib/aia/errors.rb +15 -0
  90. data/lib/aia/fact_asserter.rb +27 -0
  91. data/lib/aia/fzf.rb +9 -31
  92. data/lib/aia/handler_context.rb +17 -0
  93. data/lib/aia/handler_protocol.rb +19 -0
  94. data/lib/aia/history_transfer.rb +55 -0
  95. data/lib/aia/input_collector.rb +3 -3
  96. data/lib/aia/layered_orchestrator.rb +471 -0
  97. data/lib/aia/logger.rb +45 -25
  98. data/lib/aia/mcp_config_normalizer.rb +35 -0
  99. data/lib/aia/mcp_connection_manager.rb +315 -0
  100. data/lib/aia/mcp_discovery.rb +44 -0
  101. data/lib/aia/mcp_grouper.rb +33 -0
  102. data/lib/aia/mcp_server_config.rb +30 -0
  103. data/lib/aia/mcp_utility.rb +60 -0
  104. data/lib/aia/mention_router.rb +217 -0
  105. data/lib/aia/model_alias_registry.rb +97 -0
  106. data/lib/aia/model_switch_handler.rb +100 -0
  107. data/lib/aia/network_builder.rb +160 -0
  108. data/lib/aia/network_memory_manager.rb +55 -0
  109. data/lib/aia/patches/ruby_llm_streaming_error.rb +43 -0
  110. data/lib/aia/patches/ruby_llm_tool_error.rb +96 -0
  111. data/lib/aia/pipeline_orchestrator.rb +272 -0
  112. data/lib/aia/plugin_loader.rb +170 -0
  113. data/lib/aia/plugin_monitor.rb +211 -0
  114. data/lib/aia/prompt_decomposer.rb +159 -0
  115. data/lib/aia/prompt_handler.rb +56 -82
  116. data/lib/aia/robot_builder.rb +51 -0
  117. data/lib/aia/robot_factory.rb +338 -0
  118. data/lib/aia/robot_namer.rb +110 -0
  119. data/lib/aia/session.rb +87 -17
  120. data/lib/aia/session_tracker.rb +207 -0
  121. data/lib/aia/similarity_scorer.rb +41 -0
  122. data/lib/aia/skill_utils.rb +105 -1
  123. data/lib/aia/spawn_handler.rb +129 -0
  124. data/lib/aia/spawn_spec_parser.rb +65 -0
  125. data/lib/aia/special_mode_handler.rb +322 -0
  126. data/lib/aia/speech.rb +67 -0
  127. data/lib/aia/startup_coordinator.rb +151 -0
  128. data/lib/aia/streaming_runner.rb +172 -0
  129. data/lib/aia/system_prompt_assembler.rb +92 -0
  130. data/lib/aia/task_coordinator.rb +207 -0
  131. data/lib/aia/task_decomposer.rb +57 -0
  132. data/lib/aia/task_executor.rb +51 -0
  133. data/lib/aia/tfidf_math.rb +27 -0
  134. data/lib/aia/timing.rb +15 -0
  135. data/lib/aia/tool_filter/tfidf.rb +116 -0
  136. data/lib/aia/tool_filter/wordnet_expander.rb +127 -0
  137. data/lib/aia/tool_filter.rb +83 -0
  138. data/lib/aia/tool_filter_registry.rb +30 -0
  139. data/lib/aia/tool_filter_strategy.rb +146 -0
  140. data/lib/aia/tool_introspection.rb +17 -0
  141. data/lib/aia/tool_loader.rb +216 -0
  142. data/lib/aia/tool_utility.rb +30 -0
  143. data/lib/aia/tools/delegate_to_foreman_tool.rb +70 -0
  144. data/lib/aia/tools/recruit_robot_tool.rb +60 -0
  145. data/lib/aia/tools/reskill_robot_tool.rb +44 -0
  146. data/lib/aia/tools/task_board_tool.rb +115 -0
  147. data/lib/aia/trakflow_bridge.rb +175 -0
  148. data/lib/aia/turn_state.rb +95 -0
  149. data/lib/aia/ui_presenter.rb +182 -206
  150. data/lib/aia/utility.rb +136 -87
  151. data/lib/aia/{history_manager.rb → variable_input_collector.rb} +9 -9
  152. data/lib/aia/verification_network.rb +57 -0
  153. data/lib/aia.rb +124 -63
  154. data/mkdocs.yml +1 -0
  155. metadata +187 -58
  156. data/justfile +0 -215
  157. data/lib/aia/adapter/chat_execution.rb +0 -242
  158. data/lib/aia/adapter/error_handler.rb +0 -68
  159. data/lib/aia/adapter/gem_activator.rb +0 -57
  160. data/lib/aia/adapter/mcp_connector.rb +0 -274
  161. data/lib/aia/adapter/modality_handlers.rb +0 -167
  162. data/lib/aia/adapter/model_registry.rb +0 -81
  163. data/lib/aia/adapter/multi_model_chat.rb +0 -218
  164. data/lib/aia/adapter/provider_configurator.rb +0 -59
  165. data/lib/aia/adapter/tool_filter.rb +0 -85
  166. data/lib/aia/adapter/tool_loader.rb +0 -90
  167. data/lib/aia/chat_processor_service.rb +0 -178
  168. data/lib/aia/prompt_pipeline.rb +0 -183
  169. data/lib/aia/ruby_llm_adapter.rb +0 -95
  170. data/lib/extensions/openstruct_merge.rb +0 -48
  171. data/lib/extensions/ruby_llm/.irbrc +0 -56
  172. data/lib/extensions/ruby_llm/modalities.rb +0 -36
  173. data/lib/extensions/ruby_llm/provider_fix.rb +0 -79
  174. data/lib/refinements/string.rb +0 -16
  175. data/main.just +0 -76
data/lib/aia/utility.rb CHANGED
@@ -1,118 +1,167 @@
1
1
  # lib/aia/utility.rb
2
2
 
3
- require 'word_wrapper' # Pure ruby word wrapping
3
+ require 'word_wrapper'
4
+ require 'simple_flow'
5
+ require 'trak_flow'
6
+ require 'tty-table'
7
+ begin
8
+ require 'ruby_llm/mcp'
9
+ rescue LoadError, StandardError
10
+ # ruby_llm-mcp may not be installed
11
+ end
12
+
13
+ require_relative 'mcp_utility'
14
+ require_relative 'tool_utility'
4
15
 
5
16
  module AIA
6
- class Utility
17
+ module Utility
7
18
  class << self
8
- def tools?
9
- AIA.config&.tool_names && !AIA.config.tool_names.empty?
19
+ include MCPUtility
20
+ include ToolUtility
21
+
22
+ def models_last_refresh
23
+ aia_dir = AIA.config&.paths&.aia_dir
24
+ return nil if aia_dir.nil?
25
+ models_file = File.join(File.expand_path(aia_dir), 'models.json')
26
+ File.mtime(models_file).strftime('%Y-%m-%d %H:%M')
27
+ rescue Errno::ENOENT
28
+ nil
10
29
  end
11
30
 
12
- def user_tools?
13
- AIA.config&.tools&.paths && !AIA.config.tools.paths.empty?
31
+ def build_crew_line
32
+ names = robot_names
33
+ return '' if names.empty?
34
+ "Today's crew: #{format_crew_mentions(names)}"
14
35
  end
15
36
 
16
- def mcp_servers?
17
- AIA.config&.mcp_servers && !AIA.config.mcp_servers.empty?
37
+ def robot
38
+ art = [
39
+ ' , ,',
40
+ ' (\____/)',
41
+ ' (_oo_)',
42
+ ' (O)',
43
+ ' __|||__ \)',
44
+ ' [/ Tobor \] /',
45
+ ' / \_______/ \/',
46
+ ' / /___\\',
47
+ ' (\ /_____\\',
48
+ ' ::: :::',
49
+ ' ::: :::'
50
+ ].join("\n")
51
+
52
+ art_width = art.lines.map(&:length).max
53
+ info_width = [TTY::Screen.width - art_width - 4, 30].max
54
+ label_width = 7 # "Models:" is the widest label
55
+ value_width = [info_width - label_width - 1, 20].max
56
+
57
+ header = "AIA v#{AIA::VERSION} is Online"
58
+
59
+ inner = TTY::Table.new(banner_detail_rows)
60
+ details = inner.render(:basic, multiline: true,
61
+ column_widths: [label_width, value_width],
62
+ alignments: %i[right left])
63
+
64
+ outer = TTY::Table.new([[art, "#{header}\n\n#{details}"]])
65
+ puts "\n#{outer.render(:basic, multiline: true, column_widths: [art_width, info_width], padding: [0, 1, 0, 0])}"
18
66
  end
19
67
 
20
- # Returns only successfully connected MCP server names
21
- def mcp_server_names
22
- # Use connected_mcp_servers if available (populated during MCP setup)
23
- connected = AIA.config&.connected_mcp_servers
24
- return connected if connected && !connected.empty?
68
+ private
25
69
 
26
- # Fallback to configured servers if connection status not yet known
27
- return [] unless mcp_servers?
28
- AIA.config.mcp_servers.map { |s| s[:name] || s["name"] }.compact
70
+ def robot_names
71
+ client = AIA.client
72
+ return [] unless client
73
+ client.crew.map(&:name)
29
74
  end
30
75
 
31
- # Returns true if there are any connected MCP servers
32
- def connected_mcp_servers?
33
- connected = AIA.config&.connected_mcp_servers
34
- connected && !connected.empty?
76
+ def format_crew_mentions(names)
77
+ names.map { |n| "@#{n.downcase}" }.join(', ')
35
78
  end
36
79
 
37
- # Returns list of failed MCP servers with their errors
38
- def failed_mcp_servers
39
- AIA.config&.failed_mcp_servers || []
80
+ def banner_detail_rows
81
+ [
82
+ ['Models:', banner_models],
83
+ ['DB:', banner_db],
84
+ ['Libs:', banner_libs],
85
+ ['Tools:', banner_tools],
86
+ ['MCP:', banner_mcp],
87
+ ['Crew:', banner_crew]
88
+ ]
40
89
  end
41
90
 
42
- def supports_tools?
43
- AIA.client&.model&.supports_functions? || false
91
+ def banner_models
92
+ models = AIA.config&.models
93
+ return 'unknown-model' if models.nil? || models.empty?
94
+ models.map do |spec|
95
+ case spec
96
+ when AIA::ModelSpec then spec.name
97
+ when Hash then spec[:name] || spec['name'] || spec.to_s
98
+ else spec.to_s
99
+ end
100
+ end.join(', ')
44
101
  end
45
102
 
46
- # Returns the last refresh date from models.json modification time
47
- def models_last_refresh
48
- aia_dir = AIA.config&.paths&.aia_dir
49
- return nil if aia_dir.nil?
50
-
51
- models_file = File.join(File.expand_path(aia_dir), 'models.json')
52
- return nil unless File.exist?(models_file)
53
-
54
- File.mtime(models_file).strftime('%Y-%m-%d %H:%M')
103
+ def banner_libs
104
+ parts = ["ruby_llm v#{RubyLLM::VERSION}"]
105
+ parts << "ruby_llm-mcp v#{RubyLLM::MCP::VERSION}" if defined?(RubyLLM::MCP::VERSION)
106
+ parts << "robot_lab v#{RobotLab::VERSION}" if defined?(RobotLab::VERSION)
107
+ parts << "simple_flow v#{SimpleFlow::VERSION}" if defined?(SimpleFlow::VERSION)
108
+ parts << "trak_flow v#{TrakFlow::VERSION}" if defined?(TrakFlow::VERSION)
109
+ parts << "typed_bus v#{TypedBus::VERSION}" if defined?(TypedBus::VERSION)
110
+ parts.join(', ')
55
111
  end
56
112
 
57
- # Displays the AIA robot ASCII art
58
- # Yes, its slightly frivolous but it does contain some
59
- # useful configuration information.
60
- def robot
61
- indent = 18
62
- spaces = " "*indent
63
- width = TTY::Screen.width - indent - 2
64
-
65
- mcp_version = defined?(RubyLLM::MCP::VERSION) ? " MCP v" + RubyLLM::MCP::VERSION : ''
66
-
67
- # Extract model names from config (handles ModelSpec objects or Hashes)
68
- model_display = if AIA.config&.models && !AIA.config.models.empty?
69
- models = AIA.config.models
70
- models.map do |spec|
71
- if spec.is_a?(AIA::ModelSpec)
72
- spec.name
73
- elsif spec.is_a?(Hash)
74
- spec[:name] || spec['name'] || spec.to_s
75
- else
76
- spec.to_s
77
- end
78
- end.join(', ')
113
+ def banner_tools
114
+ count = total_tool_count
115
+ if count.positive?
116
+ "#{count} #{count == 1 ? 'tool' : 'tools'} loaded"
79
117
  else
80
- 'unknown-model'
118
+ 'none loaded'
81
119
  end
120
+ end
82
121
 
83
- # Build MCP line based on connection status
84
- mcp_line = if !mcp_servers?
85
- '' # No MCP servers configured
86
- elsif connected_mcp_servers?
87
- "MCP: #{mcp_server_names.join(', ')}"
88
- else
89
- "MCP: (none connected)"
90
- end
122
+ def banner_mcp
123
+ connected = mcp_client_labels
124
+ failed = failed_mcp_servers.map { |f| server_name(f) }
125
+ return '(none configured)' if connected.empty? && failed.empty?
126
+ parts = []
127
+ parts << connected.join(', ') unless connected.empty?
128
+ parts << "FAILED: #{failed.join(', ')}" unless failed.empty?
129
+ parts.join(' | ')
130
+ end
91
131
 
92
- puts <<-ROBOT
93
-
94
- , ,
95
- (\\____/) AI Assistant (v#{AIA::VERSION}) is Online
96
- (_oo_) #{model_display}#{supports_tools? ? ' (supports tools)' : ''}
97
- (O) using ruby_llm (v#{RubyLLM::VERSION}#{mcp_version})
98
- __||__ \\) model db was last refreshed on
99
- [/______\\] / #{models_last_refresh || 'unknown'}
100
- / \\__AI__/ \\/ #{user_tools? ? 'I will also use your tools' : (tools? ? 'You can share my tools' : 'I did not bring any tools')}
101
- / /__\\ #{mcp_line}
102
- (\\ /____\\ #{user_tools? && tools? ? 'My Toolbox contains:' : ''}
103
- ROBOT
104
- if user_tools? && tools?
105
- tool_names = AIA.config.tool_names
106
- if tool_names && !tool_names.to_s.empty?
107
- puts WordWrapper::MinimumRaggedness.new(
108
- width,
109
- tool_names.to_s # String of tool names, comma separated
110
- ).wrap
111
- .split("\n")
112
- .map{|s| spaces+s+"\n"}
113
- .join
132
+ # Post-connection: connected_mcp_servers is the authoritative name list;
133
+ # mcp_server_tool_counts provides per-server tool counts.
134
+ # Pre-connection fallback: read live from RubyLLM::MCP.clients (--require clients).
135
+ # :reek:TooManyStatements -- authoritative post-connection labels with a live RubyLLM::MCP fallback before connection
136
+ def mcp_client_labels
137
+ cfg = AIA.config
138
+ return [] if cfg&.flags&.no_mcp
139
+
140
+ connected = cfg&.connected_mcp_servers
141
+ unless connected.nil?
142
+ counts = cfg&.mcp_server_tool_counts || {}
143
+ return connected.map do |name|
144
+ count = counts[name]
145
+ count ? "#{name}(#{count})" : name
114
146
  end
115
147
  end
148
+
149
+ return [] unless defined?(RubyLLM::MCP)
150
+ RubyLLM::MCP.clients.filter_map do |name, client|
151
+ count = client.tools.count rescue nil
152
+ "#{name}(#{count})" if count
153
+ end
154
+ end
155
+
156
+ def banner_db
157
+ refresh = models_last_refresh
158
+ refresh ? "refreshed #{refresh.gsub(' ', ' at ')}" : 'not yet refreshed'
159
+ end
160
+
161
+ def banner_crew
162
+ names = robot_names
163
+ return '' if names.empty?
164
+ format_crew_mentions(names)
116
165
  end
117
166
  end
118
167
  end
@@ -1,15 +1,15 @@
1
- # lib/aia/history_manager.rb
1
+ # lib/aia/variable_input_collector.rb
2
2
 
3
3
  require 'json'
4
4
  require 'fileutils'
5
5
 
6
6
  module AIA
7
- class HistoryManager
7
+ class VariableInputCollector
8
8
  def initialize
9
9
  # No prompt dependency — just handles user input for parameter collection
10
10
  end
11
11
 
12
-
12
+ # :reek:TooManyStatements -- one Reline round-trip: question build, readline, Ctrl+D/empty fallbacks, prompt_proc restore
13
13
  def request_variable_value(variable_name:, default_value: nil)
14
14
  Reline::HISTORY.clear
15
15
 
@@ -28,24 +28,24 @@ module AIA
28
28
 
29
29
  if input.nil? # Ctrl+D
30
30
  return default_value if default_value
31
- puts "\nParameter '#{variable_name}' is required."
32
- exit(1)
31
+ raise AIA::Error, "Parameter '#{variable_name}' is required."
33
32
  end
34
33
 
35
34
  value = input.strip
36
35
  if value.empty?
37
36
  return default_value if default_value
38
- puts "Parameter '#{variable_name}' is required."
39
- exit(1)
37
+ raise AIA::Error, "Parameter '#{variable_name}' is required."
40
38
  end
41
39
 
42
40
  value
43
41
  rescue Interrupt
44
- puts "\nVariable input interrupted."
45
- exit(1)
42
+ raise AIA::Error, "Variable input interrupted."
46
43
  ensure
47
44
  Reline.line_editor.prompt_proc = original_prompt_proc
48
45
  end
49
46
  end
50
47
  end
48
+
49
+ # Backward-compatibility alias — prefer VariableInputCollector in new code.
50
+ HistoryManager = VariableInputCollector
51
51
  end
@@ -0,0 +1,57 @@
1
+ # frozen_string_literal: true
2
+
3
+ # lib/aia/verification_network.rb
4
+ #
5
+ # Self-review via consensus: two robots independently answer,
6
+ # then a reconciler compares and produces a verified answer.
7
+
8
+ module AIA
9
+ class VerificationNetwork
10
+ # Build a verification network for the given config.
11
+ #
12
+ # @param config the AIA configuration
13
+ # @return [RobotLab::Network] a consensus network with verifiers + reconciler
14
+ def self.build(config)
15
+ model = config.models.first
16
+ run_config = RobotFactory.build_run_config(config)
17
+ tools = ToolLoader.filtered_tools(config)
18
+ mcp = RobotFactory.mcp_server_configs(config)
19
+ model_id = model.internal_id
20
+
21
+ # rubocop:disable-next Metrics/BlockLength
22
+ RobotLab.create_network(name: "aia-verification") do
23
+ worker_a = RobotLab.build(
24
+ name: "verifier-a",
25
+ model: model_id,
26
+ system_prompt: "Answer the following question. Be thorough and precise.",
27
+ local_tools: tools,
28
+ mcp_servers: mcp,
29
+ config: run_config
30
+ )
31
+ task :verify_a, worker_a, depends_on: :none
32
+
33
+ worker_b = RobotLab.build(
34
+ name: "verifier-b",
35
+ model: model_id,
36
+ system_prompt: "Answer the following question independently. " \
37
+ "Focus on correctness and completeness.",
38
+ local_tools: tools,
39
+ mcp_servers: mcp,
40
+ config: run_config
41
+ )
42
+ task :verify_b, worker_b, depends_on: :none
43
+
44
+ reconciler = RobotLab.build(
45
+ name: "reconciler",
46
+ model: model_id,
47
+ system_prompt: "Compare two independent answers to the same question. " \
48
+ "Identify agreements and disagreements. " \
49
+ "Produce a final, reconciled answer that is most accurate. " \
50
+ "Note any areas of uncertainty.",
51
+ config: run_config
52
+ )
53
+ task :reconcile, reconciler, depends_on: %i[verify_a verify_b]
54
+ end
55
+ end
56
+ end
57
+ end
data/lib/aia.rb CHANGED
@@ -1,65 +1,100 @@
1
1
  # lib/aia.rb
2
2
  #
3
- # This is the main entry point for the AIA application.
4
- # The AIA module serves as the namespace for the AIA application, which
5
- # provides an interface for interacting with AI models and managing prompts.
6
-
7
- require 'ruby_llm'
8
- require 'ruby_llm/mcp'
9
-
10
- # NOTE: Complex parameters are now supported natively in ruby_llm-mcp >= 0.8.0
11
- # The support_complex_parameters! method has been removed.
3
+ # Main entry point for the AIA application (v2).
4
+ # AIA is a thin CLI shell orchestrating robot_lab robots.
12
5
 
6
+ gem 'bigdecimal', '>= 4.0'
7
+ require 'bigdecimal'
8
+ require 'robot_lab'
13
9
  require 'pm'
14
10
 
15
-
16
- require 'debug_me'
17
- include DebugMe
18
- $DEBUG_ME = false
19
- DebugMeDefaultOptions[:skip1] = true
20
-
21
- require_relative 'extensions/openstruct_merge' # adds self.merge self.get_value
22
- require_relative 'extensions/ruby_llm/modalities' # adds model.modalities.text_to_text? etc.
23
-
24
- require_relative 'refinements/string' # adds #include_any? #include_all?
11
+ require_relative 'aia/patches/ruby_llm_tool_error'
12
+ require_relative 'aia/patches/ruby_llm_streaming_error'
25
13
 
26
14
  require_relative 'aia/errors'
15
+ require_relative 'aia/timing'
16
+ require_relative 'aia/tool_introspection'
17
+ require_relative 'aia/turn_state'
18
+ require_relative 'aia/content_extractor'
19
+ require_relative 'aia/speech'
27
20
  require_relative 'aia/utility'
21
+ require_relative 'aia/mcp_server_config'
28
22
  require_relative 'aia/skill_utils'
29
23
  require_relative 'aia/version'
30
24
  require_relative 'aia/config'
31
25
  require_relative 'aia/logger'
32
26
 
33
- # Top-level logger method available anywhere in the application
34
- def logger
35
- AIA::LoggerManager.aia_logger
36
- end
37
-
38
27
  require_relative 'aia/config/cli_parser'
39
28
  require_relative 'aia/config/validator'
40
29
  require_relative 'aia/prompt_handler'
41
- require_relative 'aia/ruby_llm_adapter'
30
+ require_relative 'aia/tool_loader'
31
+ require_relative 'aia/plugin_loader'
32
+ require_relative 'aia/plugin_monitor'
33
+ require_relative 'aia/spawn_spec_parser'
34
+ require_relative 'aia/crew'
35
+ require_relative 'aia/tools/recruit_robot_tool'
36
+ require_relative 'aia/tools/reskill_robot_tool'
37
+ require_relative 'aia/system_prompt_assembler'
38
+ require_relative 'aia/mcp_config_normalizer'
39
+ require_relative 'aia/network_memory_manager'
40
+ require_relative 'aia/robot_factory'
41
+ require_relative 'aia/robot_builder'
42
42
  require_relative 'aia/directive_processor'
43
- require_relative 'aia/history_manager'
43
+ require_relative 'aia/variable_input_collector'
44
44
  require_relative 'aia/ui_presenter'
45
- require_relative 'aia/chat_processor_service'
46
45
  require_relative 'aia/input_collector'
47
- require_relative 'aia/prompt_pipeline'
46
+ require_relative 'aia/handler_context'
47
+ require_relative 'aia/handler_protocol'
48
+ require_relative 'aia/model_alias_registry'
49
+ require_relative 'aia/model_switch_handler'
50
+ require_relative 'aia/mcp_discovery'
51
+ require_relative 'aia/mcp_grouper'
52
+ require_relative 'aia/cost_calculator'
53
+ require_relative 'aia/session_tracker'
54
+ require_relative 'aia/similarity_scorer'
55
+ require_relative 'aia/fact_asserter'
56
+ require_relative 'aia/tool_filter'
57
+ require_relative 'aia/tool_filter_registry'
58
+ require_relative 'aia/tool_filter_strategy'
59
+ require_relative 'aia/streaming_runner'
60
+ require_relative 'aia/mention_router'
61
+ require_relative 'aia/special_mode_handler'
62
+ require_relative 'aia/mcp_connection_manager'
63
+ require_relative 'aia/startup_coordinator'
64
+ require_relative 'aia/pipeline_orchestrator'
48
65
  require_relative 'aia/chat_loop'
49
66
  require_relative 'aia/session'
50
67
 
51
68
  # The AIA module serves as the namespace for the AIA application, which
52
69
  # provides an interface for interacting with AI models and managing prompts.
53
70
  module AIA
71
+ require 'debug_me'
72
+ include DebugMe
73
+
74
+ $DEBUG_ME = false
75
+ DebugMeDefaultOptions[:skip1] = true
76
+
54
77
  at_exit do
55
- warn 'Exiting AIA application...'
78
+ $stderr.puts 'Exiting AIA application...'
56
79
  end
57
80
 
58
81
  @config = nil
59
82
  @client = nil
83
+ @session_tracker = nil
84
+ @turn_state = TurnState.new
60
85
 
61
86
  class << self
62
- attr_accessor :config, :client
87
+ attr_accessor :config, :client, :session_tracker, :turn_state, :task_coordinator
88
+
89
+ def logger
90
+ AIA::LoggerManager.aia_logger
91
+ end
92
+
93
+ def reset!
94
+ @config = @client = @session_tracker = @turn_state =
95
+ @task_coordinator = nil
96
+ ToolLoader.reset_instance!
97
+ end
63
98
 
64
99
  def good_file?(filename)
65
100
  File.exist?(filename) &&
@@ -67,78 +102,104 @@ module AIA
67
102
  !File.directory?(filename)
68
103
  end
69
104
 
70
-
71
105
  def bad_file?(filename)
72
106
  !good_file?(filename)
73
107
  end
74
108
 
75
-
76
109
  # Convenience flag accessors (delegate to config.flags section)
77
110
  def chat?
78
111
  @config&.flags&.chat == true
79
112
  end
80
113
 
81
-
82
114
  def debug?
83
115
  @config&.flags&.debug == true
84
116
  end
85
117
 
118
+ # Emit a warning always; if --debug is active and an exception is given,
119
+ # also print the first 5 backtrace frames so errors are diagnosable.
120
+ #
121
+ # @param msg [String]
122
+ # @param exc [Exception, nil]
123
+ def debug_warn(msg, exc: nil)
124
+ $stderr.puts msg
125
+ return unless exc && exc.backtrace && config&.flags&.debug
126
+ $stderr.puts exc.backtrace.first(5).join("\n")
127
+ end
86
128
 
87
129
  def verbose?
88
130
  @config&.flags&.verbose == true
89
131
  end
90
132
 
91
-
92
133
  def fuzzy?
93
134
  @config&.flags&.fuzzy == true
94
135
  end
95
136
 
96
-
97
137
  def speak?
98
138
  @config&.flags&.speak == true
99
139
  end
100
140
 
141
+ def paint?
142
+ @config&.flags&.paint == true
143
+ end
101
144
 
102
145
  def append?
103
146
  @config&.output&.append == true
104
147
  end
105
148
 
106
-
149
+ # :reek:TooManyStatements -- application boot sequence: parse, validate, log, load plugins, build robots, start session
107
150
  def run
108
- # Parse CLI arguments
109
151
  cli_overrides = CLIParser.parse
110
-
111
- # Create config with CLI overrides
112
152
  @config = Config.setup(cli_overrides)
153
+ return if ConfigValidator.tailor(@config) == :early_exit
154
+
155
+ log_startup
156
+
157
+ PluginLoader.load!(@config)
158
+ start_plugin_monitor
159
+ RobotFactory.setup(@config)
160
+ load_fzf_integration
161
+ start_session
162
+ rescue AIA::ConfigurationError => e
163
+ $stderr.puts e.message
164
+ exit 1
165
+ rescue AIA::Error => e
166
+ $stderr.puts e.message
167
+ exit 1
168
+ end
113
169
 
114
- # Validate and tailor configuration (handles --dump early exit)
115
- ConfigValidator.tailor(@config)
116
-
117
- # Load Fzf if fuzzy search is enabled and fzf is installed
118
- if @config.flags.fuzzy
119
- begin
120
- if system('which fzf >/dev/null 2>&1')
121
- require_relative 'aia/fzf'
122
- else
123
- warn 'Warning: Fuzzy search enabled but fzf not found. Install fzf for enhanced search capabilities.'
124
- end
125
- rescue StandardError => e
126
- warn "Warning: Failed to load fzf: #{e.message}"
127
- end
128
- end
170
+ private
171
+
172
+ # Record startup in all three log files. Only aia.log gets the full
173
+ # configuration dump; llm.log and mcp.log just note that AIA started,
174
+ # since their configuration is already covered by the aia.log entry.
175
+ def log_startup
176
+ LoggerManager.aia_logger.debug('AIA started up.', @config.to_h)
177
+ LoggerManager.llm_logger.debug('AIA started up.')
178
+ LoggerManager.mcp_logger.debug('AIA started up.')
179
+ end
129
180
 
130
- prompt_handler = PromptHandler.new
181
+ def start_plugin_monitor
182
+ return unless @config.flags.chat
131
183
 
132
- @client = RubyLLMAdapter.new
184
+ @plugin_monitor = PluginMonitor.new(@config).start
185
+ at_exit { @plugin_monitor&.stop }
186
+ end
187
+
188
+ def load_fzf_integration
189
+ return unless @config.flags.fuzzy
133
190
 
134
- # There are two kinds of sessions: batch and chat
135
- # A chat session is started when the --chat CLI option is used
136
- # BUT its also possible to start a chat session with an initial prompt AND
137
- # within that initial prompt there can be a workflow (aka pipeline)
138
- # defined. If that is the case, then the chat session will not start
139
- # until the initial prompt has completed its workflow.
191
+ if system('which fzf >/dev/null 2>&1')
192
+ require_relative 'aia/fzf'
193
+ else
194
+ $stderr.puts 'Warning: Fuzzy search enabled but fzf not found. Install fzf for enhanced search capabilities.'
195
+ end
196
+ rescue StandardError => e
197
+ $stderr.puts "Warning: Failed to load fzf: #{e.message}"
198
+ end
140
199
 
141
- session = Session.new(prompt_handler)
200
+ def start_session
201
+ session = Session.new(PromptHandler.new)
202
+ at_exit { session.cleanup }
142
203
  session.start
143
204
  end
144
205
  end
data/mkdocs.yml CHANGED
@@ -151,6 +151,7 @@ nav:
151
151
  - Getting Started: guides/getting-started.md
152
152
  - Chat Mode: guides/chat.md
153
153
  - Working with Models: guides/models.md
154
+ - Crews: guides/crew.md
154
155
  - Local Models: guides/local-models.md
155
156
  - Available Models: guides/available-models.md
156
157
  - Image Generation: guides/image-generation.md