console_agent 0.9.0 → 0.11.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.
@@ -8,9 +8,10 @@ module ConsoleAgent
8
8
  # Tools that should never be cached (side effects or user interaction)
9
9
  NO_CACHE = %w[ask_user save_memory delete_memory execute_plan].freeze
10
10
 
11
- def initialize(executor: nil, mode: :default)
11
+ def initialize(executor: nil, mode: :default, channel: nil)
12
12
  @executor = executor
13
13
  @mode = mode
14
+ @channel = channel
14
15
  @definitions = []
15
16
  @handlers = {}
16
17
  @cache = {}
@@ -170,6 +171,24 @@ module ConsoleAgent
170
171
  handler: ->(args) { code.search_code(args['query'], args['directory']) }
171
172
  )
172
173
 
174
+ if @executor
175
+ register(
176
+ name: 'recall_output',
177
+ 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.',
178
+ parameters: {
179
+ 'type' => 'object',
180
+ 'properties' => {
181
+ 'id' => { 'type' => 'integer', 'description' => 'The output id to retrieve' }
182
+ },
183
+ 'required' => ['id']
184
+ },
185
+ handler: ->(args) {
186
+ result = @executor.recall_output(args['id'].to_i)
187
+ result || "No output found with id #{args['id']}"
188
+ }
189
+ )
190
+ end
191
+
173
192
  unless @mode == :init
