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
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
require 'rack'
|
|
4
4
|
require 'rackup'
|
|
5
5
|
require 'mcp'
|
|
6
|
+
require_relative 'executor'
|
|
6
7
|
|
|
7
8
|
module LanguageOperator
|
|
8
9
|
module Agent
|
|
@@ -26,10 +27,13 @@ module LanguageOperator
|
|
|
26
27
|
@agent = agent
|
|
27
28
|
@port = port || ENV.fetch('PORT', '8080').to_i
|
|
28
29
|
@routes = {}
|
|
29
|
-
@executor = Executor.new(agent)
|
|
30
30
|
@mcp_server = nil
|
|
31
31
|
@mcp_transport = nil
|
|
32
32
|
|
|
33
|
+
# Initialize executor pool to prevent MCP connection leaks
|
|
34
|
+
@executor_pool_size = ENV.fetch('EXECUTOR_POOL_SIZE', '4').to_i
|
|
35
|
+
@executor_pool = setup_executor_pool
|
|
36
|
+
|
|
33
37
|
setup_default_routes
|
|
34
38
|
end
|
|
35
39
|
|
|
@@ -164,8 +168,49 @@ module LanguageOperator
|
|
|
164
168
|
error_response(e)
|
|
165
169
|
end
|
|
166
170
|
|
|
171
|
+
# Cleanup executor pool and connections
|
|
172
|
+
#
|
|
173
|
+
# Properly closes all executors in the pool and their MCP connections
|
|
174
|
+
# to prevent resource leaks during server shutdown.
|
|
175
|
+
#
|
|
176
|
+
# @return [void]
|
|
177
|
+
def cleanup
|
|
178
|
+
return unless @executor_pool
|
|
179
|
+
|
|
180
|
+
# Drain and cleanup all executors in the pool
|
|
181
|
+
executors_cleaned = 0
|
|
182
|
+
begin
|
|
183
|
+
loop do
|
|
184
|
+
executor = @executor_pool.pop(timeout: 0.1)
|
|
185
|
+
if executor
|
|
186
|
+
executor.cleanup_connections
|
|
187
|
+
executors_cleaned += 1
|
|
188
|
+
end
|
|
189
|
+
end
|
|
190
|
+
rescue ThreadError
|
|
191
|
+
# Pool is empty, we're done
|
|
192
|
+
end
|
|
193
|
+
|
|
194
|
+
puts "Cleaned up #{executors_cleaned} executors from pool"
|
|
195
|
+
end
|
|
196
|
+
|
|
167
197
|
private
|
|
168
198
|
|
|
199
|
+
# Setup executor pool for connection reuse
|
|
200
|
+
#
|
|
201
|
+
# Creates a thread-safe queue pre-populated with executor instances
|
|
202
|
+
# to prevent creating new MCP connections for each webhook request.
|
|
203
|
+
#
|
|
204
|
+
# @return [Queue] Thread-safe executor pool
|
|
205
|
+
def setup_executor_pool
|
|
206
|
+
pool = Queue.new
|
|
207
|
+
@executor_pool_size.times do
|
|
208
|
+
pool << Executor.new(@agent)
|
|
209
|
+
end
|
|
210
|
+
puts "Initialized executor pool with #{@executor_pool_size} executors"
|
|
211
|
+
pool
|
|
212
|
+
end
|
|
213
|
+
|
|
169
214
|
# Build the Rack application
|
|
170
215
|
#
|
|
171
216
|
# @return [Rack::Builder]
|
|
@@ -235,9 +280,11 @@ module LanguageOperator
|
|
|
235
280
|
# @param request [Rack::Request] The request
|
|
236
281
|
# @return [Hash] Request context
|
|
237
282
|
def build_request_context(request)
|
|
238
|
-
# Read body, handling nil case
|
|
283
|
+
# Read body, handling nil case and rewinding for subsequent reads
|
|
239
284
|
body_content = if request.body
|
|
240
|
-
request.body.read
|
|
285
|
+
content = request.body.read
|
|
286
|
+
request.body.rewind # Reset for subsequent reads by middleware/handlers
|
|
287
|
+
content
|
|
241
288
|
else
|
|
242
289
|
''
|
|
243
290
|
end
|
|
@@ -302,20 +349,48 @@ module LanguageOperator
|
|
|
302
349
|
|
|
303
350
|
# Handle webhook request by executing agent
|
|
304
351
|
#
|
|
352
|
+
# Uses executor pooling to prevent MCP connection resource leaks.
|
|
353
|
+
# Executors are reused across requests to avoid creating new
|
|
354
|
+
# connections for each webhook request.
|
|
355
|
+
#
|
|
305
356
|
# @param context [Hash] Request context
|
|
306
357
|
# @return [Hash] Response data
|
|
307
358
|
def handle_webhook(context)
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
359
|
+
executor = nil
|
|
360
|
+
begin
|
|
361
|
+
# Get executor from pool with timeout
|
|
362
|
+
executor = @executor_pool.pop(timeout: 5)
|
|
363
|
+
result = executor.execute_with_context(
|
|
364
|
+
instruction: 'Process incoming webhook request',
|
|
365
|
+
context: context
|
|
366
|
+
)
|
|
313
367
|
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
368
|
+
{
|
|
369
|
+
status: 'processed',
|
|
370
|
+
result: result,
|
|
371
|
+
timestamp: Time.now.iso8601
|
|
372
|
+
}
|
|
373
|
+
rescue ThreadError
|
|
374
|
+
# Pool exhausted, create temporary executor as fallback
|
|
375
|
+
temp_executor = Executor.new(@agent)
|
|
376
|
+
begin
|
|
377
|
+
result = temp_executor.execute_with_context(
|
|
378
|
+
instruction: 'Process incoming webhook request',
|
|
379
|
+
context: context
|
|
380
|
+
)
|
|
381
|
+
|
|
382
|
+
{
|
|
383
|
+
status: 'processed',
|
|
384
|
+
result: result,
|
|
385
|
+
timestamp: Time.now.iso8601
|
|
386
|
+
}
|
|
387
|
+
ensure
|
|
388
|
+
temp_executor.cleanup_connections
|
|
389
|
+
end
|
|
390
|
+
ensure
|
|
391
|
+
# Return executor to pool if we got one
|
|
392
|
+
@executor_pool.push(executor) if executor
|
|
393
|
+
end
|
|
319
394
|
end
|
|
320
395
|
|
|
321
396
|
# Handle MCP protocol request
|
|
@@ -536,15 +611,36 @@ module LanguageOperator
|
|
|
536
611
|
class MockStream
|
|
537
612
|
def initialize(buffer)
|
|
538
613
|
@buffer = buffer
|
|
614
|
+
@closed = false
|
|
539
615
|
end
|
|
540
616
|
|
|
541
617
|
def write(data)
|
|
618
|
+
raise IOError, 'closed stream' if @closed
|
|
619
|
+
|
|
542
620
|
@buffer.write(data)
|
|
543
621
|
end
|
|
544
622
|
|
|
623
|
+
def flush
|
|
624
|
+
@buffer.flush if @buffer.respond_to?(:flush)
|
|
625
|
+
end
|
|
626
|
+
|
|
545
627
|
def close
|
|
546
|
-
|
|
628
|
+
@closed = true
|
|
629
|
+
end
|
|
630
|
+
|
|
631
|
+
def closed?
|
|
632
|
+
@closed
|
|
633
|
+
end
|
|
634
|
+
|
|
635
|
+
def sync=(value)
|
|
636
|
+
# No-op for compatibility
|
|
637
|
+
end
|
|
638
|
+
|
|
639
|
+
# rubocop:disable Naming/PredicateMethod
|
|
640
|
+
def sync
|
|
641
|
+
true
|
|
547
642
|
end
|
|
643
|
+
# rubocop:enable Naming/PredicateMethod
|
|
548
644
|
end
|
|
549
645
|
|
|
550
646
|
# Called by Rack to stream the response
|
|
@@ -2,11 +2,18 @@
|
|
|
2
2
|
|
|
3
3
|
require 'openssl'
|
|
4
4
|
require 'base64'
|
|
5
|
+
require_relative '../logger'
|
|
5
6
|
|
|
6
7
|
module LanguageOperator
|
|
7
8
|
module Agent
|
|
8
9
|
# Executes webhook authentication checks
|
|
9
10
|
class WebhookAuthenticator
|
|
11
|
+
@logger = LanguageOperator::Logger.new(component: 'WebhookAuth')
|
|
12
|
+
|
|
13
|
+
class << self
|
|
14
|
+
attr_reader :logger
|
|
15
|
+
end
|
|
16
|
+
|
|
10
17
|
# Authenticate a webhook request
|
|
11
18
|
#
|
|
12
19
|
# @param authentication [LanguageOperator::Dsl::WebhookAuthentication] Authentication definition
|
|
@@ -88,19 +95,46 @@ module LanguageOperator
|
|
|
88
95
|
expected_password = config[:password]
|
|
89
96
|
|
|
90
97
|
auth_header = get_header(context, 'Authorization')
|
|
91
|
-
|
|
98
|
+
unless auth_header
|
|
99
|
+
logger.debug('Basic auth failed: missing Authorization header')
|
|
100
|
+
return false
|
|
101
|
+
end
|
|
92
102
|
|
|
93
103
|
# Extract credentials from "Basic <base64>"
|
|
94
104
|
match = auth_header.match(/^Basic\s+(.+)$/i)
|
|
95
|
-
|
|
105
|
+
unless match
|
|
106
|
+
logger.debug('Basic auth failed: invalid Authorization header format')
|
|
107
|
+
return false
|
|
108
|
+
end
|
|
96
109
|
|
|
97
110
|
begin
|
|
98
111
|
credentials = Base64.decode64(match[1])
|
|
99
112
|
username, password = credentials.split(':', 2)
|
|
100
113
|
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
114
|
+
# Log debugging info for malformed credentials (no colon)
|
|
115
|
+
if password.nil?
|
|
116
|
+
logger.debug('Basic auth failed: malformed credentials (no colon separator)',
|
|
117
|
+
credentials_length: credentials.length,
|
|
118
|
+
contains_colon: credentials.include?(':'))
|
|
119
|
+
return false
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
username_valid = secure_compare(username, expected_username)
|
|
123
|
+
password_valid = secure_compare(password, expected_password)
|
|
124
|
+
|
|
125
|
+
success = username_valid && password_valid
|
|
126
|
+
|
|
127
|
+
unless success
|
|
128
|
+
logger.debug('Basic auth failed: credential mismatch',
|
|
129
|
+
username_valid: username_valid,
|
|
130
|
+
password_valid: password_valid)
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
success
|
|
134
|
+
rescue StandardError => e
|
|
135
|
+
logger.debug('Basic auth failed: exception during processing',
|
|
136
|
+
error: e.class.name,
|
|
137
|
+
message: e.message)
|
|
104
138
|
false
|
|
105
139
|
end
|
|
106
140
|
end
|
|
@@ -157,8 +157,8 @@ module LanguageOperator
|
|
|
157
157
|
case agent.mode
|
|
158
158
|
when 'autonomous', 'interactive'
|
|
159
159
|
if uses_dsl_v1
|
|
160
|
-
# DSL v1: Execute main block with task executor
|
|
161
|
-
|
|
160
|
+
# DSL v1: Execute main block with task executor in persistent mode
|
|
161
|
+
execute_main_block_persistent(agent, agent_def)
|
|
162
162
|
elsif uses_dsl_v0
|
|
163
163
|
# DSL v0: Execute workflow in autonomous mode
|
|
164
164
|
executor = LanguageOperator::Agent::Executor.new(agent)
|
|
@@ -233,6 +233,92 @@ module LanguageOperator
|
|
|
233
233
|
result
|
|
234
234
|
end
|
|
235
235
|
|
|
236
|
+
# Execute main block (DSL v1) in persistent mode for autonomous agents
|
|
237
|
+
#
|
|
238
|
+
# @param agent [LanguageOperator::Agent::Base] The agent instance
|
|
239
|
+
# @param agent_def [LanguageOperator::Dsl::AgentDefinition] The agent definition
|
|
240
|
+
# @return [void]
|
|
241
|
+
def self.execute_main_block_persistent(agent, agent_def)
|
|
242
|
+
shutdown_requested = setup_signal_handlers
|
|
243
|
+
|
|
244
|
+
logger.info('Starting agent in persistent autonomous mode',
|
|
245
|
+
agent_name: agent_def.name,
|
|
246
|
+
task_count: agent_def.tasks.size)
|
|
247
|
+
|
|
248
|
+
# Execute initial main block
|
|
249
|
+
execute_main_block(agent, agent_def)
|
|
250
|
+
logger.info('Initial task completed - entering idle state')
|
|
251
|
+
|
|
252
|
+
# Enter waiting loop for additional instructions
|
|
253
|
+
run_idle_loop(shutdown_requested)
|
|
254
|
+
|
|
255
|
+
logger.info('Graceful shutdown requested - agent exiting')
|
|
256
|
+
end
|
|
257
|
+
|
|
258
|
+
# Setup signal handlers for graceful shutdown
|
|
259
|
+
#
|
|
260
|
+
# @return [Proc] shutdown_requested flag wrapped in a closure
|
|
261
|
+
def self.setup_signal_handlers
|
|
262
|
+
shutdown_requested = false
|
|
263
|
+
trap('TERM') do
|
|
264
|
+
logger.info('SIGTERM received - requesting graceful shutdown')
|
|
265
|
+
shutdown_requested = true
|
|
266
|
+
end
|
|
267
|
+
trap('INT') do
|
|
268
|
+
logger.info('SIGINT received - requesting graceful shutdown')
|
|
269
|
+
shutdown_requested = true
|
|
270
|
+
end
|
|
271
|
+
|
|
272
|
+
-> { shutdown_requested }
|
|
273
|
+
end
|
|
274
|
+
|
|
275
|
+
# Run the idle loop waiting for new instructions
|
|
276
|
+
#
|
|
277
|
+
# @param shutdown_requested [Proc] Closure that returns shutdown state
|
|
278
|
+
# @return [void]
|
|
279
|
+
def self.run_idle_loop(shutdown_requested)
|
|
280
|
+
idle_timeout = ENV.fetch('AGENT_IDLE_TIMEOUT', '300').to_i # 5 minutes default
|
|
281
|
+
last_activity = Time.now
|
|
282
|
+
|
|
283
|
+
until shutdown_requested.call
|
|
284
|
+
begin
|
|
285
|
+
last_activity = Time.now if handle_new_instruction
|
|
286
|
+
|
|
287
|
+
sleep 5 # Avoid busy waiting
|
|
288
|
+
|
|
289
|
+
# Reset idle timer when timeout reached (stay persistent)
|
|
290
|
+
if idle_timeout.positive? && Time.now - last_activity > idle_timeout
|
|
291
|
+
logger.info('Idle timeout reached - agent remaining available',
|
|
292
|
+
timeout_seconds: idle_timeout)
|
|
293
|
+
last_activity = Time.now
|
|
294
|
+
end
|
|
295
|
+
rescue StandardError => e
|
|
296
|
+
logger.error('Error in idle loop',
|
|
297
|
+
error: e.message,
|
|
298
|
+
backtrace: e.backtrace[0..3])
|
|
299
|
+
sleep 10 # Back off on error
|
|
300
|
+
end
|
|
301
|
+
end
|
|
302
|
+
end
|
|
303
|
+
|
|
304
|
+
# Handle new instruction from environment variable
|
|
305
|
+
#
|
|
306
|
+
# @return [Boolean] True if instruction was processed
|
|
307
|
+
def self.handle_new_instruction
|
|
308
|
+
new_instruction = ENV.fetch('AGENT_NEW_INSTRUCTION', nil)
|
|
309
|
+
return false unless new_instruction && !new_instruction.strip.empty?
|
|
310
|
+
|
|
311
|
+
logger.info('New instruction received',
|
|
312
|
+
instruction: new_instruction[0..100])
|
|
313
|
+
|
|
314
|
+
# Clear the environment variable to prevent re-execution
|
|
315
|
+
ENV['AGENT_NEW_INSTRUCTION'] = ''
|
|
316
|
+
|
|
317
|
+
# Execute the new instruction (implementation would depend on requirements)
|
|
318
|
+
logger.info('Instruction acknowledged - agent remains in autonomous mode')
|
|
319
|
+
true
|
|
320
|
+
end
|
|
321
|
+
|
|
236
322
|
# Execute the output handler (neural or symbolic)
|
|
237
323
|
#
|
|
238
324
|
# @param agent_def [LanguageOperator::Dsl::AgentDefinition] The agent definition
|
|
@@ -1,11 +1,20 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require 'thor'
|
|
4
|
+
require_relative 'helpers/ux_helper'
|
|
4
5
|
|
|
5
6
|
module LanguageOperator
|
|
6
7
|
module CLI
|
|
7
8
|
# Base class for all CLI commands providing shared functionality
|
|
9
|
+
#
|
|
10
|
+
# Automatically includes {Helpers::UxHelper} to provide:
|
|
11
|
+
# - +pastel+ - Colorized terminal output
|
|
12
|
+
# - +prompt+ - Interactive user prompts
|
|
13
|
+
#
|
|
14
|
+
# All commands inheriting from BaseCommand get these helpers automatically.
|
|
8
15
|
class BaseCommand < Thor
|
|
16
|
+
include Helpers::UxHelper
|
|
17
|
+
|
|
9
18
|
no_commands do
|
|
10
19
|
# Lazy-initialized cluster context from options
|
|
11
20
|
# @return [Helpers::ClusterContext]
|
|
@@ -55,21 +64,16 @@ module LanguageOperator
|
|
|
55
64
|
exit 1
|
|
56
65
|
end
|
|
57
66
|
|
|
58
|
-
# Confirm resource deletion with user
|
|
67
|
+
# Confirm resource deletion with user (delegates to UxHelper)
|
|
59
68
|
# @param resource_type [String] Type of resource being deleted
|
|
60
69
|
# @param name [String] Resource name
|
|
61
70
|
# @param cluster [String] Cluster name
|
|
62
|
-
# @param details [Hash] Additional details to display
|
|
63
71
|
# @param force [Boolean] Skip confirmation if true
|
|
64
72
|
# @return [Boolean] True if deletion should proceed
|
|
65
|
-
def
|
|
73
|
+
def confirm_deletion_with_force(resource_type, name, cluster, force: false)
|
|
66
74
|
return true if force
|
|
67
75
|
|
|
68
|
-
|
|
69
|
-
details.each { |key, value| puts " #{key}: #{value}" }
|
|
70
|
-
puts
|
|
71
|
-
|
|
72
|
-
Helpers::UserPrompts.confirm('Are you sure?')
|
|
76
|
+
confirm_deletion(resource_type, name, cluster)
|
|
73
77
|
end
|
|
74
78
|
|
|
75
79
|
# Check if resource has dependencies and confirm deletion
|
|
@@ -94,7 +98,7 @@ module LanguageOperator
|
|
|
94
98
|
puts 'Delete these agents first, or use --force to delete anyway.'
|
|
95
99
|
puts
|
|
96
100
|
|
|
97
|
-
return Helpers::UserPrompts.confirm('Are you sure?')
|
|
101
|
+
return CLI::Helpers::UserPrompts.confirm('Are you sure?')
|
|
98
102
|
end
|
|
99
103
|
|
|
100
104
|
true
|
|
@@ -102,14 +106,16 @@ module LanguageOperator
|
|
|
102
106
|
|
|
103
107
|
# List resources or handle empty state
|
|
104
108
|
# @param type [String] Resource type (e.g., 'LanguageModel')
|
|
109
|
+
# @param resource_name [String] Human-readable resource name (e.g., 'models')
|
|
105
110
|
# @param empty_message [String, nil] Custom message when no resources found
|
|
106
111
|
# @yield Optional block to execute when list is empty (for guidance)
|
|
107
112
|
# @return [Array<Hash>] List of resources (empty array if none found)
|
|
108
|
-
def list_resources_or_empty(type, empty_message: nil)
|
|
113
|
+
def list_resources_or_empty(type, resource_name: nil, empty_message: nil)
|
|
109
114
|
resources = ctx.client.list_resources(type, namespace: ctx.namespace)
|
|
110
115
|
|
|
111
116
|
if resources.empty?
|
|
112
|
-
|
|
117
|
+
resource_display = resource_name || type.downcase.gsub('language', '')
|
|
118
|
+
msg = empty_message || "No #{resource_display} found"
|
|
113
119
|
Formatters::ProgressFormatter.info(msg)
|
|
114
120
|
yield if block_given?
|
|
115
121
|
end
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# CommandLoader - Single require point for all CLI commands
|
|
4
|
+
#
|
|
5
|
+
# This module eliminates duplication across command files by auto-loading
|
|
6
|
+
# all common formatters, helpers, and dependencies.
|
|
7
|
+
#
|
|
8
|
+
# Before CommandLoader, each command file had 15+ duplicate require statements.
|
|
9
|
+
# Now commands just need: require_relative '../command_loader'
|
|
10
|
+
#
|
|
11
|
+
# Usage in command files:
|
|
12
|
+
# require_relative '../command_loader'
|
|
13
|
+
# require_relative '../wizards/my_wizard' # command-specific requires only
|
|
14
|
+
#
|
|
15
|
+
# class MyCommand < BaseCommand
|
|
16
|
+
# include Constants # For RESOURCE_* constants
|
|
17
|
+
#
|
|
18
|
+
# def some_method
|
|
19
|
+
# ctx.client.get_resource(RESOURCE_AGENT, name, ctx.namespace)
|
|
20
|
+
# end
|
|
21
|
+
# end
|
|
22
|
+
|
|
23
|
+
module LanguageOperator
|
|
24
|
+
module CLI
|
|
25
|
+
module CommandLoader
|
|
26
|
+
# Auto-load all common CLI dependencies
|
|
27
|
+
# This runs once when the module is first required
|
|
28
|
+
def self.setup
|
|
29
|
+
# Core base class (must load first)
|
|
30
|
+
require_relative 'base_command'
|
|
31
|
+
|
|
32
|
+
# Constants for resource types
|
|
33
|
+
require_relative '../constants'
|
|
34
|
+
|
|
35
|
+
# Formatters - visual output and progress indicators
|
|
36
|
+
require_relative 'formatters/progress_formatter'
|
|
37
|
+
require_relative 'formatters/table_formatter'
|
|
38
|
+
require_relative 'formatters/value_formatter'
|
|
39
|
+
require_relative 'formatters/log_formatter'
|
|
40
|
+
require_relative 'formatters/status_formatter'
|
|
41
|
+
require_relative 'formatters/code_formatter'
|
|
42
|
+
require_relative 'formatters/log_style'
|
|
43
|
+
# require_relative 'formatters/optimization_formatter'
|
|
44
|
+
|
|
45
|
+
# Helpers - shared utilities and validations
|
|
46
|
+
require_relative 'helpers/cluster_validator'
|
|
47
|
+
require_relative 'helpers/cluster_context'
|
|
48
|
+
require_relative 'helpers/user_prompts'
|
|
49
|
+
require_relative 'helpers/editor_helper'
|
|
50
|
+
require_relative 'helpers/resource_dependency_checker'
|
|
51
|
+
require_relative 'helpers/ux_helper'
|
|
52
|
+
require_relative 'helpers/validation_helper'
|
|
53
|
+
require_relative 'helpers/provider_helper'
|
|
54
|
+
require_relative 'helpers/schedule_builder'
|
|
55
|
+
require_relative 'helpers/kubeconfig_validator'
|
|
56
|
+
|
|
57
|
+
# Error handling
|
|
58
|
+
require_relative 'errors/handler'
|
|
59
|
+
require_relative 'errors/suggestions'
|
|
60
|
+
|
|
61
|
+
# Configuration and Kubernetes clients
|
|
62
|
+
require_relative '../config/cluster_config'
|
|
63
|
+
require_relative '../config/tool_registry'
|
|
64
|
+
require_relative '../kubernetes/client'
|
|
65
|
+
require_relative '../kubernetes/resource_builder'
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
# Auto-setup when this file is required
|
|
72
|
+
LanguageOperator::CLI::CommandLoader.setup
|