language-operator 0.1.61 → 0.1.62
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 +4 -4
- data/.claude/commands/persona.md +9 -0
- data/.claude/commands/task.md +46 -1
- data/.rubocop.yml +13 -0
- data/.rubocop_custom/use_ux_helper.rb +44 -0
- data/CHANGELOG.md +8 -0
- data/Gemfile.lock +12 -1
- data/Makefile +26 -7
- data/Makefile.common +50 -0
- data/bin/aictl +8 -1
- data/components/agent/Gemfile +1 -1
- data/components/agent/bin/langop-agent +7 -0
- data/docs/README.md +58 -0
- data/docs/{dsl/best-practices.md → best-practices.md} +4 -4
- data/docs/cli-reference.md +274 -0
- data/docs/{dsl/constraints.md → constraints.md} +5 -5
- data/docs/how-agents-work.md +156 -0
- data/docs/installation.md +218 -0
- data/docs/quickstart.md +299 -0
- data/docs/understanding-generated-code.md +265 -0
- data/docs/using-tools.md +457 -0
- data/docs/webhooks.md +509 -0
- data/examples/ux_helpers_demo.rb +296 -0
- data/lib/language_operator/agent/base.rb +11 -1
- data/lib/language_operator/agent/executor.rb +23 -6
- data/lib/language_operator/agent/safety/safe_executor.rb +41 -39
- data/lib/language_operator/agent/task_executor.rb +346 -63
- data/lib/language_operator/agent/web_server.rb +110 -14
- data/lib/language_operator/agent/webhook_authenticator.rb +39 -5
- data/lib/language_operator/agent.rb +88 -2
- data/lib/language_operator/cli/base_command.rb +17 -11
- data/lib/language_operator/cli/command_loader.rb +72 -0
- data/lib/language_operator/cli/commands/agent/base.rb +837 -0
- data/lib/language_operator/cli/commands/agent/code_operations.rb +102 -0
- data/lib/language_operator/cli/commands/agent/helpers/cluster_llm_client.rb +116 -0
- data/lib/language_operator/cli/commands/agent/helpers/code_parser.rb +115 -0
- data/lib/language_operator/cli/commands/agent/helpers/synthesis_watcher.rb +96 -0
- data/lib/language_operator/cli/commands/agent/learning.rb +289 -0
- data/lib/language_operator/cli/commands/agent/lifecycle.rb +102 -0
- data/lib/language_operator/cli/commands/agent/logs.rb +125 -0
- data/lib/language_operator/cli/commands/agent/workspace.rb +327 -0
- data/lib/language_operator/cli/commands/cluster.rb +129 -84
- data/lib/language_operator/cli/commands/install.rb +1 -1
- data/lib/language_operator/cli/commands/model/base.rb +215 -0
- data/lib/language_operator/cli/commands/model/test.rb +165 -0
- data/lib/language_operator/cli/commands/persona.rb +16 -34
- data/lib/language_operator/cli/commands/quickstart.rb +3 -2
- data/lib/language_operator/cli/commands/status.rb +40 -67
- data/lib/language_operator/cli/commands/system/base.rb +44 -0
- data/lib/language_operator/cli/commands/system/exec.rb +147 -0
- data/lib/language_operator/cli/commands/system/helpers/llm_synthesis.rb +183 -0
- data/lib/language_operator/cli/commands/system/helpers/pod_manager.rb +212 -0
- data/lib/language_operator/cli/commands/system/helpers/template_loader.rb +57 -0
- data/lib/language_operator/cli/commands/system/helpers/template_validator.rb +174 -0
- data/lib/language_operator/cli/commands/system/schema.rb +92 -0
- data/lib/language_operator/cli/commands/system/synthesis_template.rb +151 -0
- data/lib/language_operator/cli/commands/system/synthesize.rb +224 -0
- data/lib/language_operator/cli/commands/system/validate_template.rb +130 -0
- data/lib/language_operator/cli/commands/tool/base.rb +271 -0
- data/lib/language_operator/cli/commands/tool/install.rb +255 -0
- data/lib/language_operator/cli/commands/tool/search.rb +69 -0
- data/lib/language_operator/cli/commands/tool/test.rb +115 -0
- data/lib/language_operator/cli/commands/use.rb +29 -6
- data/lib/language_operator/cli/errors/handler.rb +20 -17
- data/lib/language_operator/cli/errors/suggestions.rb +3 -5
- data/lib/language_operator/cli/errors/thor_errors.rb +55 -0
- data/lib/language_operator/cli/formatters/code_formatter.rb +4 -11
- data/lib/language_operator/cli/formatters/log_formatter.rb +8 -15
- data/lib/language_operator/cli/formatters/progress_formatter.rb +6 -8
- data/lib/language_operator/cli/formatters/status_formatter.rb +26 -7
- data/lib/language_operator/cli/formatters/table_formatter.rb +47 -36
- data/lib/language_operator/cli/formatters/value_formatter.rb +75 -0
- data/lib/language_operator/cli/helpers/cluster_context.rb +5 -3
- data/lib/language_operator/cli/helpers/kubeconfig_validator.rb +2 -1
- data/lib/language_operator/cli/helpers/label_utils.rb +97 -0
- data/lib/language_operator/{ux/concerns/provider_helpers.rb → cli/helpers/provider_helper.rb} +10 -29
- data/lib/language_operator/cli/helpers/schedule_builder.rb +21 -1
- data/lib/language_operator/cli/helpers/user_prompts.rb +19 -11
- data/lib/language_operator/cli/helpers/ux_helper.rb +538 -0
- data/lib/language_operator/{ux/concerns/input_validation.rb → cli/helpers/validation_helper.rb} +13 -66
- data/lib/language_operator/cli/main.rb +50 -40
- data/lib/language_operator/cli/templates/tools/generic.yaml +3 -0
- data/lib/language_operator/cli/wizards/agent_wizard.rb +12 -20
- data/lib/language_operator/cli/wizards/model_wizard.rb +271 -0
- data/lib/language_operator/cli/wizards/quickstart_wizard.rb +8 -34
- data/lib/language_operator/client/base.rb +28 -0
- data/lib/language_operator/client/config.rb +4 -1
- data/lib/language_operator/client/mcp_connector.rb +1 -1
- data/lib/language_operator/config/cluster_config.rb +3 -2
- data/lib/language_operator/config.rb +38 -11
- data/lib/language_operator/constants/kubernetes_labels.rb +80 -0
- data/lib/language_operator/constants.rb +13 -0
- data/lib/language_operator/dsl/http.rb +127 -10
- data/lib/language_operator/dsl.rb +153 -6
- data/lib/language_operator/errors.rb +50 -0
- data/lib/language_operator/kubernetes/client.rb +11 -6
- data/lib/language_operator/kubernetes/resource_builder.rb +58 -84
- data/lib/language_operator/templates/schema/agent_dsl_openapi.yaml +1 -1
- data/lib/language_operator/templates/schema/agent_dsl_schema.json +1 -1
- data/lib/language_operator/type_coercion.rb +118 -34
- data/lib/language_operator/utils/secure_path.rb +74 -0
- data/lib/language_operator/utils.rb +7 -0
- data/lib/language_operator/validators.rb +54 -2
- data/lib/language_operator/version.rb +1 -1
- data/synth/001/Makefile +10 -2
- data/synth/001/agent.rb +16 -15
- data/synth/001/output.log +27 -10
- data/synth/002/Makefile +10 -2
- data/synth/003/Makefile +1 -1
- data/synth/003/README.md +205 -133
- data/synth/003/agent.optimized.rb +66 -0
- data/synth/003/agent.synthesized.rb +41 -0
- metadata +111 -35
- data/docs/dsl/agent-reference.md +0 -604
- data/docs/dsl/mcp-integration.md +0 -1177
- data/docs/dsl/webhooks.md +0 -932
- data/docs/dsl/workflows.md +0 -744
- data/lib/language_operator/cli/commands/agent.rb +0 -1712
- data/lib/language_operator/cli/commands/model.rb +0 -366
- data/lib/language_operator/cli/commands/system.rb +0 -1259
- data/lib/language_operator/cli/commands/tool.rb +0 -654
- data/lib/language_operator/cli/formatters/optimization_formatter.rb +0 -226
- data/lib/language_operator/cli/helpers/pastel_helper.rb +0 -24
- data/lib/language_operator/learning/adapters/base_adapter.rb +0 -149
- data/lib/language_operator/learning/adapters/jaeger_adapter.rb +0 -221
- data/lib/language_operator/learning/adapters/signoz_adapter.rb +0 -435
- data/lib/language_operator/learning/adapters/tempo_adapter.rb +0 -239
- data/lib/language_operator/learning/optimizer.rb +0 -319
- data/lib/language_operator/learning/pattern_detector.rb +0 -260
- data/lib/language_operator/learning/task_synthesizer.rb +0 -288
- data/lib/language_operator/learning/trace_analyzer.rb +0 -285
- data/lib/language_operator/templates/task_synthesis.tmpl +0 -98
- data/lib/language_operator/ux/base.rb +0 -81
- data/lib/language_operator/ux/concerns/README.md +0 -155
- data/lib/language_operator/ux/concerns/headings.rb +0 -90
- data/lib/language_operator/ux/create_agent.rb +0 -255
- data/lib/language_operator/ux/create_model.rb +0 -267
- data/lib/language_operator/ux/quickstart.rb +0 -594
- data/synth/003/agent.rb +0 -41
- data/synth/003/output.log +0 -68
- /data/docs/{architecture/agent-runtime.md → agent-internals.md} +0 -0
- /data/docs/{dsl/chat-endpoints.md → chat-endpoints.md} +0 -0
- /data/docs/{dsl/SCHEMA_VERSION.md → schema-versioning.md} +0 -0
|
@@ -1,1259 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require 'json'
|
|
4
|
-
require 'yaml'
|
|
5
|
-
require_relative '../base_command'
|
|
6
|
-
require_relative '../formatters/progress_formatter'
|
|
7
|
-
require_relative '../../dsl/schema'
|
|
8
|
-
|
|
9
|
-
module LanguageOperator
|
|
10
|
-
module CLI
|
|
11
|
-
module Commands
|
|
12
|
-
# System commands for schema introspection and metadata
|
|
13
|
-
class System < BaseCommand
|
|
14
|
-
desc 'schema', 'Export the DSL schema in various formats'
|
|
15
|
-
long_desc <<-DESC
|
|
16
|
-
Export the Language Operator Agent DSL schema in various formats.
|
|
17
|
-
|
|
18
|
-
The schema documents all available DSL methods, parameters, validation
|
|
19
|
-
patterns, and structure. Useful for template validation, documentation
|
|
20
|
-
generation, and IDE autocomplete.
|
|
21
|
-
|
|
22
|
-
Examples:
|
|
23
|
-
# Export JSON schema (default)
|
|
24
|
-
aictl system schema
|
|
25
|
-
|
|
26
|
-
# Export as YAML
|
|
27
|
-
aictl system schema --format yaml
|
|
28
|
-
|
|
29
|
-
# Export OpenAPI 3.0 specification
|
|
30
|
-
aictl system schema --format openapi
|
|
31
|
-
|
|
32
|
-
# Show schema version only
|
|
33
|
-
aictl system schema --version
|
|
34
|
-
|
|
35
|
-
# Save to file
|
|
36
|
-
aictl system schema > schema.json
|
|
37
|
-
aictl system schema --format openapi > openapi.json
|
|
38
|
-
DESC
|
|
39
|
-
option :format, type: :string, default: 'json', desc: 'Output format (json, yaml, openapi)'
|
|
40
|
-
option :version, type: :boolean, default: false, desc: 'Show schema version only'
|
|
41
|
-
def schema
|
|
42
|
-
handle_command_error('generate schema') do
|
|
43
|
-
# Handle version flag
|
|
44
|
-
if options[:version]
|
|
45
|
-
puts Dsl::Schema.version
|
|
46
|
-
return
|
|
47
|
-
end
|
|
48
|
-
|
|
49
|
-
# Generate schema based on format
|
|
50
|
-
format = options[:format].downcase
|
|
51
|
-
case format
|
|
52
|
-
when 'json'
|
|
53
|
-
output_json_schema
|
|
54
|
-
when 'yaml'
|
|
55
|
-
output_yaml_schema
|
|
56
|
-
when 'openapi'
|
|
57
|
-
output_openapi_schema
|
|
58
|
-
else
|
|
59
|
-
Formatters::ProgressFormatter.error("Invalid format: #{format}")
|
|
60
|
-
puts
|
|
61
|
-
puts 'Supported formats: json, yaml, openapi'
|
|
62
|
-
exit 1
|
|
63
|
-
end
|
|
64
|
-
end
|
|
65
|
-
end
|
|
66
|
-
|
|
67
|
-
no_commands do
|
|
68
|
-
# Output JSON Schema v7
|
|
69
|
-
def output_json_schema
|
|
70
|
-
schema = Dsl::Schema.to_json_schema
|
|
71
|
-
puts JSON.pretty_generate(schema)
|
|
72
|
-
end
|
|
73
|
-
|
|
74
|
-
# Output YAML Schema
|
|
75
|
-
def output_yaml_schema
|
|
76
|
-
schema = Dsl::Schema.to_json_schema
|
|
77
|
-
puts YAML.dump(schema.transform_keys(&:to_s))
|
|
78
|
-
end
|
|
79
|
-
|
|
80
|
-
# Output OpenAPI 3.0 specification
|
|
81
|
-
def output_openapi_schema
|
|
82
|
-
spec = Dsl::Schema.to_openapi
|
|
83
|
-
puts JSON.pretty_generate(spec)
|
|
84
|
-
end
|
|
85
|
-
end
|
|
86
|
-
|
|
87
|
-
desc 'validate_template', 'Validate synthesis template against DSL schema'
|
|
88
|
-
long_desc <<-DESC
|
|
89
|
-
Validate a synthesis template file against the DSL schema.
|
|
90
|
-
|
|
91
|
-
Extracts Ruby code examples from the template and validates each example
|
|
92
|
-
against the Language Operator Agent DSL schema. Checks for dangerous
|
|
93
|
-
methods, syntax errors, and compliance with safe coding practices.
|
|
94
|
-
|
|
95
|
-
Examples:
|
|
96
|
-
# Validate a custom template file
|
|
97
|
-
aictl system validate_template --template /path/to/template.tmpl
|
|
98
|
-
|
|
99
|
-
# Validate the bundled agent template (default)
|
|
100
|
-
aictl system validate_template
|
|
101
|
-
|
|
102
|
-
# Validate the bundled persona template
|
|
103
|
-
aictl system validate_template --type persona
|
|
104
|
-
|
|
105
|
-
# Verbose output with all violations
|
|
106
|
-
aictl system validate_template --template mytemplate.tmpl --verbose
|
|
107
|
-
DESC
|
|
108
|
-
option :template, type: :string, desc: 'Path to template file (defaults to bundled template)'
|
|
109
|
-
option :type, type: :string, default: 'agent', desc: 'Template type if using bundled template (agent, persona)'
|
|
110
|
-
option :verbose, type: :boolean, default: false, desc: 'Show detailed violation information'
|
|
111
|
-
def validate_template
|
|
112
|
-
handle_command_error('validate template') do
|
|
113
|
-
# Determine template source
|
|
114
|
-
if options[:template]
|
|
115
|
-
# Load custom template from file
|
|
116
|
-
unless File.exist?(options[:template])
|
|
117
|
-
Formatters::ProgressFormatter.error("Template file not found: #{options[:template]}")
|
|
118
|
-
exit 1
|
|
119
|
-
end
|
|
120
|
-
template_content = File.read(options[:template])
|
|
121
|
-
template_name = File.basename(options[:template])
|
|
122
|
-
else
|
|
123
|
-
# Load bundled template
|
|
124
|
-
template_type = options[:type].downcase
|
|
125
|
-
unless %w[agent persona].include?(template_type)
|
|
126
|
-
Formatters::ProgressFormatter.error("Invalid template type: #{template_type}")
|
|
127
|
-
puts
|
|
128
|
-
puts 'Supported types: agent, persona'
|
|
129
|
-
exit 1
|
|
130
|
-
end
|
|
131
|
-
template_content = load_bundled_template(template_type)
|
|
132
|
-
template_name = "bundled #{template_type} template"
|
|
133
|
-
end
|
|
134
|
-
|
|
135
|
-
# Display header
|
|
136
|
-
puts "Validating template: #{template_name}"
|
|
137
|
-
puts '=' * 60
|
|
138
|
-
puts
|
|
139
|
-
|
|
140
|
-
# Extract code examples
|
|
141
|
-
code_examples = extract_code_examples(template_content)
|
|
142
|
-
|
|
143
|
-
if code_examples.empty?
|
|
144
|
-
Formatters::ProgressFormatter.warn('No Ruby code examples found in template')
|
|
145
|
-
puts
|
|
146
|
-
puts 'Templates should contain Ruby code blocks like:'
|
|
147
|
-
puts '```ruby'
|
|
148
|
-
puts 'agent "my-agent" do'
|
|
149
|
-
puts ' # ...'
|
|
150
|
-
puts 'end'
|
|
151
|
-
puts '```'
|
|
152
|
-
exit 1
|
|
153
|
-
end
|
|
154
|
-
|
|
155
|
-
puts "Found #{code_examples.size} code example(s)"
|
|
156
|
-
puts
|
|
157
|
-
|
|
158
|
-
# Validate each example
|
|
159
|
-
all_valid = true
|
|
160
|
-
code_examples.each_with_index do |example, idx|
|
|
161
|
-
puts "Example #{idx + 1} (starting at line #{example[:start_line]}):"
|
|
162
|
-
puts '-' * 40
|
|
163
|
-
|
|
164
|
-
result = validate_code_against_schema(example[:code])
|
|
165
|
-
|
|
166
|
-
if result[:valid] && result[:warnings].empty?
|
|
167
|
-
Formatters::ProgressFormatter.success('Valid - No issues found')
|
|
168
|
-
elsif result[:valid]
|
|
169
|
-
Formatters::ProgressFormatter.success('Valid - With warnings')
|
|
170
|
-
result[:warnings].each do |warn|
|
|
171
|
-
line = example[:start_line] + (warn[:location] || 0)
|
|
172
|
-
puts " ⚠ Line #{line}: #{warn[:message]}"
|
|
173
|
-
end
|
|
174
|
-
else
|
|
175
|
-
Formatters::ProgressFormatter.error('Invalid - Violations detected')
|
|
176
|
-
result[:errors].each do |err|
|
|
177
|
-
line = example[:start_line] + (err[:location] || 0)
|
|
178
|
-
puts " ✗ Line #{line}: #{err[:message]}"
|
|
179
|
-
puts " Type: #{err[:type]}" if options[:verbose]
|
|
180
|
-
end
|
|
181
|
-
all_valid = false
|
|
182
|
-
end
|
|
183
|
-
|
|
184
|
-
puts
|
|
185
|
-
end
|
|
186
|
-
|
|
187
|
-
# Final summary
|
|
188
|
-
puts '=' * 60
|
|
189
|
-
if all_valid
|
|
190
|
-
Formatters::ProgressFormatter.success('All examples are valid')
|
|
191
|
-
exit 0
|
|
192
|
-
else
|
|
193
|
-
Formatters::ProgressFormatter.error('Validation failed')
|
|
194
|
-
puts
|
|
195
|
-
puts 'Fix the violations above and run validation again.'
|
|
196
|
-
exit 1
|
|
197
|
-
end
|
|
198
|
-
end
|
|
199
|
-
end
|
|
200
|
-
|
|
201
|
-
desc 'synthesize [INSTRUCTIONS]', 'Synthesize agent code from natural language instructions'
|
|
202
|
-
long_desc <<-DESC
|
|
203
|
-
Synthesize agent code by converting natural language instructions
|
|
204
|
-
into Ruby DSL code without creating an actual agent.
|
|
205
|
-
|
|
206
|
-
This command uses a LanguageModel resource from your cluster to generate
|
|
207
|
-
agent code. If --model is not specified, the first available model will
|
|
208
|
-
be auto-selected.
|
|
209
|
-
|
|
210
|
-
Instructions can be provided either as a command argument or via STDIN.
|
|
211
|
-
If no argument is provided, the command will read from STDIN.
|
|
212
|
-
|
|
213
|
-
This command helps you validate your instructions and understand how the
|
|
214
|
-
synthesis engine interprets them. Use --dry-run to see the prompt that
|
|
215
|
-
would be sent to the LLM, or run without it to generate actual code.
|
|
216
|
-
|
|
217
|
-
Examples:
|
|
218
|
-
# Test with dry-run (show prompt only)
|
|
219
|
-
aictl system synthesize "Monitor GitHub issues daily" --dry-run
|
|
220
|
-
|
|
221
|
-
# Generate code from instructions (auto-selects first available model)
|
|
222
|
-
aictl system synthesize "Send daily reports to Slack"
|
|
223
|
-
|
|
224
|
-
# Use a specific cluster model
|
|
225
|
-
aictl system synthesize "Process webhooks from GitHub" --model my-claude
|
|
226
|
-
|
|
227
|
-
# Output raw code without formatting (useful for piping to files)
|
|
228
|
-
aictl system synthesize "Monitor logs" --raw > agent.rb
|
|
229
|
-
|
|
230
|
-
# Read instructions from STDIN
|
|
231
|
-
cat instructions.txt | aictl system synthesize > agent.rb
|
|
232
|
-
|
|
233
|
-
# Read from STDIN with pipe
|
|
234
|
-
echo "Monitor GitHub issues" | aictl system synthesize --raw
|
|
235
|
-
|
|
236
|
-
# Specify custom agent name and tools
|
|
237
|
-
aictl system synthesize "Process webhooks from GitHub" \\
|
|
238
|
-
--agent-name github-processor \\
|
|
239
|
-
--tools github,slack \\
|
|
240
|
-
--model my-gpt4
|
|
241
|
-
DESC
|
|
242
|
-
option :agent_name, type: :string, default: 'test-agent', desc: 'Name for the test agent'
|
|
243
|
-
option :tools, type: :string, desc: 'Comma-separated list of available tools'
|
|
244
|
-
option :models, type: :string, desc: 'Comma-separated list of available models (from cluster)'
|
|
245
|
-
option :model, type: :string, desc: 'Model to use for synthesis (defaults to first available in cluster)'
|
|
246
|
-
option :dry_run, type: :boolean, default: false, desc: 'Show prompt without calling LLM'
|
|
247
|
-
option :raw, type: :boolean, default: false, desc: 'Output only the raw code without formatting'
|
|
248
|
-
def synthesize(instructions = nil)
|
|
249
|
-
handle_command_error('synthesize agent') do
|
|
250
|
-
# Read instructions from STDIN if not provided as argument
|
|
251
|
-
if instructions.nil? || instructions.strip.empty?
|
|
252
|
-
if $stdin.tty?
|
|
253
|
-
Formatters::ProgressFormatter.error('No instructions provided')
|
|
254
|
-
puts
|
|
255
|
-
puts 'Provide instructions either as an argument or via STDIN:'
|
|
256
|
-
puts ' aictl system synthesize "Your instructions here"'
|
|
257
|
-
puts ' cat instructions.txt | aictl system synthesize'
|
|
258
|
-
exit 1
|
|
259
|
-
else
|
|
260
|
-
instructions = $stdin.read.strip
|
|
261
|
-
if instructions.empty?
|
|
262
|
-
Formatters::ProgressFormatter.error('No instructions provided')
|
|
263
|
-
puts
|
|
264
|
-
puts 'Provide instructions either as an argument or via STDIN:'
|
|
265
|
-
puts ' aictl system synthesize "Your instructions here"'
|
|
266
|
-
puts ' cat instructions.txt | aictl system synthesize'
|
|
267
|
-
exit 1
|
|
268
|
-
end
|
|
269
|
-
end
|
|
270
|
-
end
|
|
271
|
-
# Select model to use for synthesis
|
|
272
|
-
selected_model = select_synthesis_model
|
|
273
|
-
|
|
274
|
-
# Load synthesis template
|
|
275
|
-
template_content = load_bundled_template('agent')
|
|
276
|
-
|
|
277
|
-
# Detect temporal intent from instructions
|
|
278
|
-
temporal_intent = detect_temporal_intent(instructions)
|
|
279
|
-
|
|
280
|
-
# Prepare template data
|
|
281
|
-
template_data = {
|
|
282
|
-
'Instructions' => instructions,
|
|
283
|
-
'AgentName' => options[:agent_name],
|
|
284
|
-
'ToolsList' => format_tools_list(options[:tools]),
|
|
285
|
-
'ModelsList' => format_models_list(options[:models]),
|
|
286
|
-
'TemporalIntent' => temporal_intent,
|
|
287
|
-
'PersonaSection' => '',
|
|
288
|
-
'ScheduleSection' => temporal_intent == 'scheduled' ? ' schedule "0 */1 * * *" # Example hourly schedule' : '',
|
|
289
|
-
'ScheduleRules' => temporal_intent == 'scheduled' ? "\n2. Include schedule with cron expression\n3. Set mode to :scheduled\n4. " : "\n2. ",
|
|
290
|
-
'ConstraintsSection' => '',
|
|
291
|
-
'ErrorContext' => nil
|
|
292
|
-
}
|
|
293
|
-
|
|
294
|
-
# Render template (Go-style template syntax)
|
|
295
|
-
rendered_prompt = render_go_template(template_content, template_data)
|
|
296
|
-
|
|
297
|
-
if options[:dry_run]
|
|
298
|
-
# Show the prompt that would be sent
|
|
299
|
-
puts 'Synthesis Prompt Preview'
|
|
300
|
-
puts '=' * 80
|
|
301
|
-
puts
|
|
302
|
-
puts rendered_prompt
|
|
303
|
-
puts
|
|
304
|
-
puts '=' * 80
|
|
305
|
-
Formatters::ProgressFormatter.success('Dry-run complete - prompt displayed above')
|
|
306
|
-
return
|
|
307
|
-
end
|
|
308
|
-
|
|
309
|
-
# Call LLM to generate code (no output - just do it)
|
|
310
|
-
llm_response = call_llm_for_synthesis(rendered_prompt, selected_model)
|
|
311
|
-
|
|
312
|
-
# Extract Ruby code from response
|
|
313
|
-
generated_code = extract_ruby_code(llm_response)
|
|
314
|
-
|
|
315
|
-
if generated_code.nil?
|
|
316
|
-
Formatters::ProgressFormatter.error('Failed to extract Ruby code from LLM response')
|
|
317
|
-
puts
|
|
318
|
-
puts 'LLM Response:'
|
|
319
|
-
puts llm_response
|
|
320
|
-
exit 1
|
|
321
|
-
end
|
|
322
|
-
|
|
323
|
-
# Handle raw output
|
|
324
|
-
if options[:raw]
|
|
325
|
-
puts generated_code
|
|
326
|
-
return
|
|
327
|
-
end
|
|
328
|
-
|
|
329
|
-
# Display formatted code
|
|
330
|
-
require 'rouge'
|
|
331
|
-
formatter = Rouge::Formatters::Terminal256.new
|
|
332
|
-
lexer = Rouge::Lexers::Ruby.new
|
|
333
|
-
highlighted_code = formatter.format(lexer.lex(generated_code))
|
|
334
|
-
|
|
335
|
-
puts highlighted_code
|
|
336
|
-
end
|
|
337
|
-
end
|
|
338
|
-
|
|
339
|
-
desc 'exec [AGENT_FILE]', 'Execute an agent file in a test pod on the cluster'
|
|
340
|
-
long_desc <<-DESC
|
|
341
|
-
Deploy and execute an agent file in a temporary test pod on the Kubernetes cluster.
|
|
342
|
-
|
|
343
|
-
This command creates a ConfigMap with the agent code, deploys a test pod,
|
|
344
|
-
streams the logs until completion, and cleans up all resources.
|
|
345
|
-
|
|
346
|
-
The agent code is mounted at /etc/agent/code/agent.rb as expected by the agent runtime.
|
|
347
|
-
|
|
348
|
-
Agent code can be provided either as a file path or via STDIN.
|
|
349
|
-
If no file path is provided, the command will read from STDIN.
|
|
350
|
-
|
|
351
|
-
Examples:
|
|
352
|
-
# Execute a synthesized agent file
|
|
353
|
-
aictl system exec agent.rb
|
|
354
|
-
|
|
355
|
-
# Execute with a custom agent name
|
|
356
|
-
aictl system exec agent.rb --agent-name my-test
|
|
357
|
-
|
|
358
|
-
# Keep the pod after execution for debugging
|
|
359
|
-
aictl system exec agent.rb --keep-pod
|
|
360
|
-
|
|
361
|
-
# Use a different agent image
|
|
362
|
-
aictl system exec agent.rb --image ghcr.io/language-operator/agent:v0.1.0
|
|
363
|
-
|
|
364
|
-
# Read agent code from STDIN
|
|
365
|
-
cat agent.rb | aictl system exec
|
|
366
|
-
|
|
367
|
-
# Pipe synthesized code directly to execution
|
|
368
|
-
cat agent.txt | aictl system synthesize | aictl system exec
|
|
369
|
-
DESC
|
|
370
|
-
option :agent_name, type: :string, default: 'test-agent', desc: 'Name for the test agent pod'
|
|
371
|
-
option :keep_pod, type: :boolean, default: false, desc: 'Keep the pod after execution (for debugging)'
|
|
372
|
-
option :image, type: :string, default: 'ghcr.io/language-operator/agent:latest', desc: 'Agent container image'
|
|
373
|
-
option :timeout, type: :numeric, default: 300, desc: 'Timeout in seconds for agent execution'
|
|
374
|
-
def exec(agent_file = nil)
|
|
375
|
-
handle_command_error('exec agent') do
|
|
376
|
-
# Verify cluster is selected
|
|
377
|
-
unless ctx.client
|
|
378
|
-
Formatters::ProgressFormatter.error('No cluster context available')
|
|
379
|
-
puts
|
|
380
|
-
puts 'Please configure kubectl with a valid cluster context:'
|
|
381
|
-
puts ' kubectl config get-contexts'
|
|
382
|
-
puts ' kubectl config use-context <context-name>'
|
|
383
|
-
exit 1
|
|
384
|
-
end
|
|
385
|
-
|
|
386
|
-
# Read agent code from file or STDIN
|
|
387
|
-
agent_code = if agent_file && !agent_file.strip.empty?
|
|
388
|
-
# Read from file
|
|
389
|
-
unless File.exist?(agent_file)
|
|
390
|
-
Formatters::ProgressFormatter.error("Agent file not found: #{agent_file}")
|
|
391
|
-
exit 1
|
|
392
|
-
end
|
|
393
|
-
File.read(agent_file)
|
|
394
|
-
elsif $stdin.tty?
|
|
395
|
-
# Read from STDIN
|
|
396
|
-
Formatters::ProgressFormatter.error('No agent code provided')
|
|
397
|
-
puts
|
|
398
|
-
puts 'Provide agent code either as a file or via STDIN:'
|
|
399
|
-
puts ' aictl system exec agent.rb'
|
|
400
|
-
puts ' cat agent.rb | aictl system exec'
|
|
401
|
-
exit 1
|
|
402
|
-
else
|
|
403
|
-
code = $stdin.read.strip
|
|
404
|
-
if code.empty?
|
|
405
|
-
Formatters::ProgressFormatter.error('No agent code provided')
|
|
406
|
-
puts
|
|
407
|
-
puts 'Provide agent code either as a file or via STDIN:'
|
|
408
|
-
puts ' aictl system exec agent.rb'
|
|
409
|
-
puts ' cat agent.rb | aictl system exec'
|
|
410
|
-
exit 1
|
|
411
|
-
end
|
|
412
|
-
code
|
|
413
|
-
end
|
|
414
|
-
|
|
415
|
-
# Generate unique names
|
|
416
|
-
timestamp = Time.now.to_i
|
|
417
|
-
configmap_name = "#{options[:agent_name]}-code-#{timestamp}"
|
|
418
|
-
pod_name = "#{options[:agent_name]}-#{timestamp}"
|
|
419
|
-
|
|
420
|
-
begin
|
|
421
|
-
# Create ConfigMap with agent code
|
|
422
|
-
Formatters::ProgressFormatter.with_spinner('Creating ConfigMap with agent code') do
|
|
423
|
-
create_agent_configmap(configmap_name, agent_code)
|
|
424
|
-
end
|
|
425
|
-
|
|
426
|
-
# Create test pod
|
|
427
|
-
Formatters::ProgressFormatter.with_spinner('Creating test pod') do
|
|
428
|
-
create_test_pod(pod_name, configmap_name, options[:image])
|
|
429
|
-
end
|
|
430
|
-
|
|
431
|
-
# Wait for pod to be ready or running
|
|
432
|
-
Formatters::ProgressFormatter.with_spinner('Waiting for pod to start') do
|
|
433
|
-
wait_for_pod_start(pod_name, timeout: 60)
|
|
434
|
-
end
|
|
435
|
-
|
|
436
|
-
# Stream logs until pod completes
|
|
437
|
-
stream_pod_logs(pod_name, timeout: options[:timeout])
|
|
438
|
-
|
|
439
|
-
# Wait for pod to fully terminate and get final status
|
|
440
|
-
exit_code = wait_for_pod_termination(pod_name)
|
|
441
|
-
|
|
442
|
-
if exit_code&.zero?
|
|
443
|
-
Formatters::ProgressFormatter.success('Agent completed successfully')
|
|
444
|
-
elsif exit_code
|
|
445
|
-
Formatters::ProgressFormatter.error("Agent failed with exit code: #{exit_code}")
|
|
446
|
-
else
|
|
447
|
-
Formatters::ProgressFormatter.warn('Unable to determine pod exit status')
|
|
448
|
-
end
|
|
449
|
-
ensure
|
|
450
|
-
# Clean up resources unless --keep-pod
|
|
451
|
-
puts
|
|
452
|
-
puts
|
|
453
|
-
if options[:keep_pod]
|
|
454
|
-
Formatters::ProgressFormatter.info('Resources kept for debugging:')
|
|
455
|
-
puts " Pod: #{pod_name}"
|
|
456
|
-
puts " ConfigMap: #{configmap_name}"
|
|
457
|
-
puts
|
|
458
|
-
puts "To view logs: kubectl logs -n #{ctx.namespace} #{pod_name}"
|
|
459
|
-
puts "To delete: kubectl delete pod,configmap -n #{ctx.namespace} #{pod_name} #{configmap_name}"
|
|
460
|
-
else
|
|
461
|
-
Formatters::ProgressFormatter.with_spinner('Cleaning up resources') do
|
|
462
|
-
delete_pod(pod_name)
|
|
463
|
-
delete_configmap(configmap_name)
|
|
464
|
-
end
|
|
465
|
-
end
|
|
466
|
-
end
|
|
467
|
-
end
|
|
468
|
-
end
|
|
469
|
-
|
|
470
|
-
desc 'synthesis-template', 'Export synthesis templates for agent code generation'
|
|
471
|
-
long_desc <<-DESC
|
|
472
|
-
Export the synthesis templates used by the Language Operator to generate
|
|
473
|
-
agent code from natural language instructions.
|
|
474
|
-
|
|
475
|
-
These templates are used by the operator's synthesis engine to convert
|
|
476
|
-
user instructions into executable Ruby DSL code.
|
|
477
|
-
|
|
478
|
-
Examples:
|
|
479
|
-
# Export agent synthesis template (default)
|
|
480
|
-
aictl system synthesis-template
|
|
481
|
-
|
|
482
|
-
# Export persona distillation template
|
|
483
|
-
aictl system synthesis-template --type persona
|
|
484
|
-
|
|
485
|
-
# Export as JSON with schema included
|
|
486
|
-
aictl system synthesis-template --format json --with-schema
|
|
487
|
-
|
|
488
|
-
# Export as YAML
|
|
489
|
-
aictl system synthesis-template --format yaml
|
|
490
|
-
|
|
491
|
-
# Validate template syntax
|
|
492
|
-
aictl system synthesis-template --validate
|
|
493
|
-
|
|
494
|
-
# Save to file
|
|
495
|
-
aictl system synthesis-template > agent_synthesis.tmpl
|
|
496
|
-
DESC
|
|
497
|
-
option :format, type: :string, default: 'template', desc: 'Output format (template, json, yaml)'
|
|
498
|
-
option :type, type: :string, default: 'agent', desc: 'Template type (agent, persona)'
|
|
499
|
-
option :with_schema, type: :boolean, default: false, desc: 'Include DSL schema in output'
|
|
500
|
-
option :validate, type: :boolean, default: false, desc: 'Validate template syntax'
|
|
501
|
-
def synthesis_template
|
|
502
|
-
handle_command_error('load template') do
|
|
503
|
-
# Validate type
|
|
504
|
-
template_type = options[:type].downcase
|
|
505
|
-
unless %w[agent persona].include?(template_type)
|
|
506
|
-
Formatters::ProgressFormatter.error("Invalid template type: #{template_type}")
|
|
507
|
-
puts
|
|
508
|
-
puts 'Supported types: agent, persona'
|
|
509
|
-
exit 1
|
|
510
|
-
end
|
|
511
|
-
|
|
512
|
-
# Load template
|
|
513
|
-
template_content = load_template(template_type)
|
|
514
|
-
|
|
515
|
-
# Validate if requested
|
|
516
|
-
if options[:validate]
|
|
517
|
-
validation_result = validate_template_content(template_content, template_type)
|
|
518
|
-
|
|
519
|
-
# Display warnings if any
|
|
520
|
-
unless validation_result[:warnings].empty?
|
|
521
|
-
Formatters::ProgressFormatter.warn('Template validation warnings:')
|
|
522
|
-
validation_result[:warnings].each do |warning|
|
|
523
|
-
puts " ⚠ #{warning}"
|
|
524
|
-
end
|
|
525
|
-
puts
|
|
526
|
-
end
|
|
527
|
-
|
|
528
|
-
# Display errors and exit if validation failed
|
|
529
|
-
if validation_result[:valid]
|
|
530
|
-
Formatters::ProgressFormatter.success('Template validation passed')
|
|
531
|
-
return
|
|
532
|
-
else
|
|
533
|
-
Formatters::ProgressFormatter.error('Template validation failed:')
|
|
534
|
-
validation_result[:errors].each do |error|
|
|
535
|
-
puts " ✗ #{error}"
|
|
536
|
-
end
|
|
537
|
-
exit 1
|
|
538
|
-
end
|
|
539
|
-
end
|
|
540
|
-
|
|
541
|
-
# Generate output based on format
|
|
542
|
-
format = options[:format].downcase
|
|
543
|
-
case format
|
|
544
|
-
when 'template'
|
|
545
|
-
output_template_format(template_content)
|
|
546
|
-
when 'json'
|
|
547
|
-
output_json_format(template_content, template_type)
|
|
548
|
-
when 'yaml'
|
|
549
|
-
output_yaml_format(template_content, template_type)
|
|
550
|
-
else
|
|
551
|
-
Formatters::ProgressFormatter.error("Invalid format: #{format}")
|
|
552
|
-
puts
|
|
553
|
-
puts 'Supported formats: template, json, yaml'
|
|
554
|
-
exit 1
|
|
555
|
-
end
|
|
556
|
-
end
|
|
557
|
-
end
|
|
558
|
-
|
|
559
|
-
private
|
|
560
|
-
|
|
561
|
-
# Render Go-style template ({{.Variable}})
|
|
562
|
-
# Simplified implementation for basic variable substitution
|
|
563
|
-
def render_go_template(template, data)
|
|
564
|
-
result = template.dup
|
|
565
|
-
|
|
566
|
-
# Handle {{if .ErrorContext}} - remove this section for test-synthesis
|
|
567
|
-
result.gsub!(/{{if \.ErrorContext}}.*?{{else}}/m, '')
|
|
568
|
-
result.gsub!(/{{end}}/, '')
|
|
569
|
-
|
|
570
|
-
# Replace simple variables {{.Variable}}
|
|
571
|
-
data.each do |key, value|
|
|
572
|
-
result.gsub!("{{.#{key}}}", value.to_s)
|
|
573
|
-
end
|
|
574
|
-
|
|
575
|
-
result
|
|
576
|
-
end
|
|
577
|
-
|
|
578
|
-
# Detect temporal intent from instructions (scheduled vs autonomous)
|
|
579
|
-
def detect_temporal_intent(instructions)
|
|
580
|
-
temporal_keywords = {
|
|
581
|
-
scheduled: %w[daily weekly hourly monthly schedule cron every day week hour minute],
|
|
582
|
-
autonomous: %w[monitor watch continuously constantly always loop]
|
|
583
|
-
}
|
|
584
|
-
|
|
585
|
-
instructions_lower = instructions.downcase
|
|
586
|
-
|
|
587
|
-
# Check for scheduled keywords
|
|
588
|
-
scheduled_matches = temporal_keywords[:scheduled].count { |keyword| instructions_lower.include?(keyword) }
|
|
589
|
-
autonomous_matches = temporal_keywords[:autonomous].count { |keyword| instructions_lower.include?(keyword) }
|
|
590
|
-
|
|
591
|
-
scheduled_matches > autonomous_matches ? 'scheduled' : 'autonomous'
|
|
592
|
-
end
|
|
593
|
-
|
|
594
|
-
# Format tools list for template
|
|
595
|
-
def format_tools_list(tools_str)
|
|
596
|
-
return 'No tools specified' if tools_str.nil? || tools_str.strip.empty?
|
|
597
|
-
|
|
598
|
-
tools = tools_str.split(',').map(&:strip)
|
|
599
|
-
tools.map { |tool| "- #{tool}" }.join("\n")
|
|
600
|
-
end
|
|
601
|
-
|
|
602
|
-
# Format models list for template
|
|
603
|
-
def format_models_list(models_str)
|
|
604
|
-
# If not specified, try to detect from cluster
|
|
605
|
-
if models_str.nil? || models_str.strip.empty?
|
|
606
|
-
models = detect_available_models
|
|
607
|
-
return models.map { |model| "- #{model}" }.join("\n") unless models.empty?
|
|
608
|
-
|
|
609
|
-
return 'No models available (run: aictl model list)'
|
|
610
|
-
end
|
|
611
|
-
|
|
612
|
-
models = models_str.split(',').map(&:strip)
|
|
613
|
-
models.map { |model| "- #{model}" }.join("\n")
|
|
614
|
-
end
|
|
615
|
-
|
|
616
|
-
# Detect available models from cluster
|
|
617
|
-
def detect_available_models
|
|
618
|
-
models = ctx.client.list_resources('LanguageModel', namespace: ctx.namespace)
|
|
619
|
-
models.map { |m| m.dig('metadata', 'name') }
|
|
620
|
-
rescue StandardError => e
|
|
621
|
-
Formatters::ProgressFormatter.error("Failed to list models from cluster: #{e.message}")
|
|
622
|
-
[]
|
|
623
|
-
end
|
|
624
|
-
|
|
625
|
-
# Select model to use for synthesis
|
|
626
|
-
def select_synthesis_model
|
|
627
|
-
# If --model option specified, use it
|
|
628
|
-
return options[:model] if options[:model]
|
|
629
|
-
|
|
630
|
-
# Otherwise, auto-select from available cluster models
|
|
631
|
-
available_models = detect_available_models
|
|
632
|
-
|
|
633
|
-
if available_models.empty?
|
|
634
|
-
Formatters::ProgressFormatter.error('No models available in cluster')
|
|
635
|
-
puts
|
|
636
|
-
puts 'Please create a model first:'
|
|
637
|
-
puts ' aictl model create'
|
|
638
|
-
puts
|
|
639
|
-
puts 'Or list existing models:'
|
|
640
|
-
puts ' aictl model list'
|
|
641
|
-
exit 1
|
|
642
|
-
end
|
|
643
|
-
|
|
644
|
-
# Auto-select first available model (silently)
|
|
645
|
-
available_models.first
|
|
646
|
-
end
|
|
647
|
-
|
|
648
|
-
# Get endpoint for a cluster model
|
|
649
|
-
def get_model_endpoint(model_name)
|
|
650
|
-
# For cluster models, we use the service endpoint
|
|
651
|
-
# The service is typically named the same as the model and listens on port 4000
|
|
652
|
-
"http://#{model_name}.#{ctx.namespace}.svc.cluster.local:4000/v1"
|
|
653
|
-
end
|
|
654
|
-
|
|
655
|
-
# Call LLM to generate code from synthesis prompt using cluster model
|
|
656
|
-
def call_llm_for_synthesis(prompt, model_name)
|
|
657
|
-
require 'json'
|
|
658
|
-
require 'faraday'
|
|
659
|
-
|
|
660
|
-
# Get model resource
|
|
661
|
-
model = get_resource_or_exit('LanguageModel', model_name)
|
|
662
|
-
model_id = model.dig('spec', 'modelName')
|
|
663
|
-
|
|
664
|
-
# Get the model's pod
|
|
665
|
-
pod = get_model_pod(model_name)
|
|
666
|
-
pod_name = pod.dig('metadata', 'name')
|
|
667
|
-
|
|
668
|
-
# Set up port-forward to access the model pod
|
|
669
|
-
port_forward_pid = nil
|
|
670
|
-
local_port = find_available_port
|
|
671
|
-
|
|
672
|
-
begin
|
|
673
|
-
# Start kubectl port-forward in background
|
|
674
|
-
port_forward_pid = start_port_forward(pod_name, local_port, 4000)
|
|
675
|
-
|
|
676
|
-
# Wait for port-forward to be ready
|
|
677
|
-
wait_for_port(local_port)
|
|
678
|
-
|
|
679
|
-
# Build the JSON payload for the chat completion request
|
|
680
|
-
payload = {
|
|
681
|
-
model: model_id,
|
|
682
|
-
messages: [{ role: 'user', content: prompt }],
|
|
683
|
-
max_tokens: 4000,
|
|
684
|
-
temperature: 0.3
|
|
685
|
-
}
|
|
686
|
-
|
|
687
|
-
# Make HTTP request using Faraday
|
|
688
|
-
conn = Faraday.new(url: "http://localhost:#{local_port}") do |f|
|
|
689
|
-
f.request :json
|
|
690
|
-
f.response :json
|
|
691
|
-
f.adapter Faraday.default_adapter
|
|
692
|
-
f.options.timeout = 120
|
|
693
|
-
f.options.open_timeout = 10
|
|
694
|
-
end
|
|
695
|
-
|
|
696
|
-
response = conn.post('/v1/chat/completions', payload)
|
|
697
|
-
|
|
698
|
-
# Parse response
|
|
699
|
-
result = response.body
|
|
700
|
-
|
|
701
|
-
if result['error']
|
|
702
|
-
error_msg = result['error']['message'] || result['error']
|
|
703
|
-
raise "Model error: #{error_msg}"
|
|
704
|
-
elsif !result['choices'] || result['choices'].empty?
|
|
705
|
-
raise "Unexpected response format: #{result.inspect}"
|
|
706
|
-
end
|
|
707
|
-
|
|
708
|
-
# Extract the content from the first choice
|
|
709
|
-
result.dig('choices', 0, 'message', 'content')
|
|
710
|
-
rescue Faraday::TimeoutError
|
|
711
|
-
raise 'LLM request timed out after 120 seconds'
|
|
712
|
-
rescue Faraday::ConnectionFailed => e
|
|
713
|
-
raise "Failed to connect to model: #{e.message}"
|
|
714
|
-
rescue StandardError => e
|
|
715
|
-
Formatters::ProgressFormatter.error("LLM call failed: #{e.message}")
|
|
716
|
-
puts
|
|
717
|
-
puts "Make sure the model '#{model_name}' is running: kubectl get pods -n #{ctx.namespace}"
|
|
718
|
-
exit 1
|
|
719
|
-
ensure
|
|
720
|
-
# Clean up port-forward process
|
|
721
|
-
cleanup_port_forward(port_forward_pid) if port_forward_pid
|
|
722
|
-
end
|
|
723
|
-
end
|
|
724
|
-
|
|
725
|
-
# Get the pod for a model
|
|
726
|
-
def get_model_pod(model_name)
|
|
727
|
-
# Get the deployment for the model
|
|
728
|
-
deployment = ctx.client.get_resource('Deployment', model_name, ctx.namespace)
|
|
729
|
-
labels = deployment.dig('spec', 'selector', 'matchLabels')
|
|
730
|
-
|
|
731
|
-
raise "Deployment '#{model_name}' has no selector labels" if labels.nil?
|
|
732
|
-
|
|
733
|
-
# Convert to hash if needed
|
|
734
|
-
labels_hash = labels.respond_to?(:to_h) ? labels.to_h : labels
|
|
735
|
-
raise "Deployment '#{model_name}' has empty selector labels" if labels_hash.empty?
|
|
736
|
-
|
|
737
|
-
label_selector = labels_hash.map { |k, v| "#{k}=#{v}" }.join(',')
|
|
738
|
-
|
|
739
|
-
# Find a running pod
|
|
740
|
-
pods = ctx.client.list_resources('Pod', namespace: ctx.namespace, label_selector: label_selector)
|
|
741
|
-
raise "No pods found for model '#{model_name}'" if pods.empty?
|
|
742
|
-
|
|
743
|
-
running_pod = pods.find do |pod|
|
|
744
|
-
pod.dig('status', 'phase') == 'Running' &&
|
|
745
|
-
pod.dig('status', 'conditions')&.any? { |c| c['type'] == 'Ready' && c['status'] == 'True' }
|
|
746
|
-
end
|
|
747
|
-
|
|
748
|
-
if running_pod.nil?
|
|
749
|
-
pod_phases = pods.map { |p| p.dig('status', 'phase') }.join(', ')
|
|
750
|
-
raise "No running pods found. Pod phases: #{pod_phases}"
|
|
751
|
-
end
|
|
752
|
-
|
|
753
|
-
running_pod
|
|
754
|
-
rescue K8s::Error::NotFound
|
|
755
|
-
raise "Model deployment '#{model_name}' not found"
|
|
756
|
-
end
|
|
757
|
-
|
|
758
|
-
# Find an available local port for port-forwarding
|
|
759
|
-
def find_available_port
|
|
760
|
-
require 'socket'
|
|
761
|
-
|
|
762
|
-
# Try ports in the range 14000-14999
|
|
763
|
-
(14_000..14_999).each do |port|
|
|
764
|
-
server = TCPServer.new('127.0.0.1', port)
|
|
765
|
-
server.close
|
|
766
|
-
return port
|
|
767
|
-
rescue Errno::EADDRINUSE
|
|
768
|
-
# Port in use, try next
|
|
769
|
-
next
|
|
770
|
-
end
|
|
771
|
-
|
|
772
|
-
raise 'No available ports found in range 14000-14999'
|
|
773
|
-
end
|
|
774
|
-
|
|
775
|
-
# Start kubectl port-forward in background
|
|
776
|
-
def start_port_forward(pod_name, local_port, remote_port)
|
|
777
|
-
require 'English'
|
|
778
|
-
|
|
779
|
-
cmd = "kubectl port-forward -n #{ctx.namespace} #{pod_name} #{local_port}:#{remote_port}"
|
|
780
|
-
pid = spawn(cmd, out: '/dev/null', err: '/dev/null')
|
|
781
|
-
|
|
782
|
-
# Detach so it runs in background
|
|
783
|
-
Process.detach(pid)
|
|
784
|
-
|
|
785
|
-
pid
|
|
786
|
-
end
|
|
787
|
-
|
|
788
|
-
# Wait for port-forward to be ready
|
|
789
|
-
def wait_for_port(port, max_attempts: 30)
|
|
790
|
-
require 'socket'
|
|
791
|
-
|
|
792
|
-
max_attempts.times do
|
|
793
|
-
socket = TCPSocket.new('127.0.0.1', port)
|
|
794
|
-
socket.close
|
|
795
|
-
return true
|
|
796
|
-
rescue Errno::ECONNREFUSED, Errno::EHOSTUNREACH
|
|
797
|
-
sleep 0.1
|
|
798
|
-
end
|
|
799
|
-
|
|
800
|
-
raise "Port-forward to localhost:#{port} failed to become ready after #{max_attempts} attempts"
|
|
801
|
-
end
|
|
802
|
-
|
|
803
|
-
# Clean up port-forward process
|
|
804
|
-
def cleanup_port_forward(pid)
|
|
805
|
-
return unless pid
|
|
806
|
-
|
|
807
|
-
begin
|
|
808
|
-
Process.kill('TERM', pid)
|
|
809
|
-
Process.wait(pid, Process::WNOHANG)
|
|
810
|
-
rescue Errno::ESRCH
|
|
811
|
-
# Process already gone
|
|
812
|
-
rescue Errno::ECHILD
|
|
813
|
-
# Process already reaped
|
|
814
|
-
end
|
|
815
|
-
end
|
|
816
|
-
|
|
817
|
-
# Extract Ruby code from LLM response
|
|
818
|
-
# Looks for ```ruby ... ``` blocks
|
|
819
|
-
def extract_ruby_code(response)
|
|
820
|
-
# Match ```ruby ... ``` blocks
|
|
821
|
-
match = response.match(/```ruby\n(.*?)```/m)
|
|
822
|
-
return match[1].strip if match
|
|
823
|
-
|
|
824
|
-
# Try without language specifier
|
|
825
|
-
match = response.match(/```\n(.*?)```/m)
|
|
826
|
-
return match[1].strip if match
|
|
827
|
-
|
|
828
|
-
# If no code blocks, return nil
|
|
829
|
-
nil
|
|
830
|
-
end
|
|
831
|
-
|
|
832
|
-
# Load template from bundled gem or operator ConfigMap
|
|
833
|
-
def load_template(type)
|
|
834
|
-
# Try to fetch from operator ConfigMap first (if kubectl available)
|
|
835
|
-
template = fetch_from_operator(type)
|
|
836
|
-
return template if template
|
|
837
|
-
|
|
838
|
-
# Fall back to bundled template
|
|
839
|
-
load_bundled_template(type)
|
|
840
|
-
end
|
|
841
|
-
|
|
842
|
-
# Fetch template from operator ConfigMap via kubectl
|
|
843
|
-
def fetch_from_operator(type)
|
|
844
|
-
configmap_name = type == 'agent' ? 'agent-synthesis-template' : 'persona-distillation-template'
|
|
845
|
-
result = `kubectl get configmap #{configmap_name} -n language-operator-system -o jsonpath='{.data.template}' 2>/dev/null`
|
|
846
|
-
result.empty? ? nil : result
|
|
847
|
-
rescue StandardError
|
|
848
|
-
nil
|
|
849
|
-
end
|
|
850
|
-
|
|
851
|
-
# Load bundled template from gem
|
|
852
|
-
def load_bundled_template(type)
|
|
853
|
-
filename = type == 'agent' ? 'agent_synthesis.tmpl' : 'persona_distillation.tmpl'
|
|
854
|
-
template_path = File.join(__dir__, '..', '..', 'templates', filename)
|
|
855
|
-
File.read(template_path)
|
|
856
|
-
end
|
|
857
|
-
|
|
858
|
-
# Validate template syntax and structure
|
|
859
|
-
def validate_template_content(content, type)
|
|
860
|
-
errors = []
|
|
861
|
-
warnings = []
|
|
862
|
-
|
|
863
|
-
# Check for required placeholders based on type
|
|
864
|
-
required_placeholders = if type == 'agent'
|
|
865
|
-
%w[
|
|
866
|
-
Instructions ToolsList ModelsList AgentName TemporalIntent
|
|
867
|
-
]
|
|
868
|
-
else
|
|
869
|
-
%w[
|
|
870
|
-
PersonaName PersonaDescription PersonaSystemPrompt
|
|
871
|
-
AgentInstructions AgentTools
|
|
872
|
-
]
|
|
873
|
-
end
|
|
874
|
-
|
|
875
|
-
required_placeholders.each do |placeholder|
|
|
876
|
-
errors << "Missing required placeholder: {{.#{placeholder}}}" unless content.include?("{{.#{placeholder}}}")
|
|
877
|
-
end
|
|
878
|
-
|
|
879
|
-
# Check for balanced braces
|
|
880
|
-
open_braces = content.scan(/{{/).count
|
|
881
|
-
close_braces = content.scan(/}}/).count
|
|
882
|
-
errors << "Unbalanced template braces ({{ vs }}): #{open_braces} open, #{close_braces} close" if open_braces != close_braces
|
|
883
|
-
|
|
884
|
-
# Extract and validate Ruby code blocks
|
|
885
|
-
code_examples = extract_code_examples(content)
|
|
886
|
-
code_examples.each do |example|
|
|
887
|
-
code_result = validate_code_against_schema(example[:code])
|
|
888
|
-
unless code_result[:valid]
|
|
889
|
-
code_result[:errors].each do |err|
|
|
890
|
-
# Adjust line numbers to be relative to template
|
|
891
|
-
line = example[:start_line] + (err[:location] || 0)
|
|
892
|
-
errors << "Line #{line}: #{err[:message]}"
|
|
893
|
-
end
|
|
894
|
-
end
|
|
895
|
-
code_result[:warnings].each do |warn|
|
|
896
|
-
line = example[:start_line] + (warn[:location] || 0)
|
|
897
|
-
warnings << "Line #{line}: #{warn[:message]}"
|
|
898
|
-
end
|
|
899
|
-
end
|
|
900
|
-
|
|
901
|
-
# Extract method calls and check if they're in the safe list
|
|
902
|
-
method_calls = extract_method_calls(content)
|
|
903
|
-
safe_methods = Dsl::Schema.safe_agent_methods +
|
|
904
|
-
Dsl::Schema.safe_tool_methods +
|
|
905
|
-
Dsl::Schema.safe_helper_methods
|
|
906
|
-
method_calls.each do |method|
|
|
907
|
-
next if safe_methods.include?(method)
|
|
908
|
-
|
|
909
|
-
warnings << "Method '#{method}' not in safe methods list (may be valid Ruby builtin)"
|
|
910
|
-
end
|
|
911
|
-
|
|
912
|
-
{
|
|
913
|
-
valid: errors.empty?,
|
|
914
|
-
errors: errors,
|
|
915
|
-
warnings: warnings
|
|
916
|
-
}
|
|
917
|
-
end
|
|
918
|
-
|
|
919
|
-
# Extract Ruby code examples from template
|
|
920
|
-
# Returns array of {code: String, start_line: Integer}
|
|
921
|
-
def extract_code_examples(template)
|
|
922
|
-
examples = []
|
|
923
|
-
lines = template.split("\n")
|
|
924
|
-
in_code_block = false
|
|
925
|
-
current_code = []
|
|
926
|
-
start_line = 0
|
|
927
|
-
|
|
928
|
-
lines.each_with_index do |line, idx|
|
|
929
|
-
if line.strip.start_with?('```ruby')
|
|
930
|
-
in_code_block = true
|
|
931
|
-
start_line = idx + 2 # idx is 0-based, we want line number (1-based) of first code line
|
|
932
|
-
current_code = []
|
|
933
|
-
elsif line.strip == '```' && in_code_block
|
|
934
|
-
in_code_block = false
|
|
935
|
-
examples << { code: current_code.join("\n"), start_line: start_line } unless current_code.empty?
|
|
936
|
-
elsif in_code_block
|
|
937
|
-
current_code << line
|
|
938
|
-
end
|
|
939
|
-
end
|
|
940
|
-
|
|
941
|
-
examples
|
|
942
|
-
end
|
|
943
|
-
|
|
944
|
-
# Extract method calls from template code
|
|
945
|
-
# Returns array of method name strings
|
|
946
|
-
def extract_method_calls(template)
|
|
947
|
-
require 'prism'
|
|
948
|
-
|
|
949
|
-
method_calls = []
|
|
950
|
-
code_examples = extract_code_examples(template)
|
|
951
|
-
|
|
952
|
-
code_examples.each do |example|
|
|
953
|
-
# Parse the code to find method calls
|
|
954
|
-
result = Prism.parse(example[:code])
|
|
955
|
-
|
|
956
|
-
# Walk the AST to find method calls
|
|
957
|
-
extract_methods_from_ast(result.value, method_calls) if result.success?
|
|
958
|
-
rescue Prism::ParseError
|
|
959
|
-
# Skip code with syntax errors - they'll be caught by validate_code_against_schema
|
|
960
|
-
next
|
|
961
|
-
end
|
|
962
|
-
|
|
963
|
-
method_calls.uniq
|
|
964
|
-
end
|
|
965
|
-
|
|
966
|
-
# Recursively extract method names from AST
|
|
967
|
-
def extract_methods_from_ast(node, methods)
|
|
968
|
-
return unless node
|
|
969
|
-
|
|
970
|
-
methods << node.name.to_s if node.is_a?(Prism::CallNode)
|
|
971
|
-
|
|
972
|
-
node.compact_child_nodes.each do |child|
|
|
973
|
-
extract_methods_from_ast(child, methods)
|
|
974
|
-
end
|
|
975
|
-
end
|
|
976
|
-
|
|
977
|
-
# Validate Ruby code against DSL schema
|
|
978
|
-
# Returns {valid: Boolean, errors: Array<Hash>, warnings: Array<Hash>}
|
|
979
|
-
def validate_code_against_schema(code)
|
|
980
|
-
require 'language_operator/agent/safety/ast_validator'
|
|
981
|
-
|
|
982
|
-
validator = LanguageOperator::Agent::Safety::ASTValidator.new
|
|
983
|
-
violations = validator.validate(code, '(template)')
|
|
984
|
-
|
|
985
|
-
errors = []
|
|
986
|
-
warnings = []
|
|
987
|
-
|
|
988
|
-
violations.each do |violation|
|
|
989
|
-
case violation[:type]
|
|
990
|
-
when :syntax_error
|
|
991
|
-
errors << {
|
|
992
|
-
type: :syntax_error,
|
|
993
|
-
location: 0,
|
|
994
|
-
message: violation[:message]
|
|
995
|
-
}
|
|
996
|
-
when :dangerous_method, :dangerous_constant, :dangerous_constant_access, :dangerous_global, :backtick_execution
|
|
997
|
-
errors << {
|
|
998
|
-
type: violation[:type],
|
|
999
|
-
location: violation[:location],
|
|
1000
|
-
message: violation[:message]
|
|
1001
|
-
}
|
|
1002
|
-
else
|
|
1003
|
-
warnings << {
|
|
1004
|
-
type: violation[:type],
|
|
1005
|
-
location: violation[:location] || 0,
|
|
1006
|
-
message: violation[:message]
|
|
1007
|
-
}
|
|
1008
|
-
end
|
|
1009
|
-
end
|
|
1010
|
-
|
|
1011
|
-
{
|
|
1012
|
-
valid: errors.empty?,
|
|
1013
|
-
errors: errors,
|
|
1014
|
-
warnings: warnings
|
|
1015
|
-
}
|
|
1016
|
-
end
|
|
1017
|
-
|
|
1018
|
-
# Output raw template format
|
|
1019
|
-
def output_template_format(content)
|
|
1020
|
-
puts content
|
|
1021
|
-
end
|
|
1022
|
-
|
|
1023
|
-
# Output JSON format with metadata
|
|
1024
|
-
def output_json_format(content, type)
|
|
1025
|
-
data = {
|
|
1026
|
-
version: Dsl::Schema.version,
|
|
1027
|
-
template_type: type,
|
|
1028
|
-
template: content
|
|
1029
|
-
}
|
|
1030
|
-
|
|
1031
|
-
if options[:with_schema]
|
|
1032
|
-
data[:schema] = Dsl::Schema.to_json_schema
|
|
1033
|
-
data[:safe_agent_methods] = Dsl::Schema.safe_agent_methods
|
|
1034
|
-
data[:safe_tool_methods] = Dsl::Schema.safe_tool_methods
|
|
1035
|
-
data[:safe_helper_methods] = Dsl::Schema.safe_helper_methods
|
|
1036
|
-
end
|
|
1037
|
-
|
|
1038
|
-
puts JSON.pretty_generate(data)
|
|
1039
|
-
end
|
|
1040
|
-
|
|
1041
|
-
# Output YAML format with metadata
|
|
1042
|
-
def output_yaml_format(content, type)
|
|
1043
|
-
data = {
|
|
1044
|
-
'version' => Dsl::Schema.version,
|
|
1045
|
-
'template_type' => type,
|
|
1046
|
-
'template' => content
|
|
1047
|
-
}
|
|
1048
|
-
|
|
1049
|
-
if options[:with_schema]
|
|
1050
|
-
data['schema'] = Dsl::Schema.to_json_schema.transform_keys(&:to_s)
|
|
1051
|
-
data['safe_agent_methods'] = Dsl::Schema.safe_agent_methods
|
|
1052
|
-
data['safe_tool_methods'] = Dsl::Schema.safe_tool_methods
|
|
1053
|
-
data['safe_helper_methods'] = Dsl::Schema.safe_helper_methods
|
|
1054
|
-
end
|
|
1055
|
-
|
|
1056
|
-
puts YAML.dump(data)
|
|
1057
|
-
end
|
|
1058
|
-
|
|
1059
|
-
# Create a ConfigMap with agent code
|
|
1060
|
-
def create_agent_configmap(name, code)
|
|
1061
|
-
configmap = {
|
|
1062
|
-
'apiVersion' => 'v1',
|
|
1063
|
-
'kind' => 'ConfigMap',
|
|
1064
|
-
'metadata' => {
|
|
1065
|
-
'name' => name,
|
|
1066
|
-
'namespace' => ctx.namespace
|
|
1067
|
-
},
|
|
1068
|
-
'data' => {
|
|
1069
|
-
'agent.rb' => code
|
|
1070
|
-
}
|
|
1071
|
-
}
|
|
1072
|
-
|
|
1073
|
-
ctx.client.create_resource(configmap)
|
|
1074
|
-
end
|
|
1075
|
-
|
|
1076
|
-
# Create a test pod for running the agent
|
|
1077
|
-
def create_test_pod(name, configmap_name, image)
|
|
1078
|
-
# Detect available models in the cluster
|
|
1079
|
-
model_env = detect_model_config
|
|
1080
|
-
|
|
1081
|
-
if model_env.nil?
|
|
1082
|
-
Formatters::ProgressFormatter.warn('Could not detect model configuration from cluster')
|
|
1083
|
-
Formatters::ProgressFormatter.warn('Agent may fail without MODEL_ENDPOINTS configured')
|
|
1084
|
-
end
|
|
1085
|
-
|
|
1086
|
-
env_vars = [
|
|
1087
|
-
{ 'name' => 'AGENT_NAME', 'value' => name },
|
|
1088
|
-
{ 'name' => 'AGENT_MODE', 'value' => 'autonomous' },
|
|
1089
|
-
{ 'name' => 'AGENT_CODE_PATH', 'value' => '/etc/agent/code/agent.rb' },
|
|
1090
|
-
{ 'name' => 'CONFIG_PATH', 'value' => '/nonexistent/config.yaml' }
|
|
1091
|
-
]
|
|
1092
|
-
|
|
1093
|
-
# Add model configuration if available
|
|
1094
|
-
env_vars += model_env if model_env
|
|
1095
|
-
|
|
1096
|
-
pod = {
|
|
1097
|
-
'apiVersion' => 'v1',
|
|
1098
|
-
'kind' => 'Pod',
|
|
1099
|
-
'metadata' => {
|
|
1100
|
-
'name' => name,
|
|
1101
|
-
'namespace' => ctx.namespace,
|
|
1102
|
-
'labels' => {
|
|
1103
|
-
'app.kubernetes.io/name' => name,
|
|
1104
|
-
'app.kubernetes.io/component' => 'test-agent',
|
|
1105
|
-
'langop.io/kind' => 'LanguageAgent'
|
|
1106
|
-
}
|
|
1107
|
-
},
|
|
1108
|
-
'spec' => {
|
|
1109
|
-
'restartPolicy' => 'Never',
|
|
1110
|
-
'containers' => [
|
|
1111
|
-
{
|
|
1112
|
-
'name' => 'agent',
|
|
1113
|
-
'image' => image,
|
|
1114
|
-
'imagePullPolicy' => 'Always',
|
|
1115
|
-
'env' => env_vars,
|
|
1116
|
-
'volumeMounts' => [
|
|
1117
|
-
{
|
|
1118
|
-
'name' => 'agent-code',
|
|
1119
|
-
'mountPath' => '/etc/agent/code',
|
|
1120
|
-
'readOnly' => true
|
|
1121
|
-
}
|
|
1122
|
-
]
|
|
1123
|
-
}
|
|
1124
|
-
],
|
|
1125
|
-
'volumes' => [
|
|
1126
|
-
{
|
|
1127
|
-
'name' => 'agent-code',
|
|
1128
|
-
'configMap' => {
|
|
1129
|
-
'name' => configmap_name
|
|
1130
|
-
}
|
|
1131
|
-
}
|
|
1132
|
-
]
|
|
1133
|
-
}
|
|
1134
|
-
}
|
|
1135
|
-
|
|
1136
|
-
ctx.client.create_resource(pod)
|
|
1137
|
-
end
|
|
1138
|
-
|
|
1139
|
-
# Detect model configuration from the cluster
|
|
1140
|
-
def detect_model_config
|
|
1141
|
-
models = ctx.client.list_resources('LanguageModel', namespace: ctx.namespace)
|
|
1142
|
-
return nil if models.empty?
|
|
1143
|
-
|
|
1144
|
-
# Use first available model
|
|
1145
|
-
model = models.first
|
|
1146
|
-
model_name = model.dig('metadata', 'name')
|
|
1147
|
-
model_id = model.dig('spec', 'modelName')
|
|
1148
|
-
|
|
1149
|
-
# Build endpoint URL (port 8000 is the model service port)
|
|
1150
|
-
endpoint = "http://#{model_name}.#{ctx.namespace}.svc.cluster.local:8000"
|
|
1151
|
-
|
|
1152
|
-
[
|
|
1153
|
-
{ 'name' => 'MODEL_ENDPOINTS', 'value' => endpoint },
|
|
1154
|
-
{ 'name' => 'LLM_MODEL', 'value' => model_id },
|
|
1155
|
-
{ 'name' => 'OPENAI_API_KEY', 'value' => 'sk-dummy-key-for-local-proxy' }
|
|
1156
|
-
]
|
|
1157
|
-
rescue StandardError => e
|
|
1158
|
-
Formatters::ProgressFormatter.error("Failed to detect model configuration: #{e.message}")
|
|
1159
|
-
nil
|
|
1160
|
-
end
|
|
1161
|
-
|
|
1162
|
-
# Wait for pod to start (running or terminated)
|
|
1163
|
-
def wait_for_pod_start(name, timeout: 60)
|
|
1164
|
-
start_time = Time.now
|
|
1165
|
-
loop do
|
|
1166
|
-
pod = ctx.client.get_resource('Pod', name, ctx.namespace)
|
|
1167
|
-
phase = pod.dig('status', 'phase')
|
|
1168
|
-
|
|
1169
|
-
return if %w[Running Succeeded Failed].include?(phase)
|
|
1170
|
-
|
|
1171
|
-
raise "Pod #{name} did not start within #{timeout} seconds" if Time.now - start_time > timeout
|
|
1172
|
-
|
|
1173
|
-
sleep 1
|
|
1174
|
-
end
|
|
1175
|
-
end
|
|
1176
|
-
|
|
1177
|
-
# Stream pod logs until completion
|
|
1178
|
-
def stream_pod_logs(name, timeout: 300)
|
|
1179
|
-
require 'open3'
|
|
1180
|
-
|
|
1181
|
-
cmd = "kubectl logs -f -n #{ctx.namespace} #{name} 2>&1"
|
|
1182
|
-
Open3.popen3(cmd) do |_stdin, stdout, _stderr, wait_thr|
|
|
1183
|
-
# Set up timeout
|
|
1184
|
-
start_time = Time.now
|
|
1185
|
-
|
|
1186
|
-
# Stream logs
|
|
1187
|
-
stdout.each_line do |line|
|
|
1188
|
-
puts line
|
|
1189
|
-
|
|
1190
|
-
# Check timeout
|
|
1191
|
-
if Time.now - start_time > timeout
|
|
1192
|
-
Process.kill('TERM', wait_thr.pid)
|
|
1193
|
-
raise "Log streaming timed out after #{timeout} seconds"
|
|
1194
|
-
end
|
|
1195
|
-
end
|
|
1196
|
-
|
|
1197
|
-
# Wait for process to complete
|
|
1198
|
-
wait_thr.value
|
|
1199
|
-
end
|
|
1200
|
-
rescue Errno::EPIPE
|
|
1201
|
-
# Pod terminated, logs finished
|
|
1202
|
-
end
|
|
1203
|
-
|
|
1204
|
-
# Wait for pod to terminate and get exit code
|
|
1205
|
-
def wait_for_pod_termination(name, timeout: 10)
|
|
1206
|
-
# Give the pod a moment to fully transition after logs complete
|
|
1207
|
-
sleep 2
|
|
1208
|
-
|
|
1209
|
-
start_time = Time.now
|
|
1210
|
-
loop do
|
|
1211
|
-
pod = ctx.client.get_resource('Pod', name, ctx.namespace)
|
|
1212
|
-
phase = pod.dig('status', 'phase')
|
|
1213
|
-
container_status = pod.dig('status', 'containerStatuses', 0)
|
|
1214
|
-
|
|
1215
|
-
# Pod completed successfully or failed
|
|
1216
|
-
if %w[Succeeded Failed].include?(phase) && container_status && (terminated = container_status.dig('state', 'terminated'))
|
|
1217
|
-
return terminated['exitCode']
|
|
1218
|
-
end
|
|
1219
|
-
|
|
1220
|
-
# Check timeout
|
|
1221
|
-
if Time.now - start_time > timeout
|
|
1222
|
-
# Try one last time
|
|
1223
|
-
if container_status && (terminated = container_status.dig('state', 'terminated'))
|
|
1224
|
-
return terminated['exitCode']
|
|
1225
|
-
end
|
|
1226
|
-
|
|
1227
|
-
return nil
|
|
1228
|
-
end
|
|
1229
|
-
|
|
1230
|
-
sleep 0.5
|
|
1231
|
-
rescue K8s::Error::NotFound
|
|
1232
|
-
# Pod was deleted before we could get status
|
|
1233
|
-
return nil
|
|
1234
|
-
end
|
|
1235
|
-
end
|
|
1236
|
-
|
|
1237
|
-
# Get pod status
|
|
1238
|
-
def get_pod_status(name)
|
|
1239
|
-
pod = ctx.client.get_resource('Pod', name, ctx.namespace)
|
|
1240
|
-
pod.to_h.fetch('status', {})
|
|
1241
|
-
end
|
|
1242
|
-
|
|
1243
|
-
# Delete a pod
|
|
1244
|
-
def delete_pod(name)
|
|
1245
|
-
ctx.client.delete_resource('Pod', name, ctx.namespace)
|
|
1246
|
-
rescue K8s::Error::NotFound
|
|
1247
|
-
# Already deleted
|
|
1248
|
-
end
|
|
1249
|
-
|
|
1250
|
-
# Delete a ConfigMap
|
|
1251
|
-
def delete_configmap(name)
|
|
1252
|
-
ctx.client.delete_resource('ConfigMap', name, ctx.namespace)
|
|
1253
|
-
rescue K8s::Error::NotFound
|
|
1254
|
-
# Already deleted
|
|
1255
|
-
end
|
|
1256
|
-
end
|
|
1257
|
-
end
|
|
1258
|
-
end
|
|
1259
|
-
end
|