console_agent 0.3.0 → 0.4.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 661ab1a997f36d2b10ea9649aa41367aca039c859c3e2afa01c3cbf8b8dd9345
4
- data.tar.gz: 5cfe513d78a1785b7199fb0ebbbc70e825092e49e4051caf390e3ccb7ccf23c4
3
+ metadata.gz: 28a64e6be4f7d39d73e55b67d6e95404324355420293651acf44fd439394eb56
4
+ data.tar.gz: 8350d0a49d94733c54530debc4d8e184bd0c2774b04f82c68be24b71d32a7457
5
5
  SHA512:
6
- metadata.gz: 0d7fd63a81886c7abbf4f82db677b6b4bb9151760c378901306e789b3619d7ca31b743f7f4a14a9a28335e775877243b0c4ff8b8bd6cc6acee0c8a02c270eccc
7
- data.tar.gz: 3e0308bd89e0421a4a29dbe8c7b7be6afd584a504559a456f830e1c9a268517d131d6dcf00417347f5f3ea80a434bf9211e0515771fc3e43686f0117bf401f9b
6
+ metadata.gz: 874af072ffd5222c5ef7c782e1b5db18a148ecaf013a6ba8f2e33d863123895235c1d93106551ad5197eb35c17ad2bf65b076a79e74ef091ac76690c59fe4ba8
7
+ data.tar.gz: c2ba1ece779bf4e12b30c9bc0ed533dc9acabdaa22d6749e8f3f75d0d18fea1cb3895c669c313ec005f1e79a3a72ab7e2d16a2fefdcbc7df8509eac35afaee92
@@ -84,7 +84,7 @@ module ConsoleAgent
84
84
  nil
85
85
  end
86
86
 
87
- def ai_resume(identifier)
87
+ def ai_resume(identifier = nil)
88
88
  __ensure_console_agent_user
89
89
 
90
90
  require 'console_agent/context_builder'
@@ -93,9 +93,16 @@ module ConsoleAgent
93
93
  require 'console_agent/repl'
94
94
  require 'console_agent/session_logger'
95
95
 
96
- session = __find_session(identifier)
96
+ session = if identifier
97
+ __find_session(identifier)
98
+ else
99
+ session_class = Object.const_get('ConsoleAgent::Session')
100
+ session_class.where(mode: 'interactive', user_name: ConsoleAgent.current_user).recent.first
101
+ end
102
+
97
103
  unless session
98
- $stderr.puts "\e[31mSession not found: #{identifier}\e[0m"
104
+ msg = identifier ? "Session not found: #{identifier}" : "No interactive sessions found."
105
+ $stderr.puts "\e[31m#{msg}\e[0m"
99
106
  return nil
100
107
  end
101
108
 
@@ -106,17 +106,17 @@ module ConsoleAgent
106
106
  @total_input_tokens = session.input_tokens || 0
107
107
  @total_output_tokens = session.output_tokens || 0
108
108
 
109
- # Replay stored console output so the user sees previous context
109
+ # Seed the capture buffer with previous output so it's preserved on save
110
+ @interactive_console_capture.write(session.console_output.to_s)
111
+
112
+ # Replay to the user via the real stdout (bypass TeeIO to avoid double-capture)
110
113
  if session.console_output && !session.console_output.strip.empty?
111
- $stdout.puts "\e[2m--- Replaying previous session output ---\e[0m"
112
- $stdout.puts session.console_output
113
- $stdout.puts "\e[2m--- End of previous output ---\e[0m"
114
- $stdout.puts
114
+ @interactive_old_stdout.puts "\e[2m--- Replaying previous session output ---\e[0m"
115
+ @interactive_old_stdout.puts session.console_output
116
+ @interactive_old_stdout.puts "\e[2m--- End of previous output ---\e[0m"
117
+ @interactive_old_stdout.puts
115
118
  end
116
119
 
117
- # Copy replayed output into the capture buffer so it's preserved on save
118
- @interactive_console_capture.write(session.console_output.to_s)
119
-
120
120
  interactive_loop
121
121
  end
122
122
 
@@ -144,8 +144,9 @@ module ConsoleAgent
144
144
  def interactive_loop
145
145
  auto = ConsoleAgent.configuration.auto_execute
146
146
  name_display = @interactive_session_name ? " (#{@interactive_session_name})" : ""
147
- $stdout.puts "\e[36mConsoleAgent interactive mode#{name_display}. Type 'exit' or 'quit' to leave.\e[0m"
148
- $stdout.puts "\e[2m Auto-execute: #{auto ? 'ON' : 'OFF'} (Shift-Tab or /auto to toggle) | /usage | /name <label>\e[0m"
147
+ # Write banner to real stdout (bypass TeeIO) so it doesn't accumulate on resume
148
+ @interactive_old_stdout.puts "\e[36mConsoleAgent interactive mode#{name_display}. Type 'exit' or 'quit' to leave.\e[0m"
149
+ @interactive_old_stdout.puts "\e[2m Auto-execute: #{auto ? 'ON' : 'OFF'} (Shift-Tab or /auto to toggle) | /usage | /name <label>\e[0m"
149
150
 
150
151
  # Bind Shift-Tab to insert /auto command and submit
151
152
  if Readline.respond_to?(:parse_and_bind)
@@ -153,7 +154,7 @@ module ConsoleAgent
153
154
  end
154
155
 
155
156
  loop do
156
- input = Readline.readline("\e[33mai> \e[0m", false)
157
+ input = Readline.readline("\001\e[33m\002ai> \001\e[0m\002", false)
157
158
  break if input.nil? # Ctrl-D
158
159
 
