aia 2.0.0.0.pre.alpha → 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.
- checksums.yaml +4 -4
- data/.envrc +5 -1
- data/.loki +3 -223
- data/.reek.yml +160 -0
- data/.rubocop_strict.yml +15 -0
- data/.version +1 -1
- data/CHANGELOG.md +10 -5
- data/README.md +1 -1
- data/Rakefile +0 -113
- data/_typos.toml +10 -0
- data/aia.gemspec +92 -0
- data/config/aia.yml +13 -0
- data/lib/aia/chat_loop.rb +8 -52
- data/lib/aia/config/cli_parser.rb +66 -57
- data/lib/aia/config/mcp_parser.rb +27 -19
- data/lib/aia/config/validator.rb +83 -94
- data/lib/aia/config.rb +3 -2
- data/lib/aia/content_extractor.rb +2 -0
- data/lib/aia/cost_calculator.rb +1 -0
- data/lib/aia/debate_handler.rb +28 -20
- data/lib/aia/delegate_handler.rb +7 -3
- data/lib/aia/directive.rb +10 -8
- data/lib/aia/directives/configuration_directives.rb +115 -101
- data/lib/aia/directives/context_directives.rb +31 -28
- data/lib/aia/directives/execution_directives.rb +18 -13
- data/lib/aia/directives/model_directives.rb +134 -154
- data/lib/aia/directives/trakflow_directives.rb +27 -9
- data/lib/aia/directives/utility_directives.rb +71 -71
- data/lib/aia/directives/web_and_file_directives.rb +63 -50
- data/lib/aia/layered_orchestrator.rb +66 -43
- data/lib/aia/logger.rb +21 -21
- data/lib/aia/mcp_connection_manager.rb +50 -40
- data/lib/aia/mcp_server_config.rb +30 -0
- data/lib/aia/mcp_utility.rb +7 -4
- data/lib/aia/mention_router.rb +7 -50
- data/lib/aia/network_builder.rb +10 -5
- data/lib/aia/network_memory_manager.rb +6 -6
- data/lib/aia/pipeline_orchestrator.rb +20 -10
- data/lib/aia/plugin_monitor.rb +9 -6
- data/lib/aia/prompt_decomposer.rb +5 -3
- data/lib/aia/prompt_handler.rb +43 -49
- data/lib/aia/robot_factory.rb +12 -8
- data/lib/aia/robot_namer.rb +2 -8
- data/lib/aia/session.rb +7 -3
- data/lib/aia/session_tracker.rb +39 -41
- data/lib/aia/similarity_scorer.rb +5 -3
- data/lib/aia/special_mode_handler.rb +52 -32
- data/lib/aia/speech.rb +67 -0
- data/lib/aia/startup_coordinator.rb +1 -0
- data/lib/aia/streaming_runner.rb +6 -3
- data/lib/aia/system_prompt_assembler.rb +11 -7
- data/lib/aia/task_coordinator.rb +10 -5
- data/lib/aia/timing.rb +15 -0
- data/lib/aia/tool_filter.rb +1 -0
- data/lib/aia/tool_filter_strategy.rb +28 -25
- data/lib/aia/tool_introspection.rb +17 -0
- data/lib/aia/tool_loader.rb +12 -6
- data/lib/aia/tools/task_board_tool.rb +1 -0
- data/lib/aia/trakflow_bridge.rb +5 -3
- data/lib/aia/turn_state.rb +1 -0
- data/lib/aia/ui_presenter.rb +67 -59
- data/lib/aia/utility.rb +5 -3
- data/lib/aia/variable_input_collector.rb +1 -0
- data/lib/aia/verification_network.rb +1 -2
- data/lib/aia.rb +16 -0
- metadata +15 -9
- data/.quality/flay_baseline.txt +0 -1
- data/.quality/flog_baseline.txt +0 -29
- data/.quality/reek_baseline.txt +0 -80
|
@@ -5,16 +5,18 @@ module AIA
|
|
|
5
5
|
state_setting! :model
|
|
6
6
|
|
|
7
7
|
desc "View or set configuration values"
|
|
8
|
+
# :reek:TooManyStatements -- view/set dispatch on arg count, coercing booleans and dot-notation keys before writing
|
|
8
9
|
def config(args = [], context_manager = nil)
|
|
9
10
|
args = Array(args)
|
|
11
|
+
cfg = AIA.config
|
|
10
12
|
|
|
11
13
|
if args.empty?
|
|
12
|
-
ap
|
|
14
|
+
ap cfg.to_h
|
|
13
15
|
elsif args.length == 1
|
|
14
16
|
config_item = args.first
|
|
15
17
|
local_cfg = {}
|
|
16
|
-
local_cfg[config_item] = if
|
|
17
|
-
|
|
18
|
+
local_cfg[config_item] = if cfg.respond_to?(config_item)
|
|
19
|
+
cfg.send(config_item)
|
|
18
20
|
end
|
|
19
21
|
ap local_cfg
|
|
20
22
|
else
|
|
@@ -41,66 +43,22 @@ module AIA
|
|
|
41
43
|
alias cfg config
|
|
42
44
|
|
|
43
45
|
desc "View or change the AI model"
|
|
44
|
-
# rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/MethodLength, Metrics/PerceivedComplexity
|
|
45
46
|
def model(args, context_manager = nil)
|
|
47
|
+
cfg = AIA.config
|
|
48
|
+
|
|
46
49
|
if args.empty?
|
|
47
50
|
puts
|
|
48
|
-
models =
|
|
49
|
-
|
|
50
|
-
if models.size == 1
|
|
51
|
-
puts "Current Model:"
|
|
52
|
-
puts "=============="
|
|
53
|
-
model_name = models.first.respond_to?(:name) ? models.first.name : models.first.to_s
|
|
54
|
-
begin
|
|
55
|
-
model_info = RubyLLM::Models.find(model_name)
|
|
56
|
-
puts model_info.to_h.pretty_inspect if model_info
|
|
57
|
-
rescue StandardError
|
|
58
|
-
puts " #{model_name}"
|
|
59
|
-
end
|
|
60
|
-
else
|
|
61
|
-
puts "Multi-Model Configuration:"
|
|
62
|
-
puts "=========================="
|
|
63
|
-
puts "Model count: #{models.size}"
|
|
64
|
-
first_model = models.first.respond_to?(:name) ? models.first.name : models.first.to_s
|
|
65
|
-
puts "Primary model: #{first_model} (used for consensus when --consensus flag is enabled)"
|
|
66
|
-
consensus = AIA.config.flags.consensus
|
|
67
|
-
puts "Consensus mode: #{consensus.nil? ? 'auto-detect (disabled by default)' : consensus}"
|
|
68
|
-
puts
|
|
69
|
-
puts "Model Details:"
|
|
70
|
-
puts "-" * 50
|
|
71
|
-
|
|
72
|
-
models.each_with_index do |model_spec, index|
|
|
73
|
-
model_name = model_spec.respond_to?(:name) ? model_spec.name : model_spec.to_s
|
|
74
|
-
puts "#{index + 1}. #{model_name}#{' (primary)' if index.zero?}"
|
|
75
|
-
|
|
76
|
-
begin
|
|
77
|
-
model_info = RubyLLM::Models.find(model_name)
|
|
78
|
-
if model_info
|
|
79
|
-
puts " Provider: #{model_info.provider || 'Unknown'}"
|
|
80
|
-
puts " Context window: #{model_info.context_window || 'Unknown'}"
|
|
81
|
-
puts " Input cost: $#{model_info.input_cost || 'Unknown'}"
|
|
82
|
-
puts " Output cost: $#{model_info.output_cost || 'Unknown'}"
|
|
83
|
-
puts " Mode: #{model_info.modalities || 'Unknown'}"
|
|
84
|
-
puts " Capabilities: #{(model_info.capabilities || []).join(', ')}" if model_info.capabilities&.any?
|
|
85
|
-
else
|
|
86
|
-
puts " Details: Model not found in registry"
|
|
87
|
-
end
|
|
88
|
-
rescue StandardError => e
|
|
89
|
-
puts " Details: Unable to fetch (#{e.class.name}: #{e.message})"
|
|
90
|
-
end
|
|
91
|
-
puts
|
|
92
|
-
end
|
|
93
|
-
end
|
|
51
|
+
models = cfg.models
|
|
52
|
+
models.size == 1 ? show_single_model(models) : show_multi_model(models)
|
|
94
53
|
puts
|
|
95
54
|
else
|
|
96
55
|
model_names = args.join(' ').split(',').map(&:strip).reject(&:empty?)
|
|
97
|
-
|
|
98
|
-
AIA.client = RobotFactory.rebuild(
|
|
56
|
+
cfg.models = AIA::Config::TO_MODEL_SPECS.call(model_names)
|
|
57
|
+
AIA.client = RobotFactory.rebuild(cfg)
|
|
99
58
|
end
|
|
100
59
|
|
|
101
60
|
''
|
|
102
61
|
end
|
|
103
|
-
# rubocop:enable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/MethodLength, Metrics/PerceivedComplexity
|
|
104
62
|
|
|
105
63
|
desc "Set the temperature parameter for AI responses"
|
|
106
64
|
def temperature(args, context_manager = nil)
|
|
@@ -115,7 +73,7 @@ module AIA
|
|
|
115
73
|
alias topp top_p
|
|
116
74
|
|
|
117
75
|
desc "Dump session cost/token metrics as CSV"
|
|
118
|
-
#
|
|
76
|
+
# :reek:TooManyStatements -- CSV assembly: guards, header, rows, totals, print, optional file append
|
|
119
77
|
def cost(args = [], context_manager = nil)
|
|
120
78
|
tracker = AIA.session_tracker
|
|
121
79
|
unless tracker
|
|
@@ -133,63 +91,115 @@ module AIA
|
|
|
133
91
|
|
|
134
92
|
header = %w[model input_tokens output_tokens total_tokens cost elapsed]
|
|
135
93
|
header << 'similarity' if has_similarity
|
|
136
|
-
lines
|
|
137
|
-
|
|
138
|
-
turns.
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
"$#{'%.5f' % cost_val}",
|
|
151
|
-
"%.1fs" % elapsed
|
|
152
|
-
]
|
|
153
|
-
|
|
154
|
-
if has_similarity
|
|
155
|
-
sim = turn[:similarity]
|
|
156
|
-
row << (sim.nil? ? 'ref' : "%.1f%%" % (sim * 100))
|
|
157
|
-
end
|
|
94
|
+
lines = [header.join(',')]
|
|
95
|
+
turns.each { |turn| lines << cost_row(turn, has_similarity).join(',') }
|
|
96
|
+
lines << cost_totals_row(turns, has_similarity).join(',')
|
|
97
|
+
|
|
98
|
+
csv = lines.join("\n")
|
|
99
|
+
puts
|
|
100
|
+
puts csv
|
|
101
|
+
puts
|
|
102
|
+
|
|
103
|
+
out_file = AIA.config.output&.file
|
|
104
|
+
File.open(out_file, 'a') { |f| f.puts csv } if out_file
|
|
105
|
+
|
|
106
|
+
''
|
|
107
|
+
end
|
|
158
108
|
|
|
159
|
-
|
|
109
|
+
private
|
|
110
|
+
|
|
111
|
+
# One CSV row for a single recorded turn.
|
|
112
|
+
def cost_row(turn, has_similarity)
|
|
113
|
+
input_t = turn[:input_tokens] || 0
|
|
114
|
+
output_t = turn[:output_tokens] || 0
|
|
115
|
+
|
|
116
|
+
row = [
|
|
117
|
+
turn[:model],
|
|
118
|
+
input_t,
|
|
119
|
+
output_t,
|
|
120
|
+
input_t + output_t,
|
|
121
|
+
"$#{'%.5f' % (turn[:cost] || 0.0)}",
|
|
122
|
+
"%.1fs" % (turn[:elapsed] || 0)
|
|
123
|
+
]
|
|
124
|
+
|
|
125
|
+
if has_similarity
|
|
126
|
+
sim = turn[:similarity]
|
|
127
|
+
row << (sim.nil? ? 'ref' : "%.1f%%" % (sim * 100))
|
|
160
128
|
end
|
|
129
|
+
row
|
|
130
|
+
end
|
|
161
131
|
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
132
|
+
# The TOTAL summary row across all turns.
|
|
133
|
+
# :reek:DuplicateMethodCall -- each turns.sum aggregates a different field; there is no shared value to hoist
|
|
134
|
+
def cost_totals_row(turns, has_similarity)
|
|
135
|
+
total_input = turns.sum { |t| t[:input_tokens] || 0 }
|
|
136
|
+
total_output = turns.sum { |t| t[:output_tokens] || 0 }
|
|
137
|
+
total_cost = turns.sum { |t| t[:cost] || 0.0 }
|
|
138
|
+
max_elapsed = turns.map { |t| t[:elapsed] || 0 }.max
|
|
168
139
|
|
|
169
140
|
totals = [
|
|
170
141
|
'TOTAL',
|
|
171
142
|
total_input,
|
|
172
143
|
total_output,
|
|
173
|
-
|
|
144
|
+
total_input + total_output,
|
|
174
145
|
"$#{'%.5f' % total_cost}",
|
|
175
146
|
"%.1fs" % max_elapsed
|
|
176
147
|
]
|
|
177
148
|
totals << '' if has_similarity
|
|
178
|
-
|
|
149
|
+
totals
|
|
150
|
+
end
|
|
179
151
|
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
152
|
+
# Format a model spec's display name whether it is a ModelSpec or a bare string.
|
|
153
|
+
def model_display_name(spec)
|
|
154
|
+
spec.respond_to?(:name) ? spec.name : spec.to_s
|
|
155
|
+
end
|
|
184
156
|
|
|
185
|
-
|
|
186
|
-
|
|
157
|
+
def show_single_model(models)
|
|
158
|
+
puts "Current Model:"
|
|
159
|
+
puts "=============="
|
|
160
|
+
model_name = model_display_name(models.first)
|
|
161
|
+
begin
|
|
162
|
+
model_info = RubyLLM::Models.find(model_name)
|
|
163
|
+
puts model_info.to_h.pretty_inspect if model_info
|
|
164
|
+
rescue StandardError
|
|
165
|
+
puts " #{model_name}"
|
|
166
|
+
end
|
|
167
|
+
end
|
|
187
168
|
|
|
188
|
-
|
|
169
|
+
# :reek:TooManyStatements -- sequential terminal report: summary lines then a details block per model
|
|
170
|
+
def show_multi_model(models)
|
|
171
|
+
puts "Multi-Model Configuration:"
|
|
172
|
+
puts "=========================="
|
|
173
|
+
puts "Model count: #{models.size}"
|
|
174
|
+
puts "Primary model: #{model_display_name(models.first)} (used for consensus when --consensus flag is enabled)"
|
|
175
|
+
consensus = AIA.config.flags.consensus
|
|
176
|
+
puts "Consensus mode: #{consensus.nil? ? 'auto-detect (disabled by default)' : consensus}"
|
|
177
|
+
puts
|
|
178
|
+
puts "Model Details:"
|
|
179
|
+
puts "-" * 50
|
|
180
|
+
|
|
181
|
+
models.each_with_index do |model_spec, index|
|
|
182
|
+
puts "#{index + 1}. #{model_display_name(model_spec)}#{' (primary)' if index.zero?}"
|
|
183
|
+
show_model_details(model_display_name(model_spec))
|
|
184
|
+
puts
|
|
185
|
+
end
|
|
189
186
|
end
|
|
190
|
-
# rubocop:enable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/MethodLength, Metrics/PerceivedComplexity
|
|
191
187
|
|
|
192
|
-
|
|
188
|
+
def show_model_details(model_name)
|
|
189
|
+
model_info = RubyLLM::Models.find(model_name)
|
|
190
|
+
if model_info
|
|
191
|
+
puts " Provider: #{model_info.provider || 'Unknown'}"
|
|
192
|
+
puts " Context window: #{model_info.context_window || 'Unknown'}"
|
|
193
|
+
puts " Input cost: $#{model_info.input_cost || 'Unknown'}"
|
|
194
|
+
puts " Output cost: $#{model_info.output_cost || 'Unknown'}"
|
|
195
|
+
puts " Mode: #{model_info.modalities || 'Unknown'}"
|
|
196
|
+
puts " Capabilities: #{(model_info.capabilities || []).join(', ')}" if model_info.capabilities&.any?
|
|
197
|
+
else
|
|
198
|
+
puts " Details: Model not found in registry"
|
|
199
|
+
end
|
|
200
|
+
rescue StandardError => e
|
|
201
|
+
puts " Details: Unable to fetch (#{e.class.name}: #{e.message})"
|
|
202
|
+
end
|
|
193
203
|
|
|
194
204
|
# Apply a config value by key, supporting three forms:
|
|
195
205
|
# flat CLI key — "debug" → CLI_TO_NESTED_MAP routing
|
|
@@ -197,13 +207,15 @@ module AIA
|
|
|
197
207
|
# direct setter — "log_level_override" → AIA.config.<key>=
|
|
198
208
|
#
|
|
199
209
|
# Returns true if the value was set, false if the key was unknown.
|
|
210
|
+
# :reek:TooManyStatements -- three key-addressing forms (dot-notation walk, CLI map, direct setter) tried in order
|
|
200
211
|
def write_config_value(config_item, new_value) # rubocop:disable Naming/PredicateMethod
|
|
201
212
|
key_str = config_item.to_s
|
|
213
|
+
cfg = AIA.config
|
|
202
214
|
|
|
203
215
|
# Dot-notation: walk the config object by path segments
|
|
204
216
|
if key_str.include?('.')
|
|
205
217
|
parts = key_str.split('.')
|
|
206
|
-
obj =
|
|
218
|
+
obj = cfg
|
|
207
219
|
parents = parts[0..-2]
|
|
208
220
|
leaf = parts.last
|
|
209
221
|
|
|
@@ -222,15 +234,15 @@ module AIA
|
|
|
222
234
|
|
|
223
235
|
# Flat CLI key via CLI_TO_NESTED_MAP (e.g. "debug" → flags.debug)
|
|
224
236
|
if AIA::Config::CLI_TO_NESTED_MAP.key?(key_str.to_sym) &&
|
|
225
|
-
|
|
226
|
-
|
|
237
|
+
cfg.respond_to?(:apply_overrides)
|
|
238
|
+
cfg.apply_overrides({ key_str.to_sym => new_value })
|
|
227
239
|
maybe_clear_log_override(key_str, new_value)
|
|
228
240
|
return true
|
|
229
241
|
end
|
|
230
242
|
|
|
231
243
|
# Direct setter on the config object (e.g. "log_level_override")
|
|
232
|
-
if
|
|
233
|
-
|
|
244
|
+
if cfg.respond_to?("#{key_str}=")
|
|
245
|
+
cfg.send("#{key_str}=", new_value)
|
|
234
246
|
return true
|
|
235
247
|
end
|
|
236
248
|
|
|
@@ -241,9 +253,11 @@ module AIA
|
|
|
241
253
|
# drops back to its configured level rather than staying at DEBUG.
|
|
242
254
|
def maybe_clear_log_override(key, value)
|
|
243
255
|
return unless key.to_s == 'debug' && value == false
|
|
244
|
-
return unless AIA.config.respond_to?(:log_level_override=)
|
|
245
256
|
|
|
246
|
-
|
|
257
|
+
cfg = AIA.config
|
|
258
|
+
return unless cfg.respond_to?(:log_level_override=)
|
|
259
|
+
|
|
260
|
+
cfg.log_level_override = nil if cfg.log_level_override.to_s == 'debug'
|
|
247
261
|
end
|
|
248
262
|
end
|
|
249
263
|
end
|
|
@@ -21,6 +21,7 @@ module AIA
|
|
|
21
21
|
end
|
|
22
22
|
|
|
23
23
|
desc "Create a named checkpoint of the current context"
|
|
24
|
+
# :reek:TooManyStatements -- one snapshot build: name resolution, deep copy of every chat message list, store entry, feedback
|
|
24
25
|
def checkpoint(args, _unused = nil)
|
|
25
26
|
name = args.empty? ? nil : args.join(' ').strip
|
|
26
27
|
|
|
@@ -50,6 +51,7 @@ module AIA
|
|
|
50
51
|
alias cp checkpoint
|
|
51
52
|
|
|
52
53
|
desc "Restore context to a previous checkpoint"
|
|
54
|
+
# :reek:TooManyStatements -- restore flow: name resolution, validation, per-chat message replacement, user feedback
|
|
53
55
|
def restore(args, _unused = nil)
|
|
54
56
|
name = args.empty? ? nil : args.join(' ').strip
|
|
55
57
|
|
|
@@ -107,44 +109,23 @@ module AIA
|
|
|
107
109
|
end
|
|
108
110
|
|
|
109
111
|
desc "Display the current conversation context with checkpoint markers"
|
|
110
|
-
# rubocop:disable Metrics/AbcSize
|
|
111
112
|
def review(args, _unused = nil)
|
|
112
113
|
chats = get_chats
|
|
113
114
|
return "Error: No active chat sessions found." if chats.nil? || chats.empty?
|
|
114
115
|
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
puts "\n=== Chat Context (RubyLLM) ==="
|
|
119
|
-
puts "Total messages: #{messages.size}"
|
|
120
|
-
puts "Models: #{chats.keys.join(', ')}"
|
|
121
|
-
puts "Checkpoints: #{checkpoint_names.join(', ')}" if checkpoint_names.any?
|
|
122
|
-
puts
|
|
116
|
+
messages = chats.values.first&.messages || []
|
|
117
|
+
print_review_header(chats, messages.size)
|
|
123
118
|
|
|
124
119
|
positions = checkpoint_positions
|
|
125
|
-
|
|
126
120
|
messages.each_with_index do |msg, index|
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
puts "-" * 40
|
|
130
|
-
end
|
|
131
|
-
|
|
132
|
-
role = msg.role.to_s.capitalize
|
|
133
|
-
content = format_message_content(msg)
|
|
134
|
-
|
|
135
|
-
puts "#{index + 1}. [#{role}]: #{content}"
|
|
136
|
-
puts
|
|
137
|
-
end
|
|
138
|
-
|
|
139
|
-
if positions[messages.size]
|
|
140
|
-
puts "📍 [Checkpoint: #{positions[messages.size].join(', ')}]"
|
|
141
|
-
puts "-" * 40
|
|
121
|
+
print_checkpoint_marker(positions[index])
|
|
122
|
+
print_review_message(msg, index)
|
|
142
123
|
end
|
|
124
|
+
print_checkpoint_marker(positions[messages.size])
|
|
143
125
|
|
|
144
126
|
puts "=== End of Context ==="
|
|
145
127
|
""
|
|
146
128
|
end
|
|
147
|
-
# rubocop:enable Metrics/AbcSize
|
|
148
129
|
alias context review
|
|
149
130
|
|
|
150
131
|
desc "List all available checkpoints"
|
|
@@ -157,9 +138,10 @@ module AIA
|
|
|
157
138
|
puts "\n=== Available Checkpoints ==="
|
|
158
139
|
@checkpoint_store.each do |name, data|
|
|
159
140
|
created = data[:created_at]&.strftime('%H:%M:%S') || 'unknown'
|
|
141
|
+
preview = data[:topic_preview]
|
|
160
142
|
puts " #{name}: position #{data[:position]}, created #{created}"
|
|
161
|
-
if
|
|
162
|
-
puts " → \"#{
|
|
143
|
+
if preview && !preview.empty?
|
|
144
|
+
puts " → \"#{preview}\""
|
|
163
145
|
end
|
|
164
146
|
end
|
|
165
147
|
puts "=== End of Checkpoints ==="
|
|
@@ -173,6 +155,27 @@ module AIA
|
|
|
173
155
|
@checkpoint_store.keys
|
|
174
156
|
end
|
|
175
157
|
|
|
158
|
+
def print_review_header(chats, total)
|
|
159
|
+
puts "\n=== Chat Context (RubyLLM) ==="
|
|
160
|
+
puts "Total messages: #{total}"
|
|
161
|
+
puts "Models: #{chats.keys.join(', ')}"
|
|
162
|
+
puts "Checkpoints: #{checkpoint_names.join(', ')}" if checkpoint_names.any?
|
|
163
|
+
puts
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
# Print the marker block when checkpoint names exist at this position.
|
|
167
|
+
def print_checkpoint_marker(names)
|
|
168
|
+
return unless names
|
|
169
|
+
|
|
170
|
+
puts "📍 [Checkpoint: #{names.join(', ')}]"
|
|
171
|
+
puts "-" * 40
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
def print_review_message(msg, index)
|
|
175
|
+
puts "#{index + 1}. [#{msg.role.to_s.capitalize}]: #{format_message_content(msg)}"
|
|
176
|
+
puts
|
|
177
|
+
end
|
|
178
|
+
|
|
176
179
|
def checkpoint_positions
|
|
177
180
|
positions = {}
|
|
178
181
|
@checkpoint_store.each do |name, data|
|
|
@@ -13,9 +13,10 @@ module AIA
|
|
|
13
13
|
desc "Execute Ruby code (requires allow_ruby_eval: true in config)"
|
|
14
14
|
def ruby(args, context_manager = nil)
|
|
15
15
|
unless AIA.config.flags.allow_ruby_eval
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
16
|
+
puts "ERROR: /ruby is disabled. Set `allow_ruby_eval: true` in " \
|
|
17
|
+
"~/.aia/config.yml or pass --allow-ruby-eval to enable it. " \
|
|
18
|
+
"Note: this executes arbitrary Ruby with full process privileges."
|
|
19
|
+
return ""
|
|
19
20
|
end
|
|
20
21
|
|
|
21
22
|
ruby_code = args.join(' ')
|
|
@@ -35,10 +36,11 @@ module AIA
|
|
|
35
36
|
desc "Use text-to-speech to speak the text"
|
|
36
37
|
def say(args, context_manager = nil)
|
|
37
38
|
audio = AIA.config.audio
|
|
39
|
+
voice = audio.voice
|
|
38
40
|
env = {}
|
|
39
41
|
env['SPEECH_MODEL'] = audio.speech_model if audio.speech_model
|
|
40
|
-
if
|
|
41
|
-
system(env, 'say', '-v',
|
|
42
|
+
if voice && !voice.strip.empty?
|
|
43
|
+
system(env, 'say', '-v', voice, *args)
|
|
42
44
|
else
|
|
43
45
|
system(env, 'say', *args)
|
|
44
46
|
end
|
|
@@ -80,17 +82,19 @@ module AIA
|
|
|
80
82
|
desc "Spawn a specialist robot for the next prompt. " \
|
|
81
83
|
"Usage: /spawn | /spawn <type> | /spawn <name> <provider/model> <system prompt>"
|
|
82
84
|
def spawn(args, context_manager = nil)
|
|
83
|
-
AIA.turn_state
|
|
85
|
+
state = AIA.turn_state
|
|
86
|
+
state.force_spawn = true
|
|
84
87
|
|
|
85
88
|
if args.size >= 2
|
|
86
89
|
spec = AIA::SpawnSpecParser.parse(args)
|
|
87
|
-
|
|
88
|
-
|
|
90
|
+
state.spawn_spec = spec
|
|
91
|
+
state.spawn_type = nil
|
|
89
92
|
"Spawn mode enabled: '#{spec[:name]}' (#{spawn_model_summary(spec)}) for next prompt."
|
|
90
93
|
else
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
+
type = args.first
|
|
95
|
+
state.spawn_spec = nil
|
|
96
|
+
state.spawn_type = type
|
|
97
|
+
type_msg = type ? " (#{type})" : " (auto-detect)"
|
|
94
98
|
"Spawn mode enabled#{type_msg} for next prompt."
|
|
95
99
|
end
|
|
96
100
|
end
|
|
@@ -144,9 +148,10 @@ module AIA
|
|
|
144
148
|
|
|
145
149
|
# Human-readable model description for the /spawn confirmation message.
|
|
146
150
|
def spawn_model_summary(spec)
|
|
147
|
-
|
|
151
|
+
model = spec[:model]
|
|
152
|
+
return 'inherited model' unless model
|
|
148
153
|
|
|
149
|
-
spec[:provider] ? "#{spec[:provider]}/#{
|
|
154
|
+
spec[:provider] ? "#{spec[:provider]}/#{model}" : model
|
|
150
155
|
end
|
|
151
156
|
|
|
152
157
|
# Model plus any assigned skills, for the /add_recruit confirmation message.
|