console_agent 0.9.0 → 0.10.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 +15 -0
- data/README.md +6 -1
- data/lib/console_agent/executor.rb +22 -1
- data/lib/console_agent/providers/base.rb +16 -13
- data/lib/console_agent/repl.rb +171 -7
- data/lib/console_agent/tools/registry.rb +18 -0
- data/lib/console_agent/version.rb +1 -1
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: dc46d0592feb84b4d85481d1535dccbe417a4445593828424c12a84d96fcbc9c
|
|
4
|
+
data.tar.gz: 10fe29dc81cc425a498c6e7d6c6b82aaa586ec674c081ba3f7b5b1143b68df18
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 86760d6c3b7c4920fc2c01741be308fc3d3f133e264c8dc37cab6b1ab90e9b920a410d57c86d8f96e743396d6919735d7fad62ee584667c8ea177c4825a12d05
|
|
7
|
+
data.tar.gz: 6446b9b2af4803ccd860fd109484ef37de87850517ad117eb52892974a65a017c1b03188dfc1eb7f24aad3859749ce4f6d282220c8d5d78238e67cfdb7438def
|
data/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,21 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to this project will be documented in this file.
|
|
4
4
|
|
|
5
|
+
## [0.10.0]
|
|
6
|
+
|
|
7
|
+
- Add `/expand` command to view previous results
|
|
8
|
+
- Exclude previous output from context; add tool for LLM to retrieve it on demand
|
|
9
|
+
- Show summarized info per LLM call in `/debug`
|
|
10
|
+
|
|
11
|
+
## [0.9.0]
|
|
12
|
+
|
|
13
|
+
- Add `/system` and `/context` commands to inspect what is being sent
|
|
14
|
+
- Omit huge output from tool results
|
|
15
|
+
- Don't cancel code execution on incorrect prompt answers
|
|
16
|
+
- Preserve code blocks when compacting; require manual `/compact`
|
|
17
|
+
- Fix authentication when neither method was applied
|
|
18
|
+
- Remove prompt to upgrade model on excessive tool calls
|
|
19
|
+
|
|
5
20
|
## [0.8.0]
|
|
6
21
|
|
|
7
22
|
- Add authentication function support so host apps can avoid using basic auth
|
data/README.md
CHANGED
|
@@ -79,7 +79,10 @@ end
|
|
|
79
79
|
| `/usage` | Show token stats |
|
|
80
80
|
| `/cost` | Show per-model cost breakdown |
|
|
81
81
|
| `/think` | Upgrade to thinking model (Opus) for the rest of the session |
|
|
82
|
-
| `/debug` | Toggle
|
|
82
|
+
| `/debug` | Toggle debug summaries (context stats, cost per call) |
|
|
83
|
+
| `/expand <id>` | Show full omitted output |
|
|
84
|
+
| `/context` | Show conversation history as sent to the LLM |
|
|
85
|
+
| `/system` | Show the system prompt |
|
|
83
86
|
| `/name <label>` | Name the session for easy resume |
|
|
84
87
|
|
|
85
88
|
Prefix input with `>` to run Ruby directly (no LLM round-trip). The result is added to conversation context.
|
|
@@ -96,6 +99,8 @@ Say "think harder" in any query to auto-upgrade to the thinking model for that s
|
|
|
96
99
|
- **App guide** — `ai_init` generates a guide injected into every system prompt
|
|
97
100
|
- **Sessions** — name, list, and resume interactive conversations (`ai_setup` to enable)
|
|
98
101
|
- **History compaction** — `/compact` summarizes long conversations to reduce cost and latency
|
|
102
|
+
- **Output trimming** — older execution outputs are automatically replaced with references; the LLM can recall them on demand via `recall_output`, and you can `/expand <id>` to see them
|
|
103
|
+
- **Debug mode** — `/debug` shows context breakdown, token counts, and per-call cost estimates before and after each LLM call
|
|
99
104
|
|
|
100
105
|
## Configuration
|
|
101
106
|
|
|
@@ -48,6 +48,10 @@ module ConsoleAgent
|
|
|
48
48
|
|
|
49
49
|
def initialize(binding_context)
|
|
50
50
|
@binding_context = binding_context
|
|
51
|
+
@omitted_outputs = {}
|
|
52
|
+
@omitted_counter = 0
|
|
53
|
+
@output_store = {}
|
|
54
|
+
@output_counter = 0
|
|
51
55
|
end
|
|
52
56
|
|
|
53
57
|
def extract_code(response)
|
|
@@ -107,6 +111,20 @@ module ConsoleAgent
|
|
|
107
111
|
@last_output
|
|
108
112
|
end
|
|
109
113
|
|
|
114
|
+
def expand_output(id)
|
|
115
|
+
@omitted_outputs[id]
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
def store_output(content)
|
|
119
|
+
@output_counter += 1
|
|
120
|
+
@output_store[@output_counter] = content
|
|
121
|
+
@output_counter
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
def recall_output(id)
|
|
125
|
+
@output_store[id]
|
|
126
|
+
end
|
|
127
|
+
|
|
110
128
|
def last_answer
|
|
111
129
|
@last_answer
|
|
112
130
|
end
|
|
@@ -185,7 +203,10 @@ module ConsoleAgent
|
|
|
185
203
|
parts = []
|
|
186
204
|
parts << "#{omitted_lines} lines" if omitted_lines > 0
|
|
187
205
|
parts << "#{omitted_chars} chars" if omitted_chars > 0
|
|
188
|
-
|
|
206
|
+
|
|
207
|
+
@omitted_counter += 1
|
|
208
|
+
@omitted_outputs[@omitted_counter] = full
|
|
209
|
+
$stdout.puts colorize(" (omitting #{parts.join(', ')}) /expand #{@omitted_counter} to see all", :yellow)
|
|
189
210
|
end
|
|
190
211
|
end
|
|
191
212
|
|
|
@@ -41,24 +41,27 @@ module ConsoleAgent
|
|
|
41
41
|
def debug_request(url, body)
|
|
42
42
|
return unless config.debug
|
|
43
43
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
44
|
+
parsed = body.is_a?(String) ? (JSON.parse(body) rescue nil) : body
|
|
45
|
+
if parsed
|
|
46
|
+
# Support both symbol and string keys
|
|
47
|
+
model = parsed[:model] || parsed['model']
|
|
48
|
+
msgs = parsed[:messages] || parsed['messages']
|
|
49
|
+
sys = parsed[:system] || parsed['system']
|
|
50
|
+
tools = parsed[:tools] || parsed['tools']
|
|
51
|
+
$stderr.puts "\e[33m[debug] POST #{url} | model: #{model} | #{msgs&.length || 0} msgs | system: #{sys.to_s.length} chars | #{tools&.length || 0} tools\e[0m"
|
|
52
|
+
else
|
|
53
|
+
$stderr.puts "\e[33m[debug] POST #{url}\e[0m"
|
|
54
|
+
end
|
|
51
55
|
end
|
|
52
56
|
|
|
53
57
|
def debug_response(body)
|
|
54
58
|
return unless config.debug
|
|
55
59
|
|
|
56
|
-
|
|
57
|
-
parsed
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
$stderr.puts "\e[36m[debug] #{body}\e[0m"
|
|
60
|
+
parsed = body.is_a?(String) ? (JSON.parse(body) rescue nil) : body
|
|
61
|
+
if parsed && parsed['usage']
|
|
62
|
+
u = parsed['usage']
|
|
63
|
+
$stderr.puts "\e[36m[debug] response: #{parsed['stop_reason']} | in: #{u['input_tokens']} out: #{u['output_tokens']}\e[0m"
|
|
64
|
+
end
|
|
62
65
|
end
|
|
63
66
|
|
|
64
67
|
def parse_response(response)
|
data/lib/console_agent/repl.rb
CHANGED
|
@@ -285,6 +285,17 @@ module ConsoleAgent
|
|
|
285
285
|
next
|
|
286
286
|
end
|
|
287
287
|
|
|
288
|
+
if input.start_with?('/expand')
|
|
289
|
+
expand_id = input.sub('/expand', '').strip.to_i
|
|
290
|
+
full_output = @executor.expand_output(expand_id)
|
|
291
|
+
if full_output
|
|
292
|
+
@interactive_old_stdout.puts full_output
|
|
293
|
+
else
|
|
294
|
+
@interactive_old_stdout.puts "\e[33mNo omitted output with id #{expand_id}\e[0m"
|
|
295
|
+
end
|
|
296
|
+
next
|
|
297
|
+
end
|
|
298
|
+
|
|
288
299
|
if input == '/think'
|
|
289
300
|
upgrade_to_thinking_model
|
|
290
301
|
next
|
|
@@ -326,7 +337,8 @@ module ConsoleAgent
|
|
|
326
337
|
|
|
327
338
|
context_msg = "User directly executed code: `#{raw_code}`"
|
|
328
339
|
context_msg += "\n#{result_str}" unless output_parts.empty?
|
|
329
|
-
|
|
340
|
+
output_id = output_parts.empty? ? nil : @executor.store_output(result_str)
|
|
341
|
+
@history << { role: :user, content: context_msg, output_id: output_id }
|
|
330
342
|
|
|
331
343
|
@interactive_query ||= input
|
|
332
344
|
@last_interactive_code = raw_code
|
|
@@ -459,7 +471,8 @@ module ConsoleAgent
|
|
|
459
471
|
unless output_parts.empty?
|
|
460
472
|
result_str = output_parts.join("\n\n")
|
|
461
473
|
result_str = result_str[0..1000] + '...' if result_str.length > 1000
|
|
462
|
-
|
|
474
|
+
output_id = @executor.store_output(result_str)
|
|
475
|
+
@history << { role: :user, content: "Code was executed. #{result_str}", output_id: output_id }
|
|
463
476
|
end
|
|
464
477
|
|
|
465
478
|
:success
|
|
@@ -547,6 +560,10 @@ module ConsoleAgent
|
|
|
547
560
|
prompt.strip
|
|
548
561
|
end
|
|
549
562
|
|
|
563
|
+
# Number of most recent execution outputs to keep in full in the conversation.
|
|
564
|
+
# Older outputs are replaced with a short reference the LLM can recall via tool.
|
|
565
|
+
RECENT_OUTPUTS_TO_KEEP = 2
|
|
566
|
+
|
|
550
567
|
def send_query(query, conversation: nil)
|
|
551
568
|
ConsoleAgent.configuration.validate!
|
|
552
569
|
|
|
@@ -556,6 +573,8 @@ module ConsoleAgent
|
|
|
556
573
|
[{ role: :user, content: query }]
|
|
557
574
|
end
|
|
558
575
|
|
|
576
|
+
messages = trim_old_outputs(messages) if conversation
|
|
577
|
+
|
|
559
578
|
send_query_with_tools(messages)
|
|
560
579
|
end
|
|
561
580
|
|
|
@@ -586,6 +605,10 @@ module ConsoleAgent
|
|
|
586
605
|
$stdout.puts "\e[2m #{llm_status(round, messages, total_input, last_thinking, last_tool_names)}\e[0m"
|
|
587
606
|
end
|
|
588
607
|
|
|
608
|
+
if ConsoleAgent.configuration.debug
|
|
609
|
+
debug_pre_call(round, messages, active_system_prompt, tools, total_input, total_output)
|
|
610
|
+
end
|
|
611
|
+
|
|
589
612
|
begin
|
|
590
613
|
result = with_escape_monitoring do
|
|
591
614
|
provider.chat_with_tools(messages, tools: tools, system_prompt: active_system_prompt)
|
|
@@ -596,6 +619,10 @@ module ConsoleAgent
|
|
|
596
619
|
total_input += result.input_tokens || 0
|
|
597
620
|
total_output += result.output_tokens || 0
|
|
598
621
|
|
|
622
|
+
if ConsoleAgent.configuration.debug
|
|
623
|
+
debug_post_call(round, result, @total_input_tokens + total_input, @total_output_tokens + total_output)
|
|
624
|
+
end
|
|
625
|
+
|
|
599
626
|
break unless result.tool_use?
|
|
600
627
|
|
|
601
628
|
# Buffer thinking text for display before next LLM call
|
|
@@ -624,10 +651,14 @@ module ConsoleAgent
|
|
|
624
651
|
end
|
|
625
652
|
|
|
626
653
|
if ConsoleAgent.configuration.debug
|
|
627
|
-
$stderr.puts "\e[35m[debug tool result
|
|
654
|
+
$stderr.puts "\e[35m[debug] tool result (#{tool_result.to_s.length} chars)\e[0m"
|
|
628
655
|
end
|
|
629
656
|
|
|
630
657
|
tool_msg = provider.format_tool_result(tc[:id], tool_result)
|
|
658
|
+
# Store large tool results so they can be trimmed from older conversation turns
|
|
659
|
+
if tool_result.to_s.length > 200
|
|
660
|
+
tool_msg[:output_id] = @executor.store_output(tool_result.to_s)
|
|
661
|
+
end
|
|
631
662
|
messages << tool_msg
|
|
632
663
|
new_messages << tool_msg
|
|
633
664
|
end
|
|
@@ -712,6 +743,89 @@ module ConsoleAgent
|
|
|
712
743
|
status
|
|
713
744
|
end
|
|
714
745
|
|
|
746
|
+
def debug_pre_call(round, messages, system_prompt, tools, total_input, total_output)
|
|
747
|
+
d = "\e[35m"
|
|
748
|
+
r = "\e[0m"
|
|
749
|
+
|
|
750
|
+
# Count message types
|
|
751
|
+
user_msgs = 0
|
|
752
|
+
assistant_msgs = 0
|
|
753
|
+
tool_result_msgs = 0
|
|
754
|
+
tool_use_msgs = 0
|
|
755
|
+
output_msgs = 0
|
|
756
|
+
omitted_msgs = 0
|
|
757
|
+
total_content_chars = system_prompt.to_s.length
|
|
758
|
+
|
|
759
|
+
messages.each do |msg|
|
|
760
|
+
content_str = msg[:content].is_a?(Array) ? msg[:content].to_s : msg[:content].to_s
|
|
761
|
+
total_content_chars += content_str.length
|
|
762
|
+
|
|
763
|
+
role = msg[:role].to_s
|
|
764
|
+
if role == 'tool'
|
|
765
|
+
tool_result_msgs += 1
|
|
766
|
+
elsif msg[:content].is_a?(Array)
|
|
767
|
+
# Anthropic format — check for tool_result or tool_use blocks
|
|
768
|
+
msg[:content].each do |block|
|
|
769
|
+
next unless block.is_a?(Hash)
|
|
770
|
+
if block['type'] == 'tool_result'
|
|
771
|
+
tool_result_msgs += 1
|
|
772
|
+
omitted_msgs += 1 if block['content'].to_s.include?('Output omitted')
|
|
773
|
+
elsif block['type'] == 'tool_use'
|
|
774
|
+
tool_use_msgs += 1
|
|
775
|
+
end
|
|
776
|
+
end
|
|
777
|
+
elsif role == 'user'
|
|
778
|
+
user_msgs += 1
|
|
779
|
+
if content_str.include?('Code was executed') || content_str.include?('directly executed code')
|
|
780
|
+
output_msgs += 1
|
|
781
|
+
omitted_msgs += 1 if content_str.include?('Output omitted')
|
|
782
|
+
end
|
|
783
|
+
elsif role == 'assistant'
|
|
784
|
+
assistant_msgs += 1
|
|
785
|
+
end
|
|
786
|
+
end
|
|
787
|
+
|
|
788
|
+
tool_count = tools.respond_to?(:definitions) ? tools.definitions.length : 0
|
|
789
|
+
|
|
790
|
+
$stderr.puts "#{d}[debug] ── LLM call ##{round + 1} ──#{r}"
|
|
791
|
+
$stderr.puts "#{d}[debug] system prompt: #{format_tokens(system_prompt.to_s.length)} chars#{r}"
|
|
792
|
+
$stderr.puts "#{d}[debug] messages: #{messages.length} (#{user_msgs} user, #{assistant_msgs} assistant, #{tool_result_msgs} tool results, #{tool_use_msgs} tool calls)#{r}"
|
|
793
|
+
$stderr.puts "#{d}[debug] execution outputs: #{output_msgs} (#{omitted_msgs} omitted)#{r}" if output_msgs > 0 || omitted_msgs > 0
|
|
794
|
+
$stderr.puts "#{d}[debug] tools provided: #{tool_count}#{r}"
|
|
795
|
+
$stderr.puts "#{d}[debug] est. content size: #{format_tokens(total_content_chars)} chars#{r}"
|
|
796
|
+
if total_input > 0 || total_output > 0
|
|
797
|
+
$stderr.puts "#{d}[debug] tokens so far: in: #{format_tokens(total_input)} | out: #{format_tokens(total_output)}#{r}"
|
|
798
|
+
end
|
|
799
|
+
end
|
|
800
|
+
|
|
801
|
+
def debug_post_call(round, result, total_input, total_output)
|
|
802
|
+
d = "\e[35m"
|
|
803
|
+
r = "\e[0m"
|
|
804
|
+
|
|
805
|
+
input_t = result.input_tokens || 0
|
|
806
|
+
output_t = result.output_tokens || 0
|
|
807
|
+
model = ConsoleAgent.configuration.resolved_model
|
|
808
|
+
pricing = Configuration::PRICING[model]
|
|
809
|
+
|
|
810
|
+
parts = ["in: #{format_tokens(input_t)}", "out: #{format_tokens(output_t)}"]
|
|
811
|
+
|
|
812
|
+
if pricing
|
|
813
|
+
cost = (input_t * pricing[:input]) + (output_t * pricing[:output])
|
|
814
|
+
session_cost = (total_input * pricing[:input]) + (total_output * pricing[:output])
|
|
815
|
+
parts << "~$#{'%.4f' % cost}"
|
|
816
|
+
$stderr.puts "#{d}[debug] ← response: #{parts.join(' | ')} (session: ~$#{'%.4f' % session_cost})#{r}"
|
|
817
|
+
else
|
|
818
|
+
$stderr.puts "#{d}[debug] ← response: #{parts.join(' | ')}#{r}"
|
|
819
|
+
end
|
|
820
|
+
|
|
821
|
+
if result.tool_use?
|
|
822
|
+
tool_names = result.tool_calls.map { |tc| tc[:name] }
|
|
823
|
+
$stderr.puts "#{d}[debug] tool calls: #{tool_names.join(', ')}#{r}"
|
|
824
|
+
else
|
|
825
|
+
$stderr.puts "#{d}[debug] stop reason: #{result.stop_reason}#{r}"
|
|
826
|
+
end
|
|
827
|
+
end
|
|
828
|
+
|
|
715
829
|
def format_tokens(count)
|
|
716
830
|
if count >= 1_000_000
|
|
717
831
|
"#{(count / 1_000_000.0).round(1)}M"
|
|
@@ -975,6 +1089,54 @@ module ConsoleAgent
|
|
|
975
1089
|
config.resolved_model == config.resolved_thinking_model
|
|
976
1090
|
end
|
|
977
1091
|
|
|
1092
|
+
# Replace older execution outputs with short references.
|
|
1093
|
+
# Keeps the last RECENT_OUTPUTS_TO_KEEP outputs in full.
|
|
1094
|
+
def trim_old_outputs(messages)
|
|
1095
|
+
# Find indices of messages with output_id (execution outputs and tool results)
|
|
1096
|
+
output_indices = messages.each_with_index
|
|
1097
|
+
.select { |m, _| m[:output_id] }
|
|
1098
|
+
.map { |_, i| i }
|
|
1099
|
+
|
|
1100
|
+
if output_indices.length <= RECENT_OUTPUTS_TO_KEEP
|
|
1101
|
+
return messages.map { |m| m.except(:output_id) }
|
|
1102
|
+
end
|
|
1103
|
+
|
|
1104
|
+
# Indices to trim (all except the most recent N)
|
|
1105
|
+
trim_indices = output_indices[0..-(RECENT_OUTPUTS_TO_KEEP + 1)]
|
|
1106
|
+
messages.each_with_index.map do |msg, i|
|
|
1107
|
+
if trim_indices.include?(i)
|
|
1108
|
+
trim_message(msg)
|
|
1109
|
+
else
|
|
1110
|
+
msg.except(:output_id)
|
|
1111
|
+
end
|
|
1112
|
+
end
|
|
1113
|
+
end
|
|
1114
|
+
|
|
1115
|
+
# Replace the content of a message with a short reference to the stored output.
|
|
1116
|
+
# Handles both regular messages and tool result messages (Anthropic/OpenAI formats).
|
|
1117
|
+
def trim_message(msg)
|
|
1118
|
+
ref = "[Output omitted — use recall_output tool with id #{msg[:output_id]} to retrieve]"
|
|
1119
|
+
|
|
1120
|
+
if msg[:content].is_a?(Array)
|
|
1121
|
+
# Anthropic tool_result format: [{ 'type' => 'tool_result', 'tool_use_id' => '...', 'content' => '...' }]
|
|
1122
|
+
trimmed_content = msg[:content].map do |block|
|
|
1123
|
+
if block.is_a?(Hash) && block['type'] == 'tool_result'
|
|
1124
|
+
block.merge('content' => ref)
|
|
1125
|
+
else
|
|
1126
|
+
block
|
|
1127
|
+
end
|
|
1128
|
+
end
|
|
1129
|
+
{ role: msg[:role], content: trimmed_content }
|
|
1130
|
+
elsif msg[:role].to_s == 'tool'
|
|
1131
|
+
# OpenAI tool result format
|
|
1132
|
+
msg.except(:output_id).merge(content: ref)
|
|
1133
|
+
else
|
|
1134
|
+
# Regular user message (code execution result)
|
|
1135
|
+
first_line = msg[:content].to_s.lines.first&.strip || msg[:content]
|
|
1136
|
+
{ role: msg[:role], content: "#{first_line}\n#{ref}" }
|
|
1137
|
+
end
|
|
1138
|
+
end
|
|
1139
|
+
|
|
978
1140
|
def warn_if_history_large
|
|
979
1141
|
chars = @history.sum { |m| m[:content].to_s.length }
|
|
980
1142
|
|
|
@@ -1123,13 +1285,14 @@ module ConsoleAgent
|
|
|
1123
1285
|
return
|
|
1124
1286
|
end
|
|
1125
1287
|
|
|
1126
|
-
|
|
1127
|
-
@
|
|
1288
|
+
trimmed = trim_old_outputs(@history)
|
|
1289
|
+
@interactive_old_stdout.puts "\e[36m Conversation (#{trimmed.length} messages, as sent to LLM):\e[0m"
|
|
1290
|
+
trimmed.each_with_index do |msg, i|
|
|
1128
1291
|
role = msg[:role].to_s
|
|
1129
1292
|
content = msg[:content].to_s
|
|
1130
1293
|
label = role == 'user' ? "\e[33m[user]\e[0m" : "\e[36m[assistant]\e[0m"
|
|
1131
1294
|
@interactive_old_stdout.puts "#{label} #{content}"
|
|
1132
|
-
@interactive_old_stdout.puts if i <
|
|
1295
|
+
@interactive_old_stdout.puts if i < trimmed.length - 1
|
|
1133
1296
|
end
|
|
1134
1297
|
end
|
|
1135
1298
|
|
|
@@ -1144,7 +1307,8 @@ module ConsoleAgent
|
|
|
1144
1307
|
@interactive_old_stdout.puts "\e[2m /name <lbl> Name this session for easy resume\e[0m"
|
|
1145
1308
|
@interactive_old_stdout.puts "\e[2m /context Show conversation history sent to the LLM\e[0m"
|
|
1146
1309
|
@interactive_old_stdout.puts "\e[2m /system Show the system prompt\e[0m"
|
|
1147
|
-
@interactive_old_stdout.puts "\e[2m /
|
|
1310
|
+
@interactive_old_stdout.puts "\e[2m /expand <id> Show full omitted output\e[0m"
|
|
1311
|
+
@interactive_old_stdout.puts "\e[2m /debug Toggle debug summaries (context stats, cost per call)\e[0m"
|
|
1148
1312
|
@interactive_old_stdout.puts "\e[2m > code Execute Ruby directly (skip LLM)\e[0m"
|
|
1149
1313
|
@interactive_old_stdout.puts "\e[2m exit/quit Leave interactive mode\e[0m"
|
|
1150
1314
|
end
|
|
@@ -170,6 +170,24 @@ module ConsoleAgent
|
|
|
170
170
|
handler: ->(args) { code.search_code(args['query'], args['directory']) }
|
|
171
171
|
)
|
|
172
172
|
|
|
173
|
+
if @executor
|
|
174
|
+
register(
|
|
175
|
+
name: 'recall_output',
|
|
176
|
+
description: 'Retrieve a previous code execution output that was omitted from the conversation to save context. Use the output id shown in the "[Output omitted]" placeholder.',
|
|
177
|
+
parameters: {
|
|
178
|
+
'type' => 'object',
|
|
179
|
+
'properties' => {
|
|
180
|
+
'id' => { 'type' => 'integer', 'description' => 'The output id to retrieve' }
|
|
181
|
+
},
|
|
182
|
+
'required' => ['id']
|
|
183
|
+
},
|
|
184
|
+
handler: ->(args) {
|
|
185
|
+
result = @executor.recall_output(args['id'].to_i)
|
|
186
|
+
result || "No output found with id #{args['id']}"
|
|
187
|
+
}
|
|
188
|
+
)
|
|
189
|
+
end
|
|
190
|
+
|
|
173
191
|
unless @mode == :init
|
|
174
192
|
register(
|
|
175
193
|
name: 'ask_user',
|