159
160
  input = input.strip
@@ -163,7 +164,7 @@ module ConsoleAgent
163
164
  if input == '/auto'
164
165
  ConsoleAgent.configuration.auto_execute = !ConsoleAgent.configuration.auto_execute
165
166
  mode = ConsoleAgent.configuration.auto_execute ? 'ON' : 'OFF'
166
- $stdout.puts "\e[36m Auto-execute: #{mode}\e[0m"
167
+ @interactive_old_stdout.puts "\e[36m Auto-execute: #{mode}\e[0m"
167
168
  next
168
169
  end
169
170
 
@@ -175,7 +176,7 @@ module ConsoleAgent
175
176
  if input == '/debug'
176
177
  ConsoleAgent.configuration.debug = !ConsoleAgent.configuration.debug
177
178
  mode = ConsoleAgent.configuration.debug ? 'ON' : 'OFF'
178
- $stdout.puts "\e[36m Debug: #{mode}\e[0m"
179
+ @interactive_old_stdout.puts "\e[36m Debug: #{mode}\e[0m"
179
180
  next
180
181
  end
181
182
 
@@ -183,9 +184,9 @@ module ConsoleAgent
183
184
  name = input.sub('/name', '').strip
184
185
  if name.empty?
185
186
  if @interactive_session_name
186
- $stdout.puts "\e[36m Session name: #{@interactive_session_name}\e[0m"
187
+ @interactive_old_stdout.puts "\e[36m Session name: #{@interactive_session_name}\e[0m"
187
188
  else
188
- $stdout.puts "\e[33m Usage: /name <label> (e.g. /name salesforce_user_123)\e[0m"
189
+ @interactive_old_stdout.puts "\e[33m Usage: /name <label> (e.g. /name salesforce_user_123)\e[0m"
189
190
  end
190
191
  else
191
192
  @interactive_session_name = name
@@ -193,7 +194,7 @@ module ConsoleAgent
193
194
  require 'console_agent/session_logger'
194
195
  SessionLogger.update(@interactive_session_id, name: name)
195
196
  end
196
- $stdout.puts "\e[36m Session named: #{name}\e[0m"
197
+ @interactive_old_stdout.puts "\e[36m Session named: #{name}\e[0m"
197
198
  end
198
199
  next
199
200
  end
@@ -328,7 +329,20 @@ module ConsoleAgent
328
329
  $stdout.puts "\e[2m Thinking...\e[0m"
329
330
  end
330
331
 
331
- result = provider.chat_with_tools(messages, tools: tools, system_prompt: context)
332
+ begin
333
+ result = with_escape_monitoring do
334
+ provider.chat_with_tools(messages, tools: tools, system_prompt: context)
335
+ end
336
+ rescue Interrupt
337
+ redirect = prompt_for_redirect
338
+ if redirect
339
+ messages << { role: :user, content: redirect }
340
+ new_messages << messages.last
341
+ next
342
+ else
343
+ raise
344
+ end
345
+ end
332
346
  total_input += result.input_tokens || 0
333
347
  total_output += result.output_tokens || 0
334
348
 
@@ -390,6 +404,59 @@ module ConsoleAgent
390
404
  [final_result, new_messages]
391
405
  end
392
406
 
407
+ # Monitors stdin for Escape (or Ctrl+C, since raw mode disables signals)
408
+ # and raises Interrupt in the main thread when detected.
409
+ def with_escape_monitoring
410
+ require 'io/console'
411
+ return yield unless $stdin.respond_to?(:raw)
412
+
413
+ monitor = Thread.new do
414
+ Thread.current.report_on_exception = false
415
+ $stdin.raw do |io|
416
+ loop do
417
+ break if Thread.current[:stop]
418
+ ready = IO.select([io], nil, nil, 0.2)
419
+ next unless ready
420
+
421
+ char = io.read_nonblock(1) rescue nil
422
+ next unless char
423
+
424
+ if char == "\x03" # Ctrl+C (raw mode eats the signal)
425
+ Thread.main.raise(Interrupt)
426
+ break
427
+ elsif char == "\e"
428
+ # Distinguish standalone Escape from escape sequences (arrow keys, etc.)
429
+ seq = IO.select([io], nil, nil, 0.05)
430
+ if seq
431
+ io.read_nonblock(10) rescue nil # consume the sequence
432
+ else
433
+ Thread.main.raise(Interrupt)
434
+ break
435
+ end
436
+ end
437
+ end
438
+ end
439
+ rescue IOError, Errno::EIO, Errno::ENODEV, Errno::ENOTTY
440
+ # stdin is not a TTY (e.g. in tests or piped input) — silently skip
441
+ end
442
+
443
+ begin
444
+ yield
445
+ ensure
446
+ monitor[:stop] = true
447
+ monitor.join(1) rescue nil
448
+ end
449
+ end
450
+
451
+ def prompt_for_redirect
452
+ $stdout.puts "\n\e[33m Interrupted. What should the AI do differently?\e[0m"
453
+ $stdout.puts "\e[2m (Press Enter with no input to abort entirely)\e[0m"
454
+ $stdout.print "\e[33m redirect> \e[0m"
455
+ input = $stdin.gets
456
+ return nil if input.nil? || input.strip.empty?
457
+ input.strip
458
+ end
459
+
393
460
  def format_tool_args(name, args)
394
461
  return '' if args.nil? || args.empty?
395
462
 
@@ -1,3 +1,3 @@
1
1
  module ConsoleAgent
2
- VERSION = '0.3.0'.freeze
2
+ VERSION = '0.4.0'.freeze
3
3
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: console_agent
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.0
4
+ version: 0.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Cortfr