aia 0.9.23 → 0.10.2
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/.version +1 -1
- data/CHANGELOG.md +95 -3
- data/README.md +187 -60
- data/bin/aia +6 -0
- data/docs/cli-reference.md +145 -72
- data/docs/configuration.md +156 -19
- data/docs/directives-reference.md +28 -8
- data/docs/examples/tools/index.md +2 -2
- data/docs/faq.md +11 -11
- data/docs/guides/available-models.md +11 -11
- data/docs/guides/basic-usage.md +18 -17
- data/docs/guides/chat.md +57 -11
- data/docs/guides/executable-prompts.md +15 -15
- data/docs/guides/first-prompt.md +2 -2
- data/docs/guides/getting-started.md +6 -6
- data/docs/guides/image-generation.md +24 -24
- data/docs/guides/local-models.md +2 -2
- data/docs/guides/models.md +96 -18
- data/docs/guides/tools.md +4 -4
- data/docs/index.md +2 -2
- data/docs/installation.md +2 -2
- data/docs/prompt_management.md +11 -11
- data/docs/security.md +3 -3
- data/docs/workflows-and-pipelines.md +1 -1
- data/examples/README.md +6 -6
- data/examples/headlines +3 -3
- data/lib/aia/aia_completion.bash +2 -2
- data/lib/aia/aia_completion.fish +4 -4
- data/lib/aia/aia_completion.zsh +2 -2
- data/lib/aia/chat_processor_service.rb +31 -21
- data/lib/aia/config/cli_parser.rb +403 -403
- data/lib/aia/config/config_section.rb +87 -0
- data/lib/aia/config/defaults.yml +219 -0
- data/lib/aia/config/defaults_loader.rb +147 -0
- data/lib/aia/config/mcp_parser.rb +151 -0
- data/lib/aia/config/model_spec.rb +67 -0
- data/lib/aia/config/validator.rb +185 -136
- data/lib/aia/config.rb +336 -17
- data/lib/aia/directive_processor.rb +14 -6
- data/lib/aia/directives/checkpoint.rb +283 -0
- data/lib/aia/directives/configuration.rb +27 -98
- data/lib/aia/directives/models.rb +15 -9
- data/lib/aia/directives/registry.rb +2 -0
- data/lib/aia/directives/utility.rb +25 -9
- data/lib/aia/directives/web_and_file.rb +50 -47
- data/lib/aia/logger.rb +328 -0
- data/lib/aia/prompt_handler.rb +18 -22
- data/lib/aia/ruby_llm_adapter.rb +584 -65
- data/lib/aia/session.rb +49 -156
- data/lib/aia/topic_context.rb +125 -0
- data/lib/aia/ui_presenter.rb +20 -16
- data/lib/aia/utility.rb +50 -18
- data/lib/aia.rb +91 -66
- data/lib/extensions/ruby_llm/modalities.rb +2 -0
- data/mcp_servers/apple-mcp.json +8 -0
- data/mcp_servers/mcp_server_chart.json +11 -0
- data/mcp_servers/playwright_one.json +8 -0
- data/mcp_servers/playwright_two.json +8 -0
- data/mcp_servers/tavily_mcp_server.json +8 -0
- metadata +85 -26
- data/lib/aia/config/base.rb +0 -288
- data/lib/aia/config/defaults.rb +0 -91
- data/lib/aia/config/file_loader.rb +0 -163
- data/lib/aia/context_manager.rb +0 -134
- data/mcp_servers/imcp.json +0 -7
- data/mcp_servers/launcher.json +0 -11
- data/mcp_servers/timeserver.json +0 -8
|
@@ -0,0 +1,283 @@
|
|
|
1
|
+
# lib/aia/directives/checkpoint.rb
|
|
2
|
+
#
|
|
3
|
+
# Checkpoint and restore directives for managing conversation state.
|
|
4
|
+
# Uses RubyLLM's Chat.@messages as the source of truth for conversation history.
|
|
5
|
+
#
|
|
6
|
+
|
|
7
|
+
module AIA
|
|
8
|
+
module Directives
|
|
9
|
+
module Checkpoint
|
|
10
|
+
# Module-level state for checkpoints
|
|
11
|
+
# Note: Using @checkpoint_store to avoid naming conflict with //checkpoints directive
|
|
12
|
+
@checkpoint_store = {}
|
|
13
|
+
@checkpoint_counter = 0
|
|
14
|
+
@last_checkpoint_name = nil
|
|
15
|
+
|
|
16
|
+
class << self
|
|
17
|
+
attr_accessor :checkpoint_store, :checkpoint_counter, :last_checkpoint_name
|
|
18
|
+
|
|
19
|
+
# Reset all checkpoint state (useful for testing)
|
|
20
|
+
def reset!
|
|
21
|
+
@checkpoint_store = {}
|
|
22
|
+
@checkpoint_counter = 0
|
|
23
|
+
@last_checkpoint_name = nil
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
# //checkpoint [name]
|
|
28
|
+
# Creates a named checkpoint of the current conversation state.
|
|
29
|
+
# If no name is provided, uses an auto-incrementing number.
|
|
30
|
+
def self.checkpoint(args, _unused = nil)
|
|
31
|
+
name = args.empty? ? nil : args.join(' ').strip
|
|
32
|
+
|
|
33
|
+
if name.nil? || name.empty?
|
|
34
|
+
self.checkpoint_counter += 1
|
|
35
|
+
name = checkpoint_counter.to_s
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
chats = get_chats
|
|
39
|
+
return "Error: No active chat sessions found." if chats.nil? || chats.empty?
|
|
40
|
+
|
|
41
|
+
# Deep copy messages from all chats
|
|
42
|
+
first_chat_messages = chats.values.first&.messages || []
|
|
43
|
+
checkpoint_store[name] = {
|
|
44
|
+
messages: chats.transform_values { |chat|
|
|
45
|
+
chat.messages.map { |msg| deep_copy_message(msg) }
|
|
46
|
+
},
|
|
47
|
+
position: first_chat_messages.size,
|
|
48
|
+
created_at: Time.now,
|
|
49
|
+
topic_preview: extract_last_user_message(first_chat_messages)
|
|
50
|
+
}
|
|
51
|
+
self.last_checkpoint_name = name
|
|
52
|
+
|
|
53
|
+
puts "Checkpoint '#{name}' created at position #{checkpoint_store[name][:position]}."
|
|
54
|
+
""
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
# //restore [name]
|
|
58
|
+
# Restores the conversation state to a previously saved checkpoint.
|
|
59
|
+
# If no name is provided, restores to the previous checkpoint (one step back).
|
|
60
|
+
def self.restore(args, _unused = nil)
|
|
61
|
+
name = args.empty? ? nil : args.join(' ').strip
|
|
62
|
+
|
|
63
|
+
if name.nil? || name.empty?
|
|
64
|
+
# Find the previous checkpoint (second-to-last by position)
|
|
65
|
+
name = find_previous_checkpoint
|
|
66
|
+
if name.nil?
|
|
67
|
+
return "Error: No previous checkpoint to restore to."
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
unless checkpoint_store.key?(name)
|
|
72
|
+
available = checkpoint_names.empty? ? "none" : checkpoint_names.join(', ')
|
|
73
|
+
return "Error: Checkpoint '#{name}' not found. Available: #{available}"
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
checkpoint_data = checkpoint_store[name]
|
|
77
|
+
chats = get_chats
|
|
78
|
+
|
|
79
|
+
return "Error: No active chat sessions found." if chats.nil? || chats.empty?
|
|
80
|
+
|
|
81
|
+
# Restore messages to each chat
|
|
82
|
+
checkpoint_data[:messages].each do |model_id, saved_messages|
|
|
83
|
+
chat = chats[model_id]
|
|
84
|
+
next unless chat
|
|
85
|
+
|
|
86
|
+
# Replace the chat's messages with the saved ones
|
|
87
|
+
restored_messages = saved_messages.map { |msg| deep_copy_message(msg) }
|
|
88
|
+
chat.instance_variable_set(:@messages, restored_messages)
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
# Remove checkpoints that are now invalid (position > restored position)
|
|
92
|
+
# because they reference conversation states that no longer exist
|
|
93
|
+
restored_position = checkpoint_data[:position]
|
|
94
|
+
removed_count = remove_invalid_checkpoints(restored_position)
|
|
95
|
+
|
|
96
|
+
# Update last_checkpoint_name to this checkpoint
|
|
97
|
+
self.last_checkpoint_name = name
|
|
98
|
+
|
|
99
|
+
msg = "Context restored to checkpoint '#{name}' (position #{restored_position})."
|
|
100
|
+
msg += " Removed #{removed_count} checkpoint(s) that were beyond this position." if removed_count > 0
|
|
101
|
+
msg
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
# //clear
|
|
105
|
+
# Clears the conversation context, optionally keeping the system prompt.
|
|
106
|
+
def self.clear(args, _unused = nil)
|
|
107
|
+
keep_system = !args.include?('--all')
|
|
108
|
+
|
|
109
|
+
chats = get_chats
|
|
110
|
+
return "Error: No active chat sessions found." if chats.nil? || chats.empty?
|
|
111
|
+
|
|
112
|
+
chats.each do |_model_id, chat|
|
|
113
|
+
if keep_system
|
|
114
|
+
system_msg = chat.messages.find { |m| m.role == :system }
|
|
115
|
+
chat.instance_variable_set(:@messages, [])
|
|
116
|
+
chat.add_message(system_msg) if system_msg
|
|
117
|
+
else
|
|
118
|
+
chat.instance_variable_set(:@messages, [])
|
|
119
|
+
end
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
# Clear all checkpoints
|
|
123
|
+
checkpoint_store.clear
|
|
124
|
+
self.checkpoint_counter = 0
|
|
125
|
+
self.last_checkpoint_name = nil
|
|
126
|
+
|
|
127
|
+
"Chat context cleared."
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
# //review
|
|
131
|
+
# Displays the current conversation context with checkpoint markers.
|
|
132
|
+
def self.review(args, _unused = nil)
|
|
133
|
+
chats = get_chats
|
|
134
|
+
return "Error: No active chat sessions found." if chats.nil? || chats.empty?
|
|
135
|
+
|
|
136
|
+
# For multi-model, show first chat's messages (they should be similar for user messages)
|
|
137
|
+
first_chat = chats.values.first
|
|
138
|
+
messages = first_chat&.messages || []
|
|
139
|
+
|
|
140
|
+
puts "\n=== Chat Context (RubyLLM) ==="
|
|
141
|
+
puts "Total messages: #{messages.size}"
|
|
142
|
+
puts "Models: #{chats.keys.join(', ')}"
|
|
143
|
+
puts "Checkpoints: #{checkpoint_names.join(', ')}" if checkpoint_names.any?
|
|
144
|
+
puts
|
|
145
|
+
|
|
146
|
+
positions = checkpoint_positions
|
|
147
|
+
|
|
148
|
+
messages.each_with_index do |msg, index|
|
|
149
|
+
# Show checkpoint marker if one exists at this position
|
|
150
|
+
if positions[index]
|
|
151
|
+
puts "📍 [Checkpoint: #{positions[index].join(', ')}]"
|
|
152
|
+
puts "-" * 40
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
role = msg.role.to_s.capitalize
|
|
156
|
+
content = format_message_content(msg)
|
|
157
|
+
|
|
158
|
+
puts "#{index + 1}. [#{role}]: #{content}"
|
|
159
|
+
puts
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
# Check for checkpoint at the end
|
|
163
|
+
if positions[messages.size]
|
|
164
|
+
puts "📍 [Checkpoint: #{positions[messages.size].join(', ')}]"
|
|
165
|
+
puts "-" * 40
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
puts "=== End of Context ==="
|
|
169
|
+
""
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
# //checkpoints
|
|
173
|
+
# Lists all available checkpoints with their details.
|
|
174
|
+
def self.checkpoints_list(args, _unused = nil)
|
|
175
|
+
if checkpoint_store.empty?
|
|
176
|
+
puts "No checkpoints available."
|
|
177
|
+
return ""
|
|
178
|
+
end
|
|
179
|
+
|
|
180
|
+
puts "\n=== Available Checkpoints ==="
|
|
181
|
+
checkpoint_store.each do |name, data|
|
|
182
|
+
created = data[:created_at]&.strftime('%H:%M:%S') || 'unknown'
|
|
183
|
+
puts " #{name}: position #{data[:position]}, created #{created}"
|
|
184
|
+
if data[:topic_preview] && !data[:topic_preview].empty?
|
|
185
|
+
puts " → \"#{data[:topic_preview]}\""
|
|
186
|
+
end
|
|
187
|
+
end
|
|
188
|
+
puts "=== End of Checkpoints ==="
|
|
189
|
+
""
|
|
190
|
+
end
|
|
191
|
+
|
|
192
|
+
# Helper methods
|
|
193
|
+
def self.checkpoint_names
|
|
194
|
+
checkpoint_store.keys
|
|
195
|
+
end
|
|
196
|
+
|
|
197
|
+
def self.checkpoint_positions
|
|
198
|
+
positions = {}
|
|
199
|
+
checkpoint_store.each do |name, data|
|
|
200
|
+
pos = data[:position]
|
|
201
|
+
positions[pos] ||= []
|
|
202
|
+
positions[pos] << name
|
|
203
|
+
end
|
|
204
|
+
positions
|
|
205
|
+
end
|
|
206
|
+
|
|
207
|
+
# Remove checkpoints with position > the given position
|
|
208
|
+
# Returns the count of removed checkpoints
|
|
209
|
+
def self.remove_invalid_checkpoints(max_position)
|
|
210
|
+
invalid_names = checkpoint_store.select { |_name, data| data[:position] > max_position }.keys
|
|
211
|
+
invalid_names.each { |name| checkpoint_store.delete(name) }
|
|
212
|
+
invalid_names.size
|
|
213
|
+
end
|
|
214
|
+
|
|
215
|
+
# Find the previous checkpoint (second-to-last by position)
|
|
216
|
+
# This is used when //restore is called without a name
|
|
217
|
+
def self.find_previous_checkpoint
|
|
218
|
+
return nil if checkpoint_store.size < 2
|
|
219
|
+
|
|
220
|
+
# Sort checkpoints by position descending, return the second one
|
|
221
|
+
sorted = checkpoint_store.sort_by { |_name, data| -data[:position] }
|
|
222
|
+
sorted[1]&.first # Return the name of the second-to-last checkpoint
|
|
223
|
+
end
|
|
224
|
+
|
|
225
|
+
private
|
|
226
|
+
|
|
227
|
+
def self.get_chats
|
|
228
|
+
return nil unless AIA.client.respond_to?(:chats)
|
|
229
|
+
|
|
230
|
+
AIA.client.chats
|
|
231
|
+
end
|
|
232
|
+
|
|
233
|
+
def self.deep_copy_message(msg)
|
|
234
|
+
RubyLLM::Message.new(
|
|
235
|
+
role: msg.role,
|
|
236
|
+
content: msg.content,
|
|
237
|
+
tool_calls: msg.tool_calls&.transform_values { |tc| tc.dup rescue tc },
|
|
238
|
+
tool_call_id: msg.tool_call_id,
|
|
239
|
+
input_tokens: msg.input_tokens,
|
|
240
|
+
output_tokens: msg.output_tokens,
|
|
241
|
+
model_id: msg.model_id
|
|
242
|
+
)
|
|
243
|
+
end
|
|
244
|
+
|
|
245
|
+
def self.format_message_content(msg)
|
|
246
|
+
if msg.tool_call?
|
|
247
|
+
tool_names = msg.tool_calls.values.map(&:name).join(', ')
|
|
248
|
+
"[Tool calls: #{tool_names}]"
|
|
249
|
+
elsif msg.tool_result?
|
|
250
|
+
result_preview = msg.content.to_s[0..50]
|
|
251
|
+
"[Tool result for: #{msg.tool_call_id}] #{result_preview}..."
|
|
252
|
+
else
|
|
253
|
+
content = msg.content.to_s
|
|
254
|
+
content.length > 150 ? "#{content[0..147]}..." : content
|
|
255
|
+
end
|
|
256
|
+
end
|
|
257
|
+
|
|
258
|
+
# Extract a preview of the last user message for checkpoint context
|
|
259
|
+
def self.extract_last_user_message(messages, max_length: 70)
|
|
260
|
+
return "" if messages.nil? || messages.empty?
|
|
261
|
+
|
|
262
|
+
# Find the last user message (not system, not assistant, not tool)
|
|
263
|
+
last_user_msg = messages.reverse.find { |msg| msg.role == :user }
|
|
264
|
+
return "" unless last_user_msg
|
|
265
|
+
|
|
266
|
+
content = last_user_msg.content.to_s.strip
|
|
267
|
+
# Collapse whitespace and truncate
|
|
268
|
+
content = content.gsub(/\s+/, ' ')
|
|
269
|
+
content.length > max_length ? "#{content[0..max_length - 4]}..." : content
|
|
270
|
+
end
|
|
271
|
+
|
|
272
|
+
# Aliases
|
|
273
|
+
class << self
|
|
274
|
+
alias_method :ckp, :checkpoint
|
|
275
|
+
alias_method :cp, :checkpoint
|
|
276
|
+
alias_method :context, :review
|
|
277
|
+
# Route //checkpoints directive to checkpoints_list
|
|
278
|
+
# (safe now that we renamed attr_accessor to checkpoint_store)
|
|
279
|
+
alias_method :checkpoints, :checkpoints_list
|
|
280
|
+
end
|
|
281
|
+
end
|
|
282
|
+
end
|
|
283
|
+
end
|
|
@@ -7,12 +7,17 @@ module AIA
|
|
|
7
7
|
args = Array(args)
|
|
8
8
|
|
|
9
9
|
if args.empty?
|
|
10
|
-
ap AIA.config
|
|
10
|
+
ap AIA.config.to_h
|
|
11
11
|
""
|
|
12
12
|
elsif args.length == 1
|
|
13
13
|
config_item = args.first
|
|
14
|
-
local_cfg =
|
|
15
|
-
|
|
14
|
+
local_cfg = {}
|
|
15
|
+
# Use method-based access for Anyway::Config
|
|
16
|
+
if AIA.config.respond_to?(config_item)
|
|
17
|
+
local_cfg[config_item] = AIA.config.send(config_item)
|
|
18
|
+
else
|
|
19
|
+
local_cfg[config_item] = nil
|
|
20
|
+
end
|
|
16
21
|
ap local_cfg
|
|
17
22
|
""
|
|
18
23
|
else
|
|
@@ -24,7 +29,13 @@ module AIA
|
|
|
24
29
|
new_value = %w[true t yes y on 1 yea yeah yep yup].include?(new_value.downcase)
|
|
25
30
|
end
|
|
26
31
|
|
|
27
|
-
|
|
32
|
+
# Use method-based setter for Anyway::Config
|
|
33
|
+
setter = "#{config_item}="
|
|
34
|
+
if AIA.config.respond_to?(setter)
|
|
35
|
+
AIA.config.send(setter, new_value)
|
|
36
|
+
else
|
|
37
|
+
puts "Warning: Unknown config option '#{config_item}'"
|
|
38
|
+
end
|
|
28
39
|
""
|
|
29
40
|
end
|
|
30
41
|
end
|
|
@@ -33,29 +44,32 @@ module AIA
|
|
|
33
44
|
if args.empty?
|
|
34
45
|
# Display details for all configured models
|
|
35
46
|
puts
|
|
36
|
-
models =
|
|
47
|
+
models = AIA.config.models
|
|
37
48
|
|
|
38
49
|
if models.size == 1
|
|
39
50
|
puts "Current Model:"
|
|
40
51
|
puts "=============="
|
|
41
|
-
puts AIA.
|
|
52
|
+
puts AIA.client.model.to_h.pretty_inspect
|
|
42
53
|
else
|
|
43
54
|
puts "Multi-Model Configuration:"
|
|
44
55
|
puts "=========================="
|
|
45
56
|
puts "Model count: #{models.size}"
|
|
46
|
-
|
|
47
|
-
puts "
|
|
57
|
+
first_model = models.first.respond_to?(:name) ? models.first.name : models.first.to_s
|
|
58
|
+
puts "Primary model: #{first_model} (used for consensus when --consensus flag is enabled)"
|
|
59
|
+
consensus = AIA.config.flags.consensus
|
|
60
|
+
puts "Consensus mode: #{consensus.nil? ? 'auto-detect (disabled by default)' : consensus}"
|
|
48
61
|
puts
|
|
49
62
|
puts "Model Details:"
|
|
50
63
|
puts "-" * 50
|
|
51
64
|
|
|
52
|
-
models.each_with_index do |
|
|
65
|
+
models.each_with_index do |model_spec, index|
|
|
66
|
+
model_name = model_spec.respond_to?(:name) ? model_spec.name : model_spec.to_s
|
|
53
67
|
puts "#{index + 1}. #{model_name}#{index == 0 ? ' (primary)' : ''}"
|
|
54
68
|
|
|
55
69
|
# Try to get model details if available
|
|
56
70
|
begin
|
|
57
71
|
# Access the model details from RubyLLM's model registry
|
|
58
|
-
model_info = RubyLLM::Models.find(
|
|
72
|
+
model_info = RubyLLM::Models.find(model_name)
|
|
59
73
|
if model_info
|
|
60
74
|
puts " Provider: #{model_info.provider || 'Unknown'}"
|
|
61
75
|
puts " Context window: #{model_info.context_window || 'Unknown'}"
|
|
@@ -88,100 +102,15 @@ module AIA
|
|
|
88
102
|
send(:config, args.prepend('top_p'), context_manager)
|
|
89
103
|
end
|
|
90
104
|
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
end
|
|
95
|
-
|
|
96
|
-
context_manager.clear_context
|
|
97
|
-
''
|
|
98
|
-
end
|
|
99
|
-
|
|
100
|
-
def self.review(args, context_manager = nil)
|
|
101
|
-
return "Error: Context manager not available for //review directive." if context_manager.nil?
|
|
102
|
-
|
|
103
|
-
context = context_manager.get_context
|
|
104
|
-
checkpoint_positions = context_manager.checkpoint_positions
|
|
105
|
-
|
|
106
|
-
# Display context with checkpoint markers
|
|
107
|
-
puts "\n=== Chat Context ==="
|
|
108
|
-
puts "Total messages: #{context.size}"
|
|
109
|
-
|
|
110
|
-
if checkpoint_positions.any?
|
|
111
|
-
puts "Checkpoints: #{context_manager.checkpoint_names.join(', ')}"
|
|
112
|
-
end
|
|
113
|
-
|
|
114
|
-
puts "\n"
|
|
115
|
-
|
|
116
|
-
context.each_with_index do |message, index|
|
|
117
|
-
# Check if there's a checkpoint at this position
|
|
118
|
-
if checkpoint_positions[index]
|
|
119
|
-
checkpoint_names = checkpoint_positions[index].join(', ')
|
|
120
|
-
puts "📍 [Checkpoint: #{checkpoint_names}]"
|
|
121
|
-
puts "-" * 40
|
|
122
|
-
end
|
|
123
|
-
|
|
124
|
-
# Display the message
|
|
125
|
-
role_display = message[:role].capitalize
|
|
126
|
-
content_preview = message[:content].to_s
|
|
127
|
-
|
|
128
|
-
# Truncate long content for display
|
|
129
|
-
if content_preview.length > 200
|
|
130
|
-
content_preview = content_preview[0..197] + "..."
|
|
131
|
-
end
|
|
132
|
-
|
|
133
|
-
puts "#{index + 1}. [#{role_display}]: #{content_preview}"
|
|
134
|
-
puts ""
|
|
135
|
-
end
|
|
136
|
-
|
|
137
|
-
# Check if there's a checkpoint at the end (after all messages)
|
|
138
|
-
if checkpoint_positions[context.size]
|
|
139
|
-
checkpoint_names = checkpoint_positions[context.size].join(', ')
|
|
140
|
-
puts "📍 [Checkpoint: #{checkpoint_names}]"
|
|
141
|
-
puts "-" * 40
|
|
142
|
-
end
|
|
143
|
-
|
|
144
|
-
puts "=== End of Context ==="
|
|
145
|
-
''
|
|
146
|
-
end
|
|
147
|
-
|
|
148
|
-
def self.checkpoint(args, context_manager = nil)
|
|
149
|
-
if context_manager.nil?
|
|
150
|
-
return "Error: Context manager not available for //checkpoint directive."
|
|
151
|
-
end
|
|
152
|
-
|
|
153
|
-
name = args.empty? ? nil : args.join(' ').strip
|
|
154
|
-
checkpoint_name = context_manager.create_checkpoint(name: name)
|
|
155
|
-
puts "Checkpoint '#{checkpoint_name}' created."
|
|
156
|
-
""
|
|
157
|
-
end
|
|
158
|
-
|
|
159
|
-
def self.restore(args, context_manager = nil)
|
|
160
|
-
if context_manager.nil?
|
|
161
|
-
return "Error: Context manager not available for //restore directive."
|
|
162
|
-
end
|
|
163
|
-
|
|
164
|
-
name = args.empty? ? nil : args.join(' ').strip
|
|
165
|
-
|
|
166
|
-
if context_manager.restore_checkpoint(name: name)
|
|
167
|
-
restored_name = name || context_manager.checkpoint_names.last
|
|
168
|
-
"Context restored to checkpoint '#{restored_name}'."
|
|
169
|
-
else
|
|
170
|
-
if name
|
|
171
|
-
"Error: Checkpoint '#{name}' not found. Available checkpoints: #{context_manager.checkpoint_names.join(', ')}"
|
|
172
|
-
else
|
|
173
|
-
"Error: No checkpoints available to restore."
|
|
174
|
-
end
|
|
175
|
-
end
|
|
176
|
-
end
|
|
105
|
+
# NOTE: clear, review, checkpoint, and restore directives have been moved to
|
|
106
|
+
# lib/aia/directives/checkpoint.rb which uses RubyLLM's Chat.@messages
|
|
107
|
+
# as the source of truth for conversation history.
|
|
177
108
|
|
|
178
109
|
# Set up aliases - these work on the module's singleton class
|
|
179
110
|
class << self
|
|
180
111
|
alias_method :cfg, :config
|
|
181
112
|
alias_method :temp, :temperature
|
|
182
113
|
alias_method :topp, :top_p
|
|
183
|
-
alias_method :context, :review
|
|
184
|
-
alias_method :ckp, :checkpoint
|
|
185
114
|
end
|
|
186
115
|
end
|
|
187
116
|
end
|
|
@@ -31,12 +31,11 @@ module AIA
|
|
|
31
31
|
|
|
32
32
|
def self.available_models(args = nil, context_manager = nil)
|
|
33
33
|
# Check if we're using a local provider
|
|
34
|
-
current_models = AIA.config.
|
|
35
|
-
current_models = [current_models] if current_models.is_a?(String)
|
|
34
|
+
current_models = AIA.config.models
|
|
36
35
|
|
|
37
|
-
# Extract model names (handles
|
|
36
|
+
# Extract model names (handles ModelSpec objects)
|
|
38
37
|
model_names = current_models.map do |m|
|
|
39
|
-
m.
|
|
38
|
+
m.respond_to?(:name) ? m.name : m.to_s
|
|
40
39
|
end
|
|
41
40
|
|
|
42
41
|
using_local_provider = model_names.any? { |m| m.start_with?('ollama/', 'lms/') }
|
|
@@ -231,9 +230,10 @@ module AIA
|
|
|
231
230
|
'review' => 'Display the current conversation context with checkpoint markers',
|
|
232
231
|
'checkpoint' => 'Create a named checkpoint of the current context',
|
|
233
232
|
'restore' => 'Restore context to a previous checkpoint',
|
|
233
|
+
'checkpoints_list' => 'List all available checkpoints',
|
|
234
234
|
|
|
235
235
|
# Utility directives
|
|
236
|
-
'tools' => 'List available tools',
|
|
236
|
+
'tools' => 'List available tools (optional filter by name substring)',
|
|
237
237
|
'next' => 'Set the next prompt in the sequence',
|
|
238
238
|
'pipeline' => 'Set or view the prompt workflow sequence',
|
|
239
239
|
'terse' => 'Add instruction for concise responses',
|
|
@@ -261,6 +261,7 @@ module AIA
|
|
|
261
261
|
'temp' => nil, # alias for temperature
|
|
262
262
|
'topp' => nil, # alias for top_p
|
|
263
263
|
'context' => nil, # alias for review
|
|
264
|
+
'ckp' => nil, # alias for checkpoint
|
|
264
265
|
'cp' => nil, # alias for checkpoint
|
|
265
266
|
'workflow' => nil, # alias for pipeline
|
|
266
267
|
'rb' => nil, # alias for ruby
|
|
@@ -282,13 +283,18 @@ module AIA
|
|
|
282
283
|
AIA::Directives::Utility,
|
|
283
284
|
AIA::Directives::Configuration,
|
|
284
285
|
AIA::Directives::Execution,
|
|
285
|
-
AIA::Directives::Models
|
|
286
|
+
AIA::Directives::Models,
|
|
287
|
+
AIA::Directives::Checkpoint
|
|
286
288
|
]
|
|
287
289
|
|
|
288
290
|
all_directives = {}
|
|
289
291
|
excluded_methods = ['run', 'initialize', 'private?', 'descriptions', 'aliases', 'build_aliases',
|
|
290
292
|
'desc', 'method_added', 'register_directive_module', 'process',
|
|
291
|
-
'directive?', 'prefix_size'
|
|
293
|
+
'directive?', 'prefix_size', 'reset!', 'checkpoint_names',
|
|
294
|
+
'checkpoint_positions', 'get_chats', 'deep_copy_message',
|
|
295
|
+
'format_message_content', 'checkpoints', 'checkpoint_counter',
|
|
296
|
+
'last_checkpoint_name', 'checkpoints=', 'checkpoint_counter=',
|
|
297
|
+
'last_checkpoint_name=']
|
|
292
298
|
|
|
293
299
|
# Collect directives from all modules
|
|
294
300
|
all_modules.each do |mod|
|
|
@@ -314,7 +320,7 @@ module AIA
|
|
|
314
320
|
'temperature' => ['temp'],
|
|
315
321
|
'top_p' => ['topp'],
|
|
316
322
|
'review' => ['context'],
|
|
317
|
-
'checkpoint' => ['cp'],
|
|
323
|
+
'checkpoint' => ['ckp', 'cp'],
|
|
318
324
|
'pipeline' => ['workflow'],
|
|
319
325
|
'ruby' => ['rb'],
|
|
320
326
|
'shell' => ['sh'],
|
|
@@ -333,7 +339,7 @@ module AIA
|
|
|
333
339
|
|
|
334
340
|
# Sort and display directives by category
|
|
335
341
|
categories = {
|
|
336
|
-
'Configuration' => ['config', 'model', 'temperature', 'top_p', 'clear', 'review', 'checkpoint', 'restore'],
|
|
342
|
+
'Configuration' => ['config', 'model', 'temperature', 'top_p', 'clear', 'review', 'checkpoint', 'restore', 'checkpoints_list'],
|
|
337
343
|
'Utility' => ['tools', 'next', 'pipeline', 'terse', 'robot', 'help'],
|
|
338
344
|
'Execution' => ['ruby', 'shell', 'say'],
|
|
339
345
|
'Web & Files' => ['webpage', 'include'],
|
|
@@ -5,6 +5,7 @@ require_relative 'utility'
|
|
|
5
5
|
require_relative 'configuration'
|
|
6
6
|
require_relative 'execution'
|
|
7
7
|
require_relative 'models'
|
|
8
|
+
require_relative 'checkpoint'
|
|
8
9
|
|
|
9
10
|
module AIA
|
|
10
11
|
module Directives
|
|
@@ -115,6 +116,7 @@ module AIA
|
|
|
115
116
|
register_directive_module(Configuration)
|
|
116
117
|
register_directive_module(Execution)
|
|
117
118
|
register_directive_module(Models)
|
|
119
|
+
register_directive_module(Checkpoint)
|
|
118
120
|
end
|
|
119
121
|
end
|
|
120
122
|
end
|
|
@@ -12,19 +12,35 @@ module AIA
|
|
|
12
12
|
indent = 4
|
|
13
13
|
spaces = " " * indent
|
|
14
14
|
width = TTY::Screen.width - indent - 2
|
|
15
|
+
filter = args.first&.downcase
|
|
15
16
|
|
|
16
|
-
|
|
17
|
+
loaded_tools = AIA.config.loaded_tools || []
|
|
18
|
+
if loaded_tools.empty?
|
|
17
19
|
puts "No tools are available"
|
|
18
20
|
else
|
|
19
|
-
|
|
20
|
-
puts "Available Tools"
|
|
21
|
-
puts "==============="
|
|
21
|
+
tools_to_display = loaded_tools
|
|
22
22
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
23
|
+
if filter
|
|
24
|
+
tools_to_display = tools_to_display.select do |tool|
|
|
25
|
+
name = tool.respond_to?(:name) ? tool.name : tool.class.name
|
|
26
|
+
name.downcase.include?(filter)
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
if tools_to_display.empty?
|
|
31
|
+
puts "No tools match the filter: #{args.first}"
|
|
32
|
+
else
|
|
33
|
+
puts
|
|
34
|
+
header = filter ? "Available Tools (filtered by '#{args.first}')" : "Available Tools"
|
|
35
|
+
puts header
|
|
36
|
+
puts "=" * header.length
|
|
37
|
+
|
|
38
|
+
tools_to_display.each do |tool|
|
|
39
|
+
name = tool.respond_to?(:name) ? tool.name : tool.class.name
|
|
40
|
+
puts "\n#{name}"
|
|
41
|
+
puts "-" * name.size
|
|
42
|
+
puts WordWrapper::MinimumRaggedness.new(width, tool.description).wrap.split("\n").map { |s| spaces + s + "\n" }.join
|
|
43
|
+
end
|
|
28
44
|
end
|
|
29
45
|
end
|
|
30
46
|
puts
|