aidp 0.5.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 +128 -151
- data/bin/aidp +1 -1
- data/lib/aidp/analysis/kb_inspector.rb +471 -0
- data/lib/aidp/analysis/seams.rb +159 -0
- data/lib/aidp/analysis/tree_sitter_grammar_loader.rb +480 -0
- data/lib/aidp/analysis/tree_sitter_scan.rb +686 -0
- data/lib/aidp/analyze/error_handler.rb +2 -78
- 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/analyze/steps.rb +6 -0
- data/lib/aidp/cli/jobs_command.rb +103 -435
- data/lib/aidp/cli.rb +317 -191
- 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 -109
- data/lib/aidp/providers/base.rb +170 -0
- data/lib/aidp/providers/cursor.rb +52 -183
- data/lib/aidp/providers/gemini.rb +24 -109
- 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 +56 -35
- data/templates/ANALYZE/06a_tree_sitter_scan.md +217 -0
- 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 +106 -64
- data/lib/aidp/analyze/agent_personas.rb +0 -71
- data/lib/aidp/analyze/agent_tool_executor.rb +0 -445
- data/lib/aidp/analyze/data_retention_manager.rb +0 -426
- data/lib/aidp/analyze/database.rb +0 -260
- data/lib/aidp/analyze/dependencies.rb +0 -335
- data/lib/aidp/analyze/export_manager.rb +0 -425
- data/lib/aidp/analyze/focus_guidance.rb +0 -517
- data/lib/aidp/analyze/incremental_analyzer.rb +0 -543
- data/lib/aidp/analyze/language_analysis_strategies.rb +0 -897
- data/lib/aidp/analyze/large_analysis_progress.rb +0 -504
- data/lib/aidp/analyze/memory_manager.rb +0 -365
- data/lib/aidp/analyze/metrics_storage.rb +0 -336
- data/lib/aidp/analyze/parallel_processor.rb +0 -460
- data/lib/aidp/analyze/performance_optimizer.rb +0 -694
- data/lib/aidp/analyze/repository_chunker.rb +0 -704
- data/lib/aidp/analyze/static_analysis_detector.rb +0 -577
- data/lib/aidp/analyze/storage.rb +0 -662
- data/lib/aidp/analyze/tool_configuration.rb +0 -456
- 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/database_migration.rb +0 -158
- data/lib/aidp/job_manager.rb +0 -41
- data/lib/aidp/jobs/base_job.rb +0 -47
- data/lib/aidp/jobs/provider_execution_job.rb +0 -96
- 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,117 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require "json"
|
4
|
-
require "yaml"
|
5
|
-
|
6
|
-
module Aidp
|
7
|
-
# Detects project type, language, framework, and other characteristics
|
8
|
-
class ProjectDetector
|
9
|
-
attr_reader :project_dir
|
10
|
-
|
11
|
-
def initialize(project_dir = Dir.pwd)
|
12
|
-
@project_dir = project_dir
|
13
|
-
end
|
14
|
-
|
15
|
-
def detect
|
16
|
-
{
|
17
|
-
language: detect_language,
|
18
|
-
framework: detect_framework,
|
19
|
-
build_system: detect_build_system,
|
20
|
-
package_manager: detect_package_manager,
|
21
|
-
static_analysis_tools: detect_static_analysis_tools,
|
22
|
-
test_framework: detect_test_framework,
|
23
|
-
database: detect_database,
|
24
|
-
deployment: detect_deployment
|
25
|
-
}
|
26
|
-
end
|
27
|
-
|
28
|
-
private
|
29
|
-
|
30
|
-
def detect_language
|
31
|
-
return "ruby" if File.exist?(File.join(@project_dir, "Gemfile"))
|
32
|
-
return "javascript" if File.exist?(File.join(@project_dir, "package.json"))
|
33
|
-
return "python" if File.exist?(File.join(@project_dir, "requirements.txt")) || File.exist?(File.join(@project_dir, "pyproject.toml"))
|
34
|
-
return "java" if File.exist?(File.join(@project_dir, "pom.xml")) || File.exist?(File.join(@project_dir, "build.gradle"))
|
35
|
-
return "go" if File.exist?(File.join(@project_dir, "go.mod"))
|
36
|
-
return "rust" if File.exist?(File.join(@project_dir, "Cargo.toml"))
|
37
|
-
return "csharp" if File.exist?(File.join(@project_dir, "*.csproj"))
|
38
|
-
"unknown"
|
39
|
-
end
|
40
|
-
|
41
|
-
def detect_framework
|
42
|
-
case detect_language
|
43
|
-
when "ruby"
|
44
|
-
return "rails" if File.exist?(File.join(@project_dir, "config", "application.rb"))
|
45
|
-
return "sinatra" if File.exist?(File.join(@project_dir, "app.rb")) && File.read(File.join(@project_dir, "app.rb")).include?("Sinatra")
|
46
|
-
when "javascript"
|
47
|
-
return "react" if File.exist?(File.join(@project_dir, "package.json")) && File.read(File.join(@project_dir, "package.json")).include?("react")
|
48
|
-
return "vue" if File.exist?(File.join(@project_dir, "package.json")) && File.read(File.join(@project_dir, "package.json")).include?("vue")
|
49
|
-
return "angular" if File.exist?(File.join(@project_dir, "angular.json"))
|
50
|
-
return "express" if File.exist?(File.join(@project_dir, "package.json")) && File.read(File.join(@project_dir, "package.json")).include?("express")
|
51
|
-
when "python"
|
52
|
-
return "django" if File.exist?(File.join(@project_dir, "manage.py"))
|
53
|
-
return "flask" if File.exist?(File.join(@project_dir, "app.py")) && File.read(File.join(@project_dir, "app.py")).include?("Flask")
|
54
|
-
when "java"
|
55
|
-
return "spring" if File.exist?(File.join(@project_dir, "pom.xml")) && File.read(File.join(@project_dir, "pom.xml")).include?("spring-boot")
|
56
|
-
end
|
57
|
-
"unknown"
|
58
|
-
end
|
59
|
-
|
60
|
-
def detect_build_system
|
61
|
-
return "maven" if File.exist?(File.join(@project_dir, "pom.xml"))
|
62
|
-
return "gradle" if File.exist?(File.join(@project_dir, "build.gradle"))
|
63
|
-
return "npm" if File.exist?(File.join(@project_dir, "package.json"))
|
64
|
-
return "bundler" if File.exist?(File.join(@project_dir, "Gemfile"))
|
65
|
-
return "pip" if File.exist?(File.join(@project_dir, "requirements.txt"))
|
66
|
-
return "cargo" if File.exist?(File.join(@project_dir, "Cargo.toml"))
|
67
|
-
return "go" if File.exist?(File.join(@project_dir, "go.mod"))
|
68
|
-
"unknown"
|
69
|
-
end
|
70
|
-
|
71
|
-
def detect_package_manager
|
72
|
-
detect_build_system
|
73
|
-
end
|
74
|
-
|
75
|
-
def detect_static_analysis_tools
|
76
|
-
tools = []
|
77
|
-
tools << "rubocop" if File.exist?(File.join(@project_dir, ".rubocop.yml"))
|
78
|
-
tools << "eslint" if File.exist?(File.join(@project_dir, ".eslintrc"))
|
79
|
-
tools << "flake8" if File.exist?(File.join(@project_dir, ".flake8"))
|
80
|
-
tools << "checkstyle" if File.exist?(File.join(@project_dir, "checkstyle.xml"))
|
81
|
-
tools << "clippy" if File.exist?(File.join(@project_dir, "Cargo.toml"))
|
82
|
-
tools
|
83
|
-
end
|
84
|
-
|
85
|
-
def detect_test_framework
|
86
|
-
case detect_language
|
87
|
-
when "ruby"
|
88
|
-
return "rspec" if File.exist?(File.join(@project_dir, "spec"))
|
89
|
-
return "minitest" if File.exist?(File.join(@project_dir, "test"))
|
90
|
-
when "javascript"
|
91
|
-
return "jest" if File.exist?(File.join(@project_dir, "package.json")) && File.read(File.join(@project_dir, "package.json")).include?("jest")
|
92
|
-
return "mocha" if File.exist?(File.join(@project_dir, "package.json")) && File.read(File.join(@project_dir, "package.json")).include?("mocha")
|
93
|
-
when "python"
|
94
|
-
return "pytest" if File.exist?(File.join(@project_dir, "pytest.ini"))
|
95
|
-
return "unittest" if Dir.exist?(File.join(@project_dir, "tests"))
|
96
|
-
when "java"
|
97
|
-
return "junit" if File.exist?(File.join(@project_dir, "src", "test"))
|
98
|
-
end
|
99
|
-
"unknown"
|
100
|
-
end
|
101
|
-
|
102
|
-
def detect_database
|
103
|
-
return "postgresql" if File.exist?(File.join(@project_dir, "config", "database.yml")) && File.read(File.join(@project_dir, "config", "database.yml")).include?("postgresql")
|
104
|
-
return "mysql" if File.exist?(File.join(@project_dir, "config", "database.yml")) && File.read(File.join(@project_dir, "config", "database.yml")).include?("mysql")
|
105
|
-
return "sqlite" if File.exist?(File.join(@project_dir, "config", "database.yml")) && File.read(File.join(@project_dir, "config", "database.yml")).include?("sqlite")
|
106
|
-
"unknown"
|
107
|
-
end
|
108
|
-
|
109
|
-
def detect_deployment
|
110
|
-
return "docker" if File.exist?(File.join(@project_dir, "Dockerfile"))
|
111
|
-
return "kubernetes" if File.exist?(File.join(@project_dir, "k8s")) || File.exist?(File.join(@project_dir, "kubernetes"))
|
112
|
-
return "heroku" if File.exist?(File.join(@project_dir, "Procfile"))
|
113
|
-
return "aws" if File.exist?(File.join(@project_dir, "serverless.yml")) || File.exist?(File.join(@project_dir, "template.yaml"))
|
114
|
-
"unknown"
|
115
|
-
end
|
116
|
-
end
|
117
|
-
end
|
@@ -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
|
219
|
-
# Give up
|
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
|