aidp 0.7.0 → 0.8.0
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/README.md +60 -214
- data/bin/aidp +1 -1
- data/lib/aidp/analysis/kb_inspector.rb +38 -23
- data/lib/aidp/analysis/seams.rb +2 -31
- data/lib/aidp/analysis/tree_sitter_grammar_loader.rb +0 -13
- data/lib/aidp/analysis/tree_sitter_scan.rb +3 -20
- data/lib/aidp/analyze/error_handler.rb +2 -75
- data/lib/aidp/analyze/json_file_storage.rb +292 -0
- data/lib/aidp/analyze/progress.rb +12 -0
- data/lib/aidp/analyze/progress_visualizer.rb +12 -17
- data/lib/aidp/analyze/ruby_maat_integration.rb +13 -31
- data/lib/aidp/analyze/runner.rb +256 -87
- data/lib/aidp/cli/jobs_command.rb +100 -432
- data/lib/aidp/cli.rb +309 -239
- data/lib/aidp/config.rb +298 -10
- data/lib/aidp/debug_logger.rb +195 -0
- data/lib/aidp/debug_mixin.rb +187 -0
- data/lib/aidp/execute/progress.rb +9 -0
- data/lib/aidp/execute/runner.rb +221 -40
- data/lib/aidp/execute/steps.rb +17 -7
- data/lib/aidp/execute/workflow_selector.rb +211 -0
- data/lib/aidp/harness/completion_checker.rb +268 -0
- data/lib/aidp/harness/condition_detector.rb +1526 -0
- data/lib/aidp/harness/config_loader.rb +373 -0
- data/lib/aidp/harness/config_manager.rb +382 -0
- data/lib/aidp/harness/config_schema.rb +1006 -0
- data/lib/aidp/harness/config_validator.rb +355 -0
- data/lib/aidp/harness/configuration.rb +477 -0
- data/lib/aidp/harness/enhanced_runner.rb +494 -0
- data/lib/aidp/harness/error_handler.rb +616 -0
- data/lib/aidp/harness/provider_config.rb +423 -0
- data/lib/aidp/harness/provider_factory.rb +306 -0
- data/lib/aidp/harness/provider_manager.rb +1269 -0
- data/lib/aidp/harness/provider_type_checker.rb +88 -0
- data/lib/aidp/harness/runner.rb +411 -0
- data/lib/aidp/harness/state/errors.rb +28 -0
- data/lib/aidp/harness/state/metrics.rb +219 -0
- data/lib/aidp/harness/state/persistence.rb +128 -0
- data/lib/aidp/harness/state/provider_state.rb +132 -0
- data/lib/aidp/harness/state/ui_state.rb +68 -0
- data/lib/aidp/harness/state/workflow_state.rb +123 -0
- data/lib/aidp/harness/state_manager.rb +586 -0
- data/lib/aidp/harness/status_display.rb +888 -0
- data/lib/aidp/harness/ui/base.rb +16 -0
- data/lib/aidp/harness/ui/enhanced_tui.rb +545 -0
- data/lib/aidp/harness/ui/enhanced_workflow_selector.rb +252 -0
- data/lib/aidp/harness/ui/error_handler.rb +132 -0
- data/lib/aidp/harness/ui/frame_manager.rb +361 -0
- data/lib/aidp/harness/ui/job_monitor.rb +500 -0
- data/lib/aidp/harness/ui/navigation/main_menu.rb +311 -0
- data/lib/aidp/harness/ui/navigation/menu_formatter.rb +120 -0
- data/lib/aidp/harness/ui/navigation/menu_item.rb +142 -0
- data/lib/aidp/harness/ui/navigation/menu_state.rb +139 -0
- data/lib/aidp/harness/ui/navigation/submenu.rb +202 -0
- data/lib/aidp/harness/ui/navigation/workflow_selector.rb +176 -0
- data/lib/aidp/harness/ui/progress_display.rb +280 -0
- data/lib/aidp/harness/ui/question_collector.rb +141 -0
- data/lib/aidp/harness/ui/spinner_group.rb +184 -0
- data/lib/aidp/harness/ui/spinner_helper.rb +152 -0
- data/lib/aidp/harness/ui/status_manager.rb +312 -0
- data/lib/aidp/harness/ui/status_widget.rb +280 -0
- data/lib/aidp/harness/ui/workflow_controller.rb +312 -0
- data/lib/aidp/harness/user_interface.rb +2381 -0
- data/lib/aidp/provider_manager.rb +131 -7
- data/lib/aidp/providers/anthropic.rb +28 -103
- data/lib/aidp/providers/base.rb +170 -0
- data/lib/aidp/providers/cursor.rb +52 -181
- data/lib/aidp/providers/gemini.rb +24 -107
- data/lib/aidp/providers/macos_ui.rb +99 -5
- data/lib/aidp/providers/opencode.rb +194 -0
- data/lib/aidp/storage/csv_storage.rb +172 -0
- data/lib/aidp/storage/file_manager.rb +214 -0
- data/lib/aidp/storage/json_storage.rb +140 -0
- data/lib/aidp/version.rb +1 -1
- data/lib/aidp.rb +54 -39
- data/templates/COMMON/AGENT_BASE.md +11 -0
- data/templates/EXECUTE/00_PRD.md +4 -4
- data/templates/EXECUTE/02_ARCHITECTURE.md +5 -4
- data/templates/EXECUTE/07_TEST_PLAN.md +4 -1
- data/templates/EXECUTE/08_TASKS.md +4 -4
- data/templates/EXECUTE/10_IMPLEMENTATION_AGENT.md +4 -4
- data/templates/README.md +279 -0
- data/templates/aidp-development.yml.example +373 -0
- data/templates/aidp-minimal.yml.example +48 -0
- data/templates/aidp-production.yml.example +475 -0
- data/templates/aidp.yml.example +598 -0
- metadata +93 -69
- data/lib/aidp/analyze/agent_personas.rb +0 -71
- data/lib/aidp/analyze/agent_tool_executor.rb +0 -439
- data/lib/aidp/analyze/data_retention_manager.rb +0 -421
- data/lib/aidp/analyze/database.rb +0 -260
- data/lib/aidp/analyze/dependencies.rb +0 -335
- data/lib/aidp/analyze/export_manager.rb +0 -418
- data/lib/aidp/analyze/focus_guidance.rb +0 -517
- data/lib/aidp/analyze/incremental_analyzer.rb +0 -533
- data/lib/aidp/analyze/language_analysis_strategies.rb +0 -897
- data/lib/aidp/analyze/large_analysis_progress.rb +0 -499
- data/lib/aidp/analyze/memory_manager.rb +0 -339
- data/lib/aidp/analyze/metrics_storage.rb +0 -336
- data/lib/aidp/analyze/parallel_processor.rb +0 -454
- data/lib/aidp/analyze/performance_optimizer.rb +0 -691
- data/lib/aidp/analyze/repository_chunker.rb +0 -697
- data/lib/aidp/analyze/static_analysis_detector.rb +0 -577
- data/lib/aidp/analyze/storage.rb +0 -655
- data/lib/aidp/analyze/tool_configuration.rb +0 -441
- data/lib/aidp/analyze/tool_modernization.rb +0 -750
- data/lib/aidp/database/pg_adapter.rb +0 -148
- data/lib/aidp/database_config.rb +0 -69
- data/lib/aidp/database_connection.rb +0 -72
- data/lib/aidp/job_manager.rb +0 -41
- data/lib/aidp/jobs/base_job.rb +0 -45
- data/lib/aidp/jobs/provider_execution_job.rb +0 -83
- data/lib/aidp/project_detector.rb +0 -117
- data/lib/aidp/providers/agent_supervisor.rb +0 -348
- data/lib/aidp/providers/supervised_base.rb +0 -317
- data/lib/aidp/providers/supervised_cursor.rb +0 -22
- data/lib/aidp/sync.rb +0 -13
- data/lib/aidp/workspace.rb +0 -19
@@ -1,348 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require "timeout"
|
4
|
-
require "open3"
|
5
|
-
|
6
|
-
module Aidp
|
7
|
-
module Providers
|
8
|
-
# Supervisor for managing agent execution with progressive warnings instead of hard timeouts
|
9
|
-
class AgentSupervisor
|
10
|
-
# Execution states
|
11
|
-
STATES = {
|
12
|
-
idle: "⏳",
|
13
|
-
starting: "🚀",
|
14
|
-
running: "🔄",
|
15
|
-
warning: "⚠️",
|
16
|
-
user_aborted: "🛑",
|
17
|
-
completed: "✅",
|
18
|
-
failed: "❌"
|
19
|
-
}.freeze
|
20
|
-
|
21
|
-
attr_reader :state, :start_time, :end_time, :duration, :output, :error_output, :exit_code
|
22
|
-
|
23
|
-
def initialize(command, timeout_seconds: 300, debug: false)
|
24
|
-
@command = command
|
25
|
-
@timeout_seconds = timeout_seconds
|
26
|
-
@debug = debug
|
27
|
-
@state = :idle
|
28
|
-
@start_time = nil
|
29
|
-
@end_time = nil
|
30
|
-
@duration = 0
|
31
|
-
@output = ""
|
32
|
-
@error_output = ""
|
33
|
-
@exit_code = nil
|
34
|
-
@process_pid = nil
|
35
|
-
@output_count = 0
|
36
|
-
@last_output_time = nil
|
37
|
-
@supervisor_thread = nil
|
38
|
-
@output_threads = []
|
39
|
-
@warning_shown = false
|
40
|
-
@user_aborted = false
|
41
|
-
end
|
42
|
-
|
43
|
-
# Execute the command with supervision
|
44
|
-
def execute(input = nil)
|
45
|
-
@state = :starting
|
46
|
-
@start_time = Time.now
|
47
|
-
@last_output_time = @start_time
|
48
|
-
|
49
|
-
puts "🚀 Starting agent execution (will warn at #{@timeout_seconds}s)"
|
50
|
-
|
51
|
-
begin
|
52
|
-
# Start the process
|
53
|
-
Open3.popen3(*@command) do |stdin, stdout, stderr, wait|
|
54
|
-
@process_pid = wait.pid
|
55
|
-
@state = :running
|
56
|
-
|
57
|
-
# Send input if provided
|
58
|
-
if input
|
59
|
-
stdin.puts input
|
60
|
-
stdin.close
|
61
|
-
end
|
62
|
-
|
63
|
-
# Start timeout thread that will warn but not kill
|
64
|
-
timeout_thread = Thread.new do
|
65
|
-
sleep @timeout_seconds
|
66
|
-
if @state == :running && !@warning_shown
|
67
|
-
show_timeout_warning
|
68
|
-
end
|
69
|
-
end
|
70
|
-
|
71
|
-
# Start supervisor thread
|
72
|
-
start_supervisor_thread(wait)
|
73
|
-
|
74
|
-
# Start output collection threads
|
75
|
-
start_output_threads(stdout, stderr)
|
76
|
-
|
77
|
-
# Wait for completion
|
78
|
-
result = wait_for_completion(wait)
|
79
|
-
|
80
|
-
# Kill timeout thread since we're done
|
81
|
-
timeout_thread.kill
|
82
|
-
|
83
|
-
# Clean up threads
|
84
|
-
cleanup_threads
|
85
|
-
|
86
|
-
@end_time = Time.now
|
87
|
-
@duration = @end_time - @start_time
|
88
|
-
|
89
|
-
return result
|
90
|
-
end
|
91
|
-
rescue => e
|
92
|
-
@state = :failed
|
93
|
-
@end_time = Time.now
|
94
|
-
@duration = @end_time - @start_time if @start_time
|
95
|
-
puts "❌ Agent execution failed: #{e.message}"
|
96
|
-
raise
|
97
|
-
end
|
98
|
-
end
|
99
|
-
|
100
|
-
# Get current execution status
|
101
|
-
def status
|
102
|
-
elapsed = @start_time ? Time.now - @start_time : 0
|
103
|
-
minutes = (elapsed / 60).to_i
|
104
|
-
seconds = (elapsed % 60).to_i
|
105
|
-
time_str = (minutes > 0) ? "#{minutes}m #{seconds}s" : "#{seconds}s"
|
106
|
-
|
107
|
-
case @state
|
108
|
-
when :idle
|
109
|
-
"⏳ Idle"
|
110
|
-
when :starting
|
111
|
-
"🚀 Starting..."
|
112
|
-
when :running
|
113
|
-
output_info = (@output_count > 0) ? " (#{@output_count} outputs)" : ""
|
114
|
-
"🔄 Running #{time_str}#{output_info}"
|
115
|
-
when :warning
|
116
|
-
"⚠️ Taking longer than expected #{time_str}"
|
117
|
-
when :user_aborted
|
118
|
-
"🛑 Aborted by user after #{time_str}"
|
119
|
-
when :completed
|
120
|
-
"✅ Completed in #{time_str}"
|
121
|
-
when :failed
|
122
|
-
"❌ Failed after #{time_str}"
|
123
|
-
end
|
124
|
-
end
|
125
|
-
|
126
|
-
# Check if execution is still active
|
127
|
-
def active?
|
128
|
-
[:starting, :running, :warning].include?(@state)
|
129
|
-
end
|
130
|
-
|
131
|
-
# Check if execution completed successfully
|
132
|
-
def success?
|
133
|
-
@state == :completed && @exit_code == 0
|
134
|
-
end
|
135
|
-
|
136
|
-
# Show timeout warning and give user control
|
137
|
-
def show_timeout_warning
|
138
|
-
return if @warning_shown
|
139
|
-
@warning_shown = true
|
140
|
-
@state = :warning
|
141
|
-
|
142
|
-
puts "\n⚠️ Agent has been running for #{@timeout_seconds} seconds"
|
143
|
-
puts " This is longer than expected, but the agent may still be working."
|
144
|
-
puts " You can:"
|
145
|
-
puts " 1. Continue waiting (press Enter)"
|
146
|
-
puts " 2. Abort execution (type 'abort' and press Enter)"
|
147
|
-
puts " 3. Wait 5 more minutes (type 'wait' and press Enter)"
|
148
|
-
|
149
|
-
begin
|
150
|
-
Timeout.timeout(30) do
|
151
|
-
response = gets&.chomp&.downcase
|
152
|
-
case response
|
153
|
-
when "abort"
|
154
|
-
puts "🛑 Aborting execution..."
|
155
|
-
@user_aborted = true
|
156
|
-
@state = :user_aborted
|
157
|
-
kill!
|
158
|
-
when "wait"
|
159
|
-
puts "⏰ Will warn again in 5 minutes..."
|
160
|
-
@warning_shown = false
|
161
|
-
@state = :running
|
162
|
-
# Start another warning thread for 5 more minutes
|
163
|
-
Thread.new do
|
164
|
-
sleep 300 # 5 minutes
|
165
|
-
if @state == :running && !@warning_shown
|
166
|
-
show_timeout_warning
|
167
|
-
end
|
168
|
-
end
|
169
|
-
else
|
170
|
-
puts "🔄 Continuing to wait..."
|
171
|
-
@state = :running
|
172
|
-
end
|
173
|
-
end
|
174
|
-
rescue Timeout::Error
|
175
|
-
puts "⏰ No response received, continuing to wait..."
|
176
|
-
@state = :running
|
177
|
-
rescue Interrupt
|
178
|
-
puts "\n🛑 User interrupted, aborting..."
|
179
|
-
@user_aborted = true
|
180
|
-
@state = :user_aborted
|
181
|
-
kill!
|
182
|
-
end
|
183
|
-
end
|
184
|
-
|
185
|
-
# Force kill the process
|
186
|
-
def kill!
|
187
|
-
return unless @process_pid && active?
|
188
|
-
|
189
|
-
puts "💀 Force killing agent process (PID: #{@process_pid})"
|
190
|
-
|
191
|
-
begin
|
192
|
-
# Try graceful termination first
|
193
|
-
Process.kill("TERM", @process_pid)
|
194
|
-
sleep 1
|
195
|
-
|
196
|
-
# Force kill if still running
|
197
|
-
if process_running?(@process_pid)
|
198
|
-
Process.kill("KILL", @process_pid)
|
199
|
-
sleep 1
|
200
|
-
end
|
201
|
-
|
202
|
-
# Double-check and force kill again if needed
|
203
|
-
if process_running?(@process_pid)
|
204
|
-
puts "⚠️ Process still running, using SIGKILL..."
|
205
|
-
Process.kill("KILL", @process_pid)
|
206
|
-
sleep 1
|
207
|
-
end
|
208
|
-
|
209
|
-
@state = :user_aborted
|
210
|
-
rescue Errno::ESRCH
|
211
|
-
# Process already dead
|
212
|
-
@state = :user_aborted
|
213
|
-
rescue => e
|
214
|
-
puts "⚠️ Error killing process: #{e.message}"
|
215
|
-
# Try one more time with KILL
|
216
|
-
begin
|
217
|
-
Process.kill("KILL", @process_pid) if process_running?(@process_pid)
|
218
|
-
rescue Errno::ESRCH
|
219
|
-
# Process already terminated
|
220
|
-
end
|
221
|
-
end
|
222
|
-
end
|
223
|
-
|
224
|
-
private
|
225
|
-
|
226
|
-
def start_supervisor_thread(wait)
|
227
|
-
@supervisor_thread = Thread.new do
|
228
|
-
loop do
|
229
|
-
sleep 10 # Check every 10 seconds
|
230
|
-
|
231
|
-
# Check if process is done
|
232
|
-
if wait.value
|
233
|
-
break
|
234
|
-
end
|
235
|
-
|
236
|
-
# Check for stuck condition (no output for 3 minutes)
|
237
|
-
if @last_output_time && Time.now - @last_output_time > 180
|
238
|
-
puts "⚠️ Agent appears stuck (no output for 3+ minutes)"
|
239
|
-
# Don't kill, just warn
|
240
|
-
end
|
241
|
-
end
|
242
|
-
rescue => e
|
243
|
-
puts "⚠️ Supervisor thread error: #{e.message}" if @debug
|
244
|
-
end
|
245
|
-
|
246
|
-
@supervisor_thread
|
247
|
-
end
|
248
|
-
|
249
|
-
def start_output_threads(stdout, stderr)
|
250
|
-
# Stdout thread
|
251
|
-
@output_threads << Thread.new do
|
252
|
-
stdout.each_line do |line|
|
253
|
-
@output += line
|
254
|
-
@output_count += 1
|
255
|
-
@last_output_time = Time.now
|
256
|
-
|
257
|
-
if @debug
|
258
|
-
puts "📤 #{line.chomp}"
|
259
|
-
end
|
260
|
-
end
|
261
|
-
rescue IOError => e
|
262
|
-
puts "📤 stdout closed: #{e.message}" if @debug
|
263
|
-
rescue => e
|
264
|
-
puts "⚠️ stdout thread error: #{e.message}" if @debug
|
265
|
-
end
|
266
|
-
|
267
|
-
# Stderr thread
|
268
|
-
@output_threads << Thread.new do
|
269
|
-
stderr.each_line do |line|
|
270
|
-
@error_output += line
|
271
|
-
@output_count += 1
|
272
|
-
@last_output_time = Time.now
|
273
|
-
|
274
|
-
if @debug
|
275
|
-
puts "❌ #{line.chomp}"
|
276
|
-
end
|
277
|
-
end
|
278
|
-
rescue IOError => e
|
279
|
-
puts "❌ stderr closed: #{e.message}" if @debug
|
280
|
-
rescue => e
|
281
|
-
puts "⚠️ stderr thread error: #{e.message}" if @debug
|
282
|
-
end
|
283
|
-
end
|
284
|
-
|
285
|
-
def wait_for_completion(wait)
|
286
|
-
# Wait for process to complete
|
287
|
-
exit_status = wait.value
|
288
|
-
@exit_code = exit_status.exitstatus
|
289
|
-
|
290
|
-
# Update duration
|
291
|
-
@duration = Time.now - @start_time
|
292
|
-
|
293
|
-
if @user_aborted || @state == :user_aborted
|
294
|
-
# Process was killed by user
|
295
|
-
{
|
296
|
-
success: false,
|
297
|
-
state: @state,
|
298
|
-
output: @output,
|
299
|
-
error_output: @error_output,
|
300
|
-
exit_code: @exit_code,
|
301
|
-
duration: @duration,
|
302
|
-
reason: "user_aborted"
|
303
|
-
}
|
304
|
-
elsif exit_status.success?
|
305
|
-
@state = :completed
|
306
|
-
{
|
307
|
-
success: true,
|
308
|
-
state: @state,
|
309
|
-
output: @output,
|
310
|
-
error_output: @error_output,
|
311
|
-
exit_code: @exit_code,
|
312
|
-
duration: @duration
|
313
|
-
}
|
314
|
-
else
|
315
|
-
@state = :failed
|
316
|
-
{
|
317
|
-
success: false,
|
318
|
-
state: @state,
|
319
|
-
output: @output,
|
320
|
-
error_output: @error_output,
|
321
|
-
exit_code: @exit_code,
|
322
|
-
duration: @duration,
|
323
|
-
reason: "non_zero_exit"
|
324
|
-
}
|
325
|
-
end
|
326
|
-
end
|
327
|
-
|
328
|
-
def cleanup_threads
|
329
|
-
# Wait for output threads to finish (with timeout)
|
330
|
-
@output_threads.each do |thread|
|
331
|
-
thread.join(5) # Wait up to 5 seconds
|
332
|
-
rescue => e
|
333
|
-
puts "⚠️ Error joining thread: #{e.message}" if @debug
|
334
|
-
end
|
335
|
-
|
336
|
-
# Kill supervisor thread
|
337
|
-
@supervisor_thread&.kill
|
338
|
-
end
|
339
|
-
|
340
|
-
def process_running?(pid)
|
341
|
-
Process.kill(0, pid)
|
342
|
-
true
|
343
|
-
rescue Errno::ESRCH
|
344
|
-
false
|
345
|
-
end
|
346
|
-
end
|
347
|
-
end
|
348
|
-
end
|
@@ -1,317 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require_relative "agent_supervisor"
|
4
|
-
|
5
|
-
module Aidp
|
6
|
-
module Providers
|
7
|
-
# Base class for providers that use the agent supervisor
|
8
|
-
class SupervisedBase
|
9
|
-
# Timeout constants are now configurable via environment variables for flexibility
|
10
|
-
DEFAULT_TIMEOUT = Integer(ENV.fetch("AIDP_DEFAULT_TIMEOUT", "300")) # 5 minutes for general operations
|
11
|
-
QUICK_MODE_TIMEOUT = Integer(ENV.fetch("AIDP_QUICK_MODE_TIMEOUT", "120")) # 2 minutes for testing
|
12
|
-
REPOSITORY_ANALYSIS_TIMEOUT = Integer(ENV.fetch("AIDP_REPOSITORY_ANALYSIS_TIMEOUT", "180")) # 3 minutes
|
13
|
-
ARCHITECTURE_ANALYSIS_TIMEOUT = Integer(ENV.fetch("AIDP_ARCHITECTURE_ANALYSIS_TIMEOUT", "600")) # 10 minutes
|
14
|
-
TEST_ANALYSIS_TIMEOUT = Integer(ENV.fetch("AIDP_TEST_ANALYSIS_TIMEOUT", "300")) # 5 minutes
|
15
|
-
FUNCTIONALITY_ANALYSIS_TIMEOUT = Integer(ENV.fetch("AIDP_FUNCTIONALITY_ANALYSIS_TIMEOUT", "600")) # 10 minutes
|
16
|
-
DOCUMENTATION_ANALYSIS_TIMEOUT = Integer(ENV.fetch("AIDP_DOCUMENTATION_ANALYSIS_TIMEOUT", "300")) # 5 minutes
|
17
|
-
STATIC_ANALYSIS_TIMEOUT = Integer(ENV.fetch("AIDP_STATIC_ANALYSIS_TIMEOUT", "450")) # 7.5 minutes
|
18
|
-
REFACTORING_RECOMMENDATIONS_TIMEOUT = Integer(ENV.fetch("AIDP_REFACTORING_RECOMMENDATIONS_TIMEOUT", "600")) # 10 minutes
|
19
|
-
ADAPTIVE_TIMEOUT_BUFFER = Float(ENV.fetch("AIDP_ADAPTIVE_TIMEOUT_BUFFER", "1.2")) # 20% buffer for adaptive timeouts
|
20
|
-
attr_reader :name, :last_execution_result, :metrics
|
21
|
-
|
22
|
-
def initialize
|
23
|
-
@last_execution_result = nil
|
24
|
-
@metrics = {
|
25
|
-
total_executions: 0,
|
26
|
-
successful_executions: 0,
|
27
|
-
timeout_count: 0,
|
28
|
-
failure_count: 0,
|
29
|
-
average_duration: 0.0,
|
30
|
-
total_duration: 0.0
|
31
|
-
}
|
32
|
-
@job_context = nil
|
33
|
-
end
|
34
|
-
|
35
|
-
# Abstract method - must be implemented by subclasses
|
36
|
-
def command
|
37
|
-
raise NotImplementedError, "#{self.class} must implement #command"
|
38
|
-
end
|
39
|
-
|
40
|
-
# Abstract method - must be implemented by subclasses
|
41
|
-
def provider_name
|
42
|
-
raise NotImplementedError, "#{self.class} must implement #provider_name"
|
43
|
-
end
|
44
|
-
|
45
|
-
# Set job context for background execution
|
46
|
-
def set_job_context(job_id:, execution_id:, job_manager:)
|
47
|
-
@job_context = {
|
48
|
-
job_id: job_id,
|
49
|
-
execution_id: execution_id,
|
50
|
-
job_manager: job_manager
|
51
|
-
}
|
52
|
-
end
|
53
|
-
|
54
|
-
# Execute with supervision and recovery
|
55
|
-
def send(prompt:, session: nil)
|
56
|
-
timeout_seconds = calculate_timeout
|
57
|
-
debug = ENV["AIDP_DEBUG"] == "1"
|
58
|
-
|
59
|
-
log_info("Executing with #{provider_name} provider (timeout: #{timeout_seconds}s)")
|
60
|
-
|
61
|
-
# Create supervisor
|
62
|
-
supervisor = AgentSupervisor.new(
|
63
|
-
command,
|
64
|
-
timeout_seconds: timeout_seconds,
|
65
|
-
debug: debug
|
66
|
-
)
|
67
|
-
|
68
|
-
begin
|
69
|
-
# Execute with supervision
|
70
|
-
result = supervisor.execute(prompt)
|
71
|
-
|
72
|
-
# Update metrics
|
73
|
-
update_metrics(supervisor, result)
|
74
|
-
|
75
|
-
# Store result for debugging
|
76
|
-
@last_execution_result = result
|
77
|
-
|
78
|
-
if result[:success]
|
79
|
-
log_info("#{provider_name} completed successfully in #{format_duration(result[:duration])}")
|
80
|
-
result[:output]
|
81
|
-
else
|
82
|
-
handle_execution_failure(result, supervisor)
|
83
|
-
end
|
84
|
-
rescue => e
|
85
|
-
log_error("#{provider_name} execution error: #{e.message}")
|
86
|
-
|
87
|
-
# Try to kill the process if it's still running
|
88
|
-
supervisor.kill! if supervisor.active?
|
89
|
-
|
90
|
-
raise
|
91
|
-
end
|
92
|
-
end
|
93
|
-
|
94
|
-
# Get execution statistics
|
95
|
-
def stats
|
96
|
-
@metrics.dup
|
97
|
-
end
|
98
|
-
|
99
|
-
# Reset statistics
|
100
|
-
def reset_stats!
|
101
|
-
@metrics = {
|
102
|
-
total_executions: 0,
|
103
|
-
successful_executions: 0,
|
104
|
-
timeout_count: 0,
|
105
|
-
failure_count: 0,
|
106
|
-
average_duration: 0.0,
|
107
|
-
total_duration: 0.0
|
108
|
-
}
|
109
|
-
end
|
110
|
-
|
111
|
-
# Check if provider supports activity monitoring
|
112
|
-
def supports_activity_monitoring?
|
113
|
-
true # Supervised providers always support activity monitoring
|
114
|
-
end
|
115
|
-
|
116
|
-
# Get activity summary for metrics (compatibility with old interface)
|
117
|
-
def activity_summary
|
118
|
-
return {} unless @last_execution_result
|
119
|
-
|
120
|
-
{
|
121
|
-
provider: provider_name,
|
122
|
-
step_name: ENV["AIDP_CURRENT_STEP"],
|
123
|
-
start_time: @last_execution_result[:start_time],
|
124
|
-
end_time: @last_execution_result[:end_time],
|
125
|
-
duration: @last_execution_result[:duration],
|
126
|
-
final_state: @last_execution_result[:state],
|
127
|
-
stuck_detected: false, # Supervisor handles this differently
|
128
|
-
output_count: @last_execution_result[:output_count] || 0
|
129
|
-
}
|
130
|
-
end
|
131
|
-
|
132
|
-
# Compatibility methods for old activity monitoring interface
|
133
|
-
def setup_activity_monitoring(step_name, callback = nil, timeout = nil)
|
134
|
-
# No-op for supervised providers - supervisor handles this
|
135
|
-
end
|
136
|
-
|
137
|
-
def record_activity(message = nil)
|
138
|
-
# No-op for supervised providers - supervisor handles this
|
139
|
-
end
|
140
|
-
|
141
|
-
def mark_completed
|
142
|
-
# No-op for supervised providers - supervisor handles this
|
143
|
-
end
|
144
|
-
|
145
|
-
def mark_failed(message = nil)
|
146
|
-
# No-op for supervised providers - supervisor handles this
|
147
|
-
end
|
148
|
-
|
149
|
-
private
|
150
|
-
|
151
|
-
def calculate_timeout
|
152
|
-
# Priority order for timeout calculation:
|
153
|
-
# 1. Quick mode (for testing)
|
154
|
-
# 2. Environment variable override
|
155
|
-
# 3. Adaptive timeout based on step type
|
156
|
-
# 4. Default timeout
|
157
|
-
|
158
|
-
if ENV["AIDP_QUICK_MODE"]
|
159
|
-
log_info("Quick mode enabled - #{QUICK_MODE_TIMEOUT / 60} minute timeout")
|
160
|
-
return QUICK_MODE_TIMEOUT
|
161
|
-
end
|
162
|
-
|
163
|
-
provider_timeout_var = "AIDP_#{provider_name.upcase}_TIMEOUT"
|
164
|
-
if ENV[provider_timeout_var]
|
165
|
-
return ENV[provider_timeout_var].to_i
|
166
|
-
end
|
167
|
-
|
168
|
-
# Adaptive timeout based on step type
|
169
|
-
step_timeout = get_adaptive_timeout
|
170
|
-
if step_timeout
|
171
|
-
log_info("Using adaptive timeout: #{step_timeout} seconds")
|
172
|
-
return step_timeout
|
173
|
-
end
|
174
|
-
|
175
|
-
# Default timeout for interactive use
|
176
|
-
log_info("Using default timeout: #{DEFAULT_TIMEOUT / 60} minutes")
|
177
|
-
DEFAULT_TIMEOUT
|
178
|
-
end
|
179
|
-
|
180
|
-
def get_adaptive_timeout
|
181
|
-
# Try to get timeout recommendations from metrics storage
|
182
|
-
begin
|
183
|
-
require_relative "../analyze/metrics_storage"
|
184
|
-
storage = Aidp::Analyze::MetricsStorage.new(Dir.pwd)
|
185
|
-
recommendations = storage.calculate_timeout_recommendations
|
186
|
-
|
187
|
-
# Get current step name from environment or context
|
188
|
-
step_name = ENV["AIDP_CURRENT_STEP"] || "unknown"
|
189
|
-
|
190
|
-
if recommendations[step_name]
|
191
|
-
recommended = recommendations[step_name][:recommended_timeout]
|
192
|
-
# Add buffer for safety
|
193
|
-
return (recommended * ADAPTIVE_TIMEOUT_BUFFER).ceil
|
194
|
-
end
|
195
|
-
rescue => e
|
196
|
-
log_warning("Could not get adaptive timeout: #{e.message}") if ENV["AIDP_DEBUG"]
|
197
|
-
end
|
198
|
-
|
199
|
-
# Fallback timeouts based on step type patterns
|
200
|
-
step_name = ENV["AIDP_CURRENT_STEP"] || ""
|
201
|
-
|
202
|
-
case step_name
|
203
|
-
when /REPOSITORY_ANALYSIS/
|
204
|
-
REPOSITORY_ANALYSIS_TIMEOUT
|
205
|
-
when /ARCHITECTURE_ANALYSIS/
|
206
|
-
ARCHITECTURE_ANALYSIS_TIMEOUT
|
207
|
-
when /TEST_ANALYSIS/
|
208
|
-
TEST_ANALYSIS_TIMEOUT
|
209
|
-
when /FUNCTIONALITY_ANALYSIS/
|
210
|
-
FUNCTIONALITY_ANALYSIS_TIMEOUT
|
211
|
-
when /DOCUMENTATION_ANALYSIS/
|
212
|
-
DOCUMENTATION_ANALYSIS_TIMEOUT
|
213
|
-
when /STATIC_ANALYSIS/
|
214
|
-
STATIC_ANALYSIS_TIMEOUT
|
215
|
-
when /REFACTORING_RECOMMENDATIONS/
|
216
|
-
REFACTORING_RECOMMENDATIONS_TIMEOUT
|
217
|
-
else
|
218
|
-
nil # Use default
|
219
|
-
end
|
220
|
-
end
|
221
|
-
|
222
|
-
def update_metrics(supervisor, result)
|
223
|
-
@metrics[:total_executions] += 1
|
224
|
-
@metrics[:total_duration] += supervisor.duration
|
225
|
-
@metrics[:average_duration] = @metrics[:total_duration] / @metrics[:total_executions]
|
226
|
-
|
227
|
-
case result[:state]
|
228
|
-
when :completed
|
229
|
-
@metrics[:successful_executions] += 1
|
230
|
-
when :timeout
|
231
|
-
@metrics[:timeout_count] += 1
|
232
|
-
when :failed, :killed
|
233
|
-
@metrics[:failure_count] += 1
|
234
|
-
end
|
235
|
-
|
236
|
-
# Log metrics update if in job context
|
237
|
-
if @job_context
|
238
|
-
@job_context[:job_manager].log_message(
|
239
|
-
@job_context[:job_id],
|
240
|
-
@job_context[:execution_id],
|
241
|
-
"Updated execution metrics",
|
242
|
-
"debug",
|
243
|
-
@metrics
|
244
|
-
)
|
245
|
-
end
|
246
|
-
end
|
247
|
-
|
248
|
-
def handle_execution_failure(result, supervisor)
|
249
|
-
case result[:reason]
|
250
|
-
when "user_aborted"
|
251
|
-
message = "#{provider_name} was aborted by user after #{format_duration(result[:duration])}"
|
252
|
-
log_error(message)
|
253
|
-
raise Interrupt, message
|
254
|
-
when "non_zero_exit"
|
255
|
-
error_msg = result[:error_output].empty? ? "Unknown error" : result[:error_output].strip
|
256
|
-
message = "#{provider_name} failed with exit code #{result[:exit_code]}: #{error_msg}"
|
257
|
-
log_error(message)
|
258
|
-
raise message
|
259
|
-
else
|
260
|
-
message = "#{provider_name} failed: #{result[:reason] || "Unknown error"}"
|
261
|
-
log_error(message)
|
262
|
-
raise message
|
263
|
-
end
|
264
|
-
end
|
265
|
-
|
266
|
-
def format_duration(seconds)
|
267
|
-
minutes = (seconds / 60).to_i
|
268
|
-
secs = (seconds % 60).to_i
|
269
|
-
|
270
|
-
if minutes > 0
|
271
|
-
"#{minutes}m #{secs}s"
|
272
|
-
else
|
273
|
-
"#{secs}s"
|
274
|
-
end
|
275
|
-
end
|
276
|
-
|
277
|
-
def log_info(message)
|
278
|
-
if @job_context
|
279
|
-
@job_context[:job_manager].log_message(
|
280
|
-
@job_context[:job_id],
|
281
|
-
@job_context[:execution_id],
|
282
|
-
message,
|
283
|
-
"info"
|
284
|
-
)
|
285
|
-
else
|
286
|
-
puts message
|
287
|
-
end
|
288
|
-
end
|
289
|
-
|
290
|
-
def log_warning(message)
|
291
|
-
if @job_context
|
292
|
-
@job_context[:job_manager].log_message(
|
293
|
-
@job_context[:job_id],
|
294
|
-
@job_context[:execution_id],
|
295
|
-
message,
|
296
|
-
"warning"
|
297
|
-
)
|
298
|
-
else
|
299
|
-
puts "⚠️ #{message}"
|
300
|
-
end
|
301
|
-
end
|
302
|
-
|
303
|
-
def log_error(message)
|
304
|
-
if @job_context
|
305
|
-
@job_context[:job_manager].log_message(
|
306
|
-
@job_context[:job_id],
|
307
|
-
@job_context[:execution_id],
|
308
|
-
message,
|
309
|
-
"error"
|
310
|
-
)
|
311
|
-
else
|
312
|
-
puts "❌ #{message}"
|
313
|
-
end
|
314
|
-
end
|
315
|
-
end
|
316
|
-
end
|
317
|
-
end
|
@@ -1,22 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require_relative "supervised_base"
|
4
|
-
require_relative "../util"
|
5
|
-
|
6
|
-
module Aidp
|
7
|
-
module Providers
|
8
|
-
class SupervisedCursor < SupervisedBase
|
9
|
-
def self.available?
|
10
|
-
!!Aidp::Util.which("cursor-agent")
|
11
|
-
end
|
12
|
-
|
13
|
-
def provider_name
|
14
|
-
"cursor"
|
15
|
-
end
|
16
|
-
|
17
|
-
def command
|
18
|
-
["cursor-agent", "-p"]
|
19
|
-
end
|
20
|
-
end
|
21
|
-
end
|
22
|
-
end
|
data/lib/aidp/sync.rb
DELETED