Module: LanguageOperator::CLI::Commands::Agent::Workspace
- Included in:
- Base
- Defined in:
- lib/language_operator/cli/commands/agent/workspace.rb
Overview
Workspace management for agents
Class Method Summary collapse
Class Method Details
.included(base) ⇒ Object
14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 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 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 |
# File 'lib/language_operator/cli/commands/agent/workspace.rb', line 14 def self.included(base) base.class_eval do desc 'workspace NAME', 'Browse agent workspace files' long_desc <<-DESC Browse and manage the workspace files for an agent. Workspaces provide persistent storage for agents to maintain state, cache data, and remember information across executions. Examples: langop agent workspace my-agent # List all files langop agent workspace my-agent --path /workspace/state.json # View specific file langop agent workspace my-agent --clean # Clear workspace DESC option :cluster, type: :string, desc: 'Override current cluster context' option :path, type: :string, desc: 'View specific file contents' option :clean, type: :boolean, desc: 'Clear workspace (with confirmation)' def workspace(name) handle_command_error('access workspace') do ctx = CLI::Helpers::ClusterContext.() # Get agent to verify it exists agent = get_resource_or_exit(LanguageOperator::Constants::RESOURCE_AGENT, name) # Check if workspace is enabled workspace_enabled = agent.dig('spec', 'workspace', 'enabled') unless workspace_enabled Formatters::ProgressFormatter.warn("Workspace is not enabled for agent '#{name}'") puts puts 'Enable workspace in agent configuration:' puts ' spec:' puts ' workspace:' puts ' enabled: true' puts ' size: 10Gi' exit 1 end if [:path] view_workspace_file(ctx, name, [:path]) elsif [:clean] clean_workspace(ctx, name) else list_workspace_files(ctx, name) end end end private def get_agent_pod(ctx, agent_name) # Validate agent name for label compatibility unless CLI::Helpers::LabelUtils.valid_label_value?(agent_name) Formatters::ProgressFormatter.error("Agent name '#{agent_name}' is not valid for Kubernetes labels") puts puts 'Agent names must:' puts ' - Be 63 characters or less' puts ' - Contain only lowercase letters, numbers, hyphens, and dots' puts ' - Start and end with alphanumeric characters' exit 1 end # Find pod for this agent using normalized label selector label_selector = CLI::Helpers::LabelUtils.agent_pod_selector(agent_name) pods = ctx.client.list_resources('Pod', namespace: ctx.namespace, label_selector: label_selector) if pods.empty? debug_info = CLI::Helpers::LabelUtils.debug_pod_search(ctx, agent_name) Formatters::ProgressFormatter.error("No running pods found for agent '#{agent_name}'") puts puts 'Possible reasons:' puts ' - Agent pod has not started yet' puts ' - Agent is paused or stopped' puts ' - Agent failed to deploy' puts ' - Label mismatch (debugging info below)' puts puts 'Debug information:' puts " Agent name: #{debug_info[:agent_name]}" puts " Normalized: #{debug_info[:normalized_name]}" puts " Label selector: #{debug_info[:label_selector]}" puts " Namespace: #{debug_info[:namespace]}" puts puts 'Check agent status with:' puts " langop agent inspect #{agent_name}" puts puts 'List all pods in namespace:' puts " kubectl get pods -n #{ctx.namespace} -l #{Constants::KubernetesLabels::COMPONENT}=#{Constants::KubernetesLabels::COMPONENT_AGENT}" exit 1 end # Find a running pod running_pod = pods.find do |pod| pod.dig('status', 'phase') == 'Running' end unless running_pod Formatters::ProgressFormatter.error('Agent pod exists but is not running') puts puts "Current pod status: #{pods.first.dig('status', 'phase')}" puts puts 'Check pod logs with:' puts " langop agent logs #{agent_name}" exit 1 end running_pod.dig('metadata', 'name') end def exec_in_pod(ctx, pod_name, command) # Build command as array to prevent shell injection kubectl_prefix_array = Shellwords.shellsplit(ctx.kubectl_prefix) cmd_array = kubectl_prefix_array + ['exec', pod_name, '--'] # Add command arguments cmd_array += if command.is_a?(Array) command else [command] end # Execute with array to avoid shell interpolation stdout, stderr, status = Open3.capture3(*cmd_array) raise "Command failed: #{stderr}" unless status.success? stdout end def list_workspace_files(ctx, agent_name) pod_name = get_agent_pod(ctx, agent_name) # Check if workspace directory exists begin exec_in_pod(ctx, pod_name, 'test -d /workspace') rescue StandardError Formatters::ProgressFormatter.error('Workspace directory not found in agent pod') puts puts 'The /workspace directory does not exist in the agent pod.' puts 'This agent may not have workspace support enabled.' exit 1 end # Get workspace usage usage_output = exec_in_pod( ctx, pod_name, 'du -sh /workspace 2>/dev/null || echo "0\t/workspace"' ) workspace_size = usage_output.split("\t").first.strip # List files with details file_list = exec_in_pod( ctx, pod_name, 'find /workspace -ls 2>/dev/null | tail -n +2' ) puts puts pastel.cyan("Workspace for agent '#{agent_name}' (#{workspace_size})") puts '=' * 60 puts if file_list.strip.empty? puts pastel.dim('Workspace is empty') puts puts 'The agent will create files here as it runs.' puts return end # Parse and display file list file_list.each_line do |line| parts = line.strip.split(/\s+/, 11) next if parts.length < 11 # Extract relevant parts # Format: inode blocks perms links user group size month day time path perms = parts[2] size = parts[6] month = parts[7] day = parts[8] time_or_year = parts[9] path = parts[10] # Skip the /workspace directory itself next if path == '/workspace' # Determine file type and icon icon = if perms.start_with?('d') pastel.blue('📁') else pastel.white('📄') end # Format path relative to workspace relative_path = path.sub('/workspace/', '') indent = ' ' * relative_path.count('/') # Format size formatted_size = format_file_size(size.to_i).rjust(8) # Format time formatted_time = "#{month} #{day.rjust(2)} #{time_or_year}" puts "#{indent}#{icon} #{File.basename(relative_path).ljust(30)} #{pastel.dim(formatted_size)} #{pastel.dim(formatted_time)}" end puts puts pastel.dim('Commands:') puts pastel.dim(" langop agent workspace #{agent_name} --path /workspace/<file> # View file") puts pastel.dim(" langop agent workspace #{agent_name} --clean # Clear workspace") puts end def view_workspace_file(ctx, agent_name, file_path) pod_name = get_agent_pod(ctx, agent_name) # Check if file exists begin exec_in_pod(ctx, pod_name, "test -f #{file_path}") rescue StandardError Formatters::ProgressFormatter.error("File not found: #{file_path}") puts puts 'List available files with:' puts " langop agent workspace #{agent_name}" exit 1 end # Get file metadata stat_output = exec_in_pod( ctx, pod_name, "stat -c '%s %Y' #{file_path}" ) size, mtime = stat_output.strip.split # Get file contents contents = exec_in_pod( ctx, pod_name, "cat #{file_path}" ) # Display file puts puts pastel.cyan("File: #{file_path}") puts "Size: #{format_file_size(size.to_i)}" puts "Modified: #{(Time.at(mtime.to_i))}" puts '=' * 60 puts puts contents puts end def clean_workspace(ctx, agent_name) pod_name = get_agent_pod(ctx, agent_name) # Get current workspace usage usage_output = exec_in_pod( ctx, pod_name, 'du -sh /workspace 2>/dev/null || echo "0\t/workspace"' ) workspace_size = usage_output.split("\t").first.strip # Count files file_count = exec_in_pod( ctx, pod_name, 'find /workspace -type f | wc -l' ).strip.to_i puts puts pastel.yellow("This will delete ALL files in the workspace for '#{agent_name}'") puts puts 'The agent will lose:' puts ' • Execution history' puts ' • Cached data' puts ' • State information' puts puts "Current workspace: #{file_count} files, #{workspace_size}" puts # Use UserPrompts helper return unless CLI::Helpers::UserPrompts.confirm('Are you sure?') # Delete all files in workspace Formatters::ProgressFormatter.with_spinner('Cleaning workspace') do exec_in_pod( ctx, pod_name, 'find /workspace -mindepth 1 -delete' ) end Formatters::ProgressFormatter.success("Workspace cleared (freed #{workspace_size})") puts puts 'The agent will start fresh on its next execution.' end def format_file_size(bytes) Formatters::ValueFormatter.file_size(bytes) end def (time) Formatters::ValueFormatter.(time) end end end |