174
193
  register(
175
194
  name: 'ask_user',
@@ -284,8 +303,12 @@ module ConsoleAgent
284
303
  # Ask for plan approval (unless auto-execute)
285
304
  skip_confirmations = auto
286
305
  unless auto
287
- $stdout.print "\e[33m Accept plan? [y/N/a(uto)] \e[0m"
288
- answer = $stdin.gets.to_s.strip.downcase
306
+ if @channel
307
+ answer = @channel.confirm(" Accept plan? [y/N/a(uto)] ")
308
+ else
309
+ $stdout.print "\e[33m Accept plan? [y/N/a(uto)] \e[0m"
310
+ answer = $stdin.gets.to_s.strip.downcase
311
+ end
289
312
  case answer
290
313
  when 'a', 'auto'
291
314
  skip_confirmations = true
@@ -308,8 +331,12 @@ module ConsoleAgent
308
331
 
309
332
  # Per-step confirmation (unless auto-execute or plan-level auto)
310
333
  unless skip_confirmations
311
- $stdout.print "\e[33m Run? [y/N/edit] \e[0m"
312
- step_answer = $stdin.gets.to_s.strip.downcase
334
+ if @channel
335
+ step_answer = @channel.confirm(" Run? [y/N/edit] ")
336
+ else
337
+ $stdout.print "\e[33m Run? [y/N/edit] \e[0m"
338
+ step_answer = $stdin.gets.to_s.strip.downcase
339
+ end
313
340
 
314
341
  case step_answer
315
342
  when 'e', 'edit'
@@ -317,8 +344,12 @@ module ConsoleAgent
317
344
  if edited && edited != step['code']
318
345
  $stdout.puts "\e[33m # Edited code:\e[0m"
319
346
  $stdout.puts highlight_plan_code(edited)
320
- $stdout.print "\e[33m Run edited code? [y/N] \e[0m"
321
- confirm = $stdin.gets.to_s.strip.downcase
347
+ if @channel
348
+ confirm = @channel.confirm(" Run edited code? [y/N] ")
349
+ else
350
+ $stdout.print "\e[33m Run edited code? [y/N] \e[0m"
351
+ confirm = $stdin.gets.to_s.strip.downcase
352
+ end
322
353
  unless confirm == 'y' || confirm == 'yes'
323
354
  feedback = ask_feedback("What would you like changed?")
324
355
  results << "Step #{i + 1}: User declined after edit. Feedback: #{feedback}"
@@ -336,6 +367,17 @@ module ConsoleAgent
336
367
  end
337
368
 
338
369
  exec_result = @executor.execute(step['code'])
370
+
371
+ # On safety error, offer to re-run with guards disabled (console only)
372
+ if @executor.last_safety_error
373
+ if @channel && !@channel.supports_danger?
374
+ results << "Step #{i + 1} (#{step['description']}):\nBLOCKED by safety guard: #{@executor.last_error}. Write operations are not permitted in this channel."
375
+ break
376
+ else
377
+ exec_result = @executor.offer_danger_retry(step['code'])
378
+ end
379
+ end
380
+
339
381
  # Make result available as step1, step2, etc. for subsequent steps
340
382
  @executor.binding_context.local_variable_set(:"step#{i + 1}", exec_result)
341
383
  output = @executor.last_output
@@ -389,18 +431,26 @@ module ConsoleAgent
389
431
  end
390
432
 
391
433
  def ask_feedback(prompt)
392
- $stdout.print "\e[36m #{prompt} > \e[0m"
393
- feedback = $stdin.gets
394
- return '(no feedback provided)' if feedback.nil?
395
- feedback.strip.empty? ? '(no feedback provided)' : feedback.strip
434
+ if @channel
435
+ @channel.prompt(" #{prompt} > ")
436
+ else
437
+ $stdout.print "\e[36m #{prompt} > \e[0m"
438
+ feedback = $stdin.gets
439
+ return '(no feedback provided)' if feedback.nil?
440
+ feedback.strip.empty? ? '(no feedback provided)' : feedback.strip
441
+ end
396
442
  end
397
443
 
398
444
  def ask_user(question)
399
- $stdout.puts "\e[36m ? #{question}\e[0m"
400
- $stdout.print "\e[36m > \e[0m"
401
- answer = $stdin.gets
402
- return '(no answer provided)' if answer.nil?
403
- answer.strip.empty? ? '(no answer provided)' : answer.strip
445
+ if @channel
446
+ @channel.prompt(" ? #{question}\n > ")
447
+ else
448
+ $stdout.puts "\e[36m ? #{question}\e[0m"
449
+ $stdout.print "\e[36m > \e[0m"
450
+ answer = $stdin.gets
451
+ return '(no answer provided)' if answer.nil?
452
+ answer.strip.empty? ? '(no answer provided)' : answer.strip
453
+ end
404
454
  end
405
455
 
406
456
  def register(name:, description:, parameters:, handler:)
@@ -1,3 +1,3 @@
1
1
  module ConsoleAgent
2
- VERSION = '0.9.0'.freeze
2
+ VERSION = '0.11.0'.freeze
3
3
  end
data/lib/console_agent.rb CHANGED
@@ -58,8 +58,8 @@ module ConsoleAgent
58
58
  def status
59
59
  c = configuration
60
60
  key = c.resolved_api_key
61
- masked_key = if key.nil? || key.empty?
62
- "\e[31m(not set)\e[0m"
61
+ masked_key = if key.nil? || key.empty? || key == 'no-key'
62
+ c.provider == :local ? "\e[32m(not required)\e[0m" : "\e[31m(not set)\e[0m"
63
63
  else
64
64
  key[0..6] + '...' + key[-4..-1]
65
65
  end
@@ -69,11 +69,19 @@ module ConsoleAgent
69
69
  lines << " Provider: #{c.provider}"
70
70
  lines << " Model: #{c.resolved_model}"
71
71
  lines << " API key: #{masked_key}"
72
- lines << " Max tokens: #{c.max_tokens}"
72
+ lines << " Local URL: #{c.local_url}" if c.provider == :local
73
+ lines << " Max tokens: #{c.max_tokens || '(auto)'}"
73
74
  lines << " Temperature: #{c.temperature}"
74
75
  lines << " Timeout: #{c.timeout}s"
75
76
  lines << " Max tool rounds:#{c.max_tool_rounds}"
76
77
  lines << " Auto-execute: #{c.auto_execute}"
78
+ guards = c.safety_guards
79
+ if guards.empty?
80
+ lines << " Safe mode: \e[33m(no guards configured)\e[0m"
81
+ else
82
+ status = guards.enabled? ? "\e[32mON\e[0m" : "\e[31mOFF\e[0m"
83
+ lines << " Safe mode: #{status} (#{guards.names.join(', ')})"
84
+ end
77
85
  lines << " Memories: #{c.memories_enabled}"
78
86
  lines << " Session logging:#{session_table_status}"
79
87
  lines << " Debug: #{c.debug}"
@@ -134,6 +142,12 @@ module ConsoleAgent
134
142
  migrations << 'name'
135
143
  end
136
144
 
145
+ unless conn.column_exists?(table, :slack_thread_ts)
146
+ conn.add_column(table, :slack_thread_ts, :string, limit: 255)
147
+ conn.add_index(table, :slack_thread_ts) unless conn.index_exists?(table, :slack_thread_ts)
148
+ migrations << 'slack_thread_ts'
149
+ end
150
+
137
151
  if migrations.empty?
138
152
  $stdout.puts "\e[32mConsoleAgent: #{table} is up to date.\e[0m"
139
153
  else
@@ -1,5 +1,5 @@
1
1
  ConsoleAgent.configure do |config|
2
- # LLM provider: :anthropic or :openai
2
+ # LLM provider: :anthropic, :openai, or :local
3
3
  config.provider = :anthropic
4
4
 
5
5
  # API key (or set ANTHROPIC_API_KEY / OPENAI_API_KEY env var)
@@ -23,6 +23,12 @@ ConsoleAgent.configure do |config|
23
23
  # HTTP timeout in seconds
24
24
  config.timeout = 30
25
25
 
26
+ # Local model provider (Ollama, vLLM, or any OpenAI-compatible server):
27
+ # config.provider = :local
28
+ # config.local_url = 'http://localhost:11434'
29
+ # config.local_model = 'qwen2.5:7b'
30
+ # config.local_api_key = nil
31
+
26
32
  # Debug mode: prints full API requests/responses and tool calls to stderr
27
33
  # config.debug = true
28
34
 
@@ -38,4 +44,27 @@ ConsoleAgent.configure do |config|
38
44
  # When nil, all requests are denied. Set credentials or use config.authenticate.
39
45
  # config.admin_username = 'admin'
40
46
  # config.admin_password = 'changeme'
47
+
48
+ # Safety guards: prevent side effects (DB writes, HTTP calls, etc.) during code execution.
49
+ # When enabled, code runs in safe mode by default. Users can toggle with /danger in the REPL.
50
+ #
51
+ # Built-in guard for database writes (works on Rails 5+, all adapters):
52
+ # config.use_builtin_safety_guard :database_writes
53
+ #
54
+ # Built-in guard for HTTP mutations — blocks POST/PUT/PATCH/DELETE via Net::HTTP.
55
+ # Covers most Ruby HTTP libraries (HTTParty, RestClient, Faraday) since they use Net::HTTP:
56
+ # config.use_builtin_safety_guard :http_mutations
57
+ #
58
+ # Allowlist specific hosts or tables so they pass through without blocking:
59
+ # config.use_builtin_safety_guard :http_mutations,
60
+ # allow: [/s3\.amazonaws\.com/, /googleapis\.com/]
61
+ # config.use_builtin_safety_guard :database_writes,
62
+ # allow: ['console_agent_sessions']
63
+ #
64
+ # Built-in guard for mailers — disables ActionMailer delivery:
65
+ # config.use_builtin_safety_guard :mailers
66
+ #
67
+ # config.safety_guard :jobs do |&execute|
68
+ # Sidekiq::Testing.fake! { execute.call }
69
+ # end
41
70
  end
@@ -0,0 +1,7 @@
1
+ namespace :console_agent do
2
+ desc "Start the ConsoleAgent Slack bot (Socket Mode)"
3
+ task slack: :environment do
4
+ require 'console_agent/slack_bot'
5
+ ConsoleAgent::SlackBot.new.start
6
+ end
7
+ 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.9.0
4
+ version: 0.11.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Cortfr
@@ -98,17 +98,24 @@ files:
98
98
  - app/views/layouts/console_agent/application.html.erb
99
99
  - config/routes.rb
100
100
  - lib/console_agent.rb
101
+ - lib/console_agent/channel/base.rb
102
+ - lib/console_agent/channel/console.rb
103
+ - lib/console_agent/channel/slack.rb
101
104
  - lib/console_agent/configuration.rb
102
105
  - lib/console_agent/console_methods.rb
103
106
  - lib/console_agent/context_builder.rb
107
+ - lib/console_agent/conversation_engine.rb
104
108
  - lib/console_agent/engine.rb
105
109
  - lib/console_agent/executor.rb
106
110
  - lib/console_agent/providers/anthropic.rb
107
111
  - lib/console_agent/providers/base.rb
112
+ - lib/console_agent/providers/local.rb
108
113
  - lib/console_agent/providers/openai.rb
109
114
  - lib/console_agent/railtie.rb
110
115
  - lib/console_agent/repl.rb
116
+ - lib/console_agent/safety_guards.rb
111
117
  - lib/console_agent/session_logger.rb
118
+ - lib/console_agent/slack_bot.rb
112
119
  - lib/console_agent/storage/base.rb
113
120
  - lib/console_agent/storage/file_storage.rb
114
121
  - lib/console_agent/tools/code_tools.rb
@@ -119,6 +126,7 @@ files:
119
126
  - lib/console_agent/version.rb
120
127
  - lib/generators/console_agent/install_generator.rb
121
128
  - lib/generators/console_agent/templates/initializer.rb
129
+ - lib/tasks/console_agent.rake
122
130
  homepage: https://github.com/cortfr/console_agent
123
131
  licenses:
124
132
  - MIT