Class: Legion::CLI::Chat::Tools::RunCommand
Class Method Summary
collapse
Methods inherited from Tools::Base
deferred, deferred?, description, error_response, extension, handle_exception, input_schema, log, mcp_category, mcp_tier, runner, sticky, tags, text_response, tool_name, trigger_words
Class Method Details
.call(command:, timeout: 120, working_directory: nil) ⇒ Object
24
25
26
27
28
29
30
31
32
|
# File 'lib/legion/cli/chat/tools/run_command.rb', line 24
def self.call(command:, timeout: 120, working_directory: nil)
dir = working_directory ? File.expand_path(working_directory) : Dir.pwd
if sandbox_enabled? && sandbox_available?
execute_sandboxed(command: command, timeout: timeout, dir: dir)
else
execute_direct(command: command, timeout: timeout, dir: dir)
end
end
|
.execute_direct(command:, timeout:, dir:) ⇒ Object
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
|
# File 'lib/legion/cli/chat/tools/run_command.rb', line 63
def self.execute_direct(command:, timeout:, dir:)
stdout, stderr, status = Open3.popen3(command, chdir: dir) do |stdin, out, err, wait_thr|
stdin.close
out_reader = Thread.new { out.read }
err_reader = Thread.new { err.read }
unless wait_thr.join(timeout)
::Process.kill('TERM', wait_thr.pid)
wait_thr.join(5) || ::Process.kill('KILL', wait_thr.pid)
out_reader.kill
err_reader.kill
raise ::Timeout::Error, "command timed out after #{timeout}s"
end
[out_reader.value, err_reader.value, wait_thr.value]
end
format_output(command, stdout, stderr, status.exitstatus)
rescue ::Timeout::Error
"[command timed out after #{timeout}s]: #{command}"
rescue StandardError => e
"Error executing command: #{e.message}"
end
|
.execute_sandboxed(command:, timeout:, dir:) ⇒ Object
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
|
# File 'lib/legion/cli/chat/tools/run_command.rb', line 44
def self.execute_sandboxed(command:, timeout:, dir:)
timeout_ms = timeout * 1000
result = Legion::Extensions::Exec::Runners::Shell.execute(
command: command, cwd: dir, timeout: timeout_ms
)
if result[:error] == :blocked
"Command blocked by sandbox: #{result[:reason]}"
elsif result[:error] == :timeout
"[command timed out after #{timeout}s]: #{command}"
elsif result[:success] == false && result[:error]
"Error executing command: #{result[:error]}"
else
format_output(command, result[:stdout], result[:stderr], result[:exit_code])
end
rescue StandardError => e
"Error executing command: #{e.message}"
end
|
87
88
89
90
91
92
93
94
|
# File 'lib/legion/cli/chat/tools/run_command.rb', line 87
def self.format_output(command, stdout, stderr, exit_code)
output = String.new
output << "$ #{command}\n"
output << stdout.to_s unless stdout.to_s.empty?
output << stderr.to_s unless stderr.to_s.empty?
output << "\n[exit code: #{exit_code}]"
output
end
|
.sandbox_available? ⇒ Boolean
40
41
42
|
# File 'lib/legion/cli/chat/tools/run_command.rb', line 40
def self.sandbox_available?
defined?(Legion::Extensions::Exec::Runners::Shell)
end
|
.sandbox_enabled? ⇒ Boolean
34
35
36
37
38
|
# File 'lib/legion/cli/chat/tools/run_command.rb', line 34
def self.sandbox_enabled?
Legion::Settings.dig(:chat, :sandboxed_commands, :enabled) == true
rescue StandardError
false
end
|