Class: SwarmSDK::Tools::Bash

Inherits:
Base
  • Object
show all
Defined in:
lib/swarm_sdk/tools/bash.rb

Overview

Bash tool for executing shell commands

Executes commands in a persistent shell session with timeout support. Provides comprehensive guidance on proper usage patterns.

Constant Summary collapse

ALWAYS_BLOCKED_COMMANDS =

Commands that are ALWAYS blocked for safety reasons These cannot be overridden by permissions configuration

[
  %r{^rm\s+-rf\s+/$}, # rm -rf / - delete root filesystem
].freeze

Class Method Summary collapse

Instance Method Summary collapse

Methods inherited from Base

removable, removable?, #removable?

Constructor Details

#initialize(directory:) ⇒ Bash

Returns a new instance of Bash.



17
18
19
20
# File 'lib/swarm_sdk/tools/bash.rb', line 17

def initialize(directory:)
  super()
  @directory = File.expand_path(directory)
end

Class Method Details

.creation_requirementsObject



12
13
14
# File 'lib/swarm_sdk/tools/bash.rb', line 12

def creation_requirements
  [:directory]
end

Instance Method Details

#execute(command:, description: nil, timeout: nil) ⇒ Object



96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
# File 'lib/swarm_sdk/tools/bash.rb', line 96

def execute(command:, description: nil, timeout: nil)
  # Validate inputs
  return validation_error("command is required") if command.nil? || command.empty?

  # Check against always-blocked commands
  blocked_pattern = ALWAYS_BLOCKED_COMMANDS.find { |pattern| pattern.match?(command) }
  if blocked_pattern
    return blocked_command_error(command, blocked_pattern)
  end

  # Validate and set timeout
  timeout_ms = timeout || SwarmSDK.config.bash_command_timeout
  timeout_ms = [timeout_ms, SwarmSDK.config.bash_command_max_timeout].min
  timeout_seconds = timeout_ms / 1000.0

  # Execute command with timeout
  stdout = +""
  stderr = +""
  exit_status = nil

  begin
    require "open3"
    require "timeout"

    Timeout.timeout(timeout_seconds) do
      # CRITICAL: Change to agent's directory for subprocess
      # This is SAFE because Open3.popen3 creates a subprocess
      # The subprocess inherits the directory, but the parent fiber is unaffected
      Dir.chdir(@directory) do
        Open3.popen3(command) do |stdin, out, err, wait_thr|
          stdin.close # Close stdin since we don't send input

          # Read stdout and stderr
          stdout = out.read || ""
          stderr = err.read || ""
          exit_status = wait_thr.value.exitstatus
        end
      end
    end
  rescue Timeout::Error
    return format_timeout_error(command, timeout_seconds)
  rescue Errno::ENOENT => e
    return error("Command not found or executable not in PATH: #{e.message}")
  rescue Errno::EACCES
    return error("Permission denied: Cannot execute command '#{command}'")
  rescue StandardError => e
    return error("Failed to execute command: #{e.class.name} - #{e.message}")
  end

  # Build output
  output = format_command_output(command, description, stdout, stderr, exit_status)

  # Truncate if too long
  max_output = SwarmSDK.config.output_character_limit
  if output.length > max_output
    truncated = output[0...max_output]
    truncated += "\n\n<system-reminder>Output truncated at #{max_output} characters. The full output was #{output.length} characters.</system-reminder>"
    output = truncated
  end

  # Add usage reminders for certain patterns
  output = add_usage_reminders(output, command)

  output
rescue StandardError => e
  error("Unexpected error executing command: #{e.class.name} - #{e.message}")
end

#nameObject



22
23
24
# File 'lib/swarm_sdk/tools/bash.rb', line 22

def name
  "Bash"
end