aidp 0.22.0 → 0.24.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 +145 -31
- data/lib/aidp/cli.rb +19 -2
- data/lib/aidp/execute/work_loop_runner.rb +252 -45
- data/lib/aidp/execute/work_loop_unit_scheduler.rb +27 -2
- data/lib/aidp/harness/condition_detector.rb +42 -8
- data/lib/aidp/harness/config_manager.rb +7 -0
- data/lib/aidp/harness/config_schema.rb +25 -0
- data/lib/aidp/harness/configuration.rb +69 -6
- data/lib/aidp/harness/error_handler.rb +117 -44
- data/lib/aidp/harness/provider_manager.rb +64 -0
- data/lib/aidp/harness/provider_metrics.rb +138 -0
- data/lib/aidp/harness/runner.rb +110 -35
- data/lib/aidp/harness/simple_user_interface.rb +4 -0
- data/lib/aidp/harness/state/ui_state.rb +0 -10
- data/lib/aidp/harness/state_manager.rb +1 -15
- data/lib/aidp/harness/test_runner.rb +39 -2
- data/lib/aidp/logger.rb +34 -4
- data/lib/aidp/providers/adapter.rb +241 -0
- data/lib/aidp/providers/anthropic.rb +75 -7
- data/lib/aidp/providers/base.rb +29 -1
- data/lib/aidp/providers/capability_registry.rb +205 -0
- data/lib/aidp/providers/codex.rb +14 -0
- data/lib/aidp/providers/error_taxonomy.rb +195 -0
- data/lib/aidp/providers/gemini.rb +3 -2
- data/lib/aidp/setup/devcontainer/backup_manager.rb +11 -4
- data/lib/aidp/setup/provider_registry.rb +107 -0
- data/lib/aidp/setup/wizard.rb +189 -31
- data/lib/aidp/version.rb +1 -1
- data/lib/aidp/watch/build_processor.rb +357 -27
- data/lib/aidp/watch/plan_generator.rb +16 -1
- data/lib/aidp/watch/plan_processor.rb +54 -3
- data/lib/aidp/watch/repository_client.rb +78 -4
- data/lib/aidp/watch/repository_safety_checker.rb +12 -3
- data/lib/aidp/watch/runner.rb +52 -10
- data/lib/aidp/workflows/guided_agent.rb +53 -0
- data/lib/aidp/worktree.rb +67 -10
- data/templates/work_loop/decide_whats_next.md +21 -0
- data/templates/work_loop/diagnose_failures.md +21 -0
- metadata +10 -3
- /data/{bin → exe}/aidp +0 -0
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require "fileutils"
|
|
3
4
|
require_relative "deterministic_unit"
|
|
4
5
|
require_relative "../logger"
|
|
5
6
|
|
|
@@ -18,18 +19,21 @@ module Aidp
|
|
|
18
19
|
|
|
19
20
|
attr_reader :last_agentic_summary
|
|
20
21
|
|
|
21
|
-
def initialize(units_config, clock: Time)
|
|
22
|
+
def initialize(units_config, project_dir:, clock: Time)
|
|
22
23
|
@clock = clock
|
|
24
|
+
@project_dir = project_dir
|
|
23
25
|
@deterministic_definitions = build_deterministic_definitions(units_config[:deterministic])
|
|
24
26
|
@defaults = default_options.merge(units_config[:defaults] || {})
|
|
25
27
|
@pending_units = []
|
|
28
|
+
@initial_unit_requests = read_initial_unit_requests
|
|
26
29
|
@deterministic_history = []
|
|
27
30
|
@deterministic_state = Hash.new { |h, key| h[key] = default_deterministic_state }
|
|
28
31
|
@agentic_runs = []
|
|
29
32
|
@last_agentic_summary = nil
|
|
30
33
|
@consecutive_deciders = 0
|
|
31
34
|
@completed = false
|
|
32
|
-
|
|
35
|
+
apply_initial_requests
|
|
36
|
+
@started = @pending_units.any?
|
|
33
37
|
end
|
|
34
38
|
|
|
35
39
|
def next_unit
|
|
@@ -173,6 +177,27 @@ module Aidp
|
|
|
173
177
|
end
|
|
174
178
|
end
|
|
175
179
|
|
|
180
|
+
def read_initial_unit_requests
|
|
181
|
+
return [] unless @project_dir
|
|
182
|
+
|
|
183
|
+
path = File.join(@project_dir, ".aidp", "work_loop", "initial_units.txt")
|
|
184
|
+
return [] unless File.exist?(path)
|
|
185
|
+
|
|
186
|
+
requests = File.readlines(path, chomp: true).map(&:strip).reject(&:empty?)
|
|
187
|
+
File.delete(path)
|
|
188
|
+
requests
|
|
189
|
+
rescue => e
|
|
190
|
+
Aidp.logger.warn("work_loop", "Failed to read initial work loop requests", error: e.message)
|
|
191
|
+
[]
|
|
192
|
+
end
|
|
193
|
+
|
|
194
|
+
def apply_initial_requests
|
|
195
|
+
Array(@initial_unit_requests).each do |request|
|
|
196
|
+
queue_requested_unit(request.to_sym)
|
|
197
|
+
end
|
|
198
|
+
@initial_unit_requests = []
|
|
199
|
+
end
|
|
200
|
+
|
|
176
201
|
def default_deterministic_state
|
|
177
202
|
{last_run_at: nil, current_backoff: nil}
|
|
178
203
|
end
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require "time"
|
|
4
|
+
|
|
3
5
|
module Aidp
|
|
4
6
|
module Harness
|
|
5
7
|
# Detects run conditions (rate limits, user feedback, completion, errors)
|
|
@@ -15,14 +17,16 @@ module Aidp
|
|
|
15
17
|
/429/i,
|
|
16
18
|
/rate.{0,20}exceeded/i,
|
|
17
19
|
/throttled/i,
|
|
18
|
-
/limit.{0,20}exceeded/i
|
|
20
|
+
/limit.{0,20}exceeded/i,
|
|
21
|
+
/session limit/i
|
|
19
22
|
],
|
|
20
23
|
# Anthropic/Claude specific
|
|
21
24
|
anthropic: [
|
|
22
25
|
/rate limit exceeded/i,
|
|
23
26
|
/too many requests/i,
|
|
24
27
|
/quota.{0,20}exceeded/i,
|
|
25
|
-
/anthropic.{0,20}rate.{0,20}limit/i
|
|
28
|
+
/anthropic.{0,20}rate.{0,20}limit/i,
|
|
29
|
+
/session limit reached/i
|
|
26
30
|
],
|
|
27
31
|
# OpenAI specific
|
|
28
32
|
openai: [
|
|
@@ -143,10 +147,10 @@ module Aidp
|
|
|
143
147
|
|
|
144
148
|
# Rate limit reset time patterns
|
|
145
149
|
@reset_time_patterns = [
|
|
146
|
-
/reset
|
|
147
|
-
/retry
|
|
150
|
+
/reset(?:s)?\s+in\s+(\d+)\s+seconds/i,
|
|
151
|
+
/retry\s+after\s+(\d+)\s+seconds/i,
|
|
148
152
|
/wait[^\d]*(\d+)[^\d]*seconds/i,
|
|
149
|
-
/(\d+)
|
|
153
|
+
/(\d+)\s+seconds\s+until\s+reset/i,
|
|
150
154
|
/reset.{0,20}at.{0,20}(\d{4}-\d{2}-\d{2}\s+\d{2}:\d{2}:\d{2})/i,
|
|
151
155
|
/retry.{0,20}after.{0,20}(\d{4}-\d{2}-\d{2}\s+\d{2}:\d{2}:\d{2})/i
|
|
152
156
|
]
|
|
@@ -168,7 +172,18 @@ module Aidp
|
|
|
168
172
|
result[:output],
|
|
169
173
|
result[:response],
|
|
170
174
|
result[:body]
|
|
171
|
-
].compact.join(" ")
|
|
175
|
+
].compact.join(" ").strip
|
|
176
|
+
|
|
177
|
+
return nil if text_content.empty?
|
|
178
|
+
|
|
179
|
+
{
|
|
180
|
+
provider: provider,
|
|
181
|
+
detected_at: Time.now,
|
|
182
|
+
reset_time: extract_reset_time(text_content),
|
|
183
|
+
retry_after: extract_retry_after(text_content),
|
|
184
|
+
limit_type: detect_limit_type(text_content, provider),
|
|
185
|
+
message: text_content
|
|
186
|
+
}
|
|
172
187
|
|
|
173
188
|
return false if text_content.empty?
|
|
174
189
|
|
|
@@ -205,17 +220,34 @@ module Aidp
|
|
|
205
220
|
|
|
206
221
|
# Extract reset time from rate limit message
|
|
207
222
|
def extract_reset_time(text_content)
|
|
223
|
+
# Handle expressions like "resets 4am" or "reset at 4:30pm"
|
|
224
|
+
time_of_day_match = text_content.match(/reset(?:s)?(?:\s+at)?\s+(\d{1,2})(?::(\d{2}))?\s*(am|pm)/i)
|
|
225
|
+
if time_of_day_match
|
|
226
|
+
hour = time_of_day_match[1].to_i
|
|
227
|
+
minute = time_of_day_match[2] ? time_of_day_match[2].to_i : 0
|
|
228
|
+
meridiem = time_of_day_match[3].downcase
|
|
229
|
+
|
|
230
|
+
hour %= 12
|
|
231
|
+
hour += 12 if meridiem == "pm"
|
|
232
|
+
|
|
233
|
+
now = Time.now
|
|
234
|
+
reset_time = Time.new(now.year, now.month, now.day, hour, minute, 0, now.utc_offset)
|
|
235
|
+
reset_time += 86_400 if reset_time <= now
|
|
236
|
+
return reset_time
|
|
237
|
+
end
|
|
238
|
+
|
|
208
239
|
@reset_time_patterns.each do |pattern|
|
|
209
240
|
match = text_content.match(pattern)
|
|
210
241
|
next unless match
|
|
211
242
|
|
|
212
243
|
if match[1].match?(/^\d+$/)
|
|
213
244
|
# Seconds from now
|
|
214
|
-
Time.now + match[1].to_i
|
|
245
|
+
return Time.now + match[1].to_i
|
|
215
246
|
else
|
|
216
247
|
# Specific timestamp
|
|
217
248
|
begin
|
|
218
|
-
Time.parse(match[1])
|
|
249
|
+
parsed_time = Time.parse(match[1])
|
|
250
|
+
return parsed_time if parsed_time
|
|
219
251
|
rescue ArgumentError
|
|
220
252
|
nil
|
|
221
253
|
end
|
|
@@ -246,6 +278,8 @@ module Aidp
|
|
|
246
278
|
|
|
247
279
|
# Detect the type of rate limit
|
|
248
280
|
def detect_limit_type(text_content, provider)
|
|
281
|
+
return "session_limit" if text_content.match?(/session limit/i)
|
|
282
|
+
|
|
249
283
|
case provider&.to_s&.downcase
|
|
250
284
|
when "anthropic", "claude"
|
|
251
285
|
if text_content.match?(/requests per minute/i)
|
|
@@ -10,6 +10,8 @@ module Aidp
|
|
|
10
10
|
class ConfigManager
|
|
11
11
|
include ProviderTypeChecker
|
|
12
12
|
|
|
13
|
+
attr_reader :project_dir
|
|
14
|
+
|
|
13
15
|
def initialize(project_dir = Dir.pwd)
|
|
14
16
|
@project_dir = project_dir
|
|
15
17
|
@loader = ConfigLoader.new(project_dir)
|
|
@@ -101,6 +103,11 @@ module Aidp
|
|
|
101
103
|
}
|
|
102
104
|
end
|
|
103
105
|
|
|
106
|
+
# Get max retries (alias for backward compatibility with ErrorHandler)
|
|
107
|
+
def max_retries(options = {})
|
|
108
|
+
retry_config(options)[:max_attempts]
|
|
109
|
+
end
|
|
110
|
+
|
|
104
111
|
# Get circuit breaker configuration
|
|
105
112
|
def circuit_breaker_config(options = {})
|
|
106
113
|
harness_config = harness_config(options)
|
|
@@ -886,6 +886,31 @@ module Aidp
|
|
|
886
886
|
type: :string
|
|
887
887
|
}
|
|
888
888
|
},
|
|
889
|
+
dangerous_mode: {
|
|
890
|
+
type: :hash,
|
|
891
|
+
required: false,
|
|
892
|
+
default: {},
|
|
893
|
+
properties: {
|
|
894
|
+
enabled: {
|
|
895
|
+
type: :boolean,
|
|
896
|
+
required: false,
|
|
897
|
+
default: false
|
|
898
|
+
},
|
|
899
|
+
flags: {
|
|
900
|
+
type: :array,
|
|
901
|
+
required: false,
|
|
902
|
+
default: [],
|
|
903
|
+
items: {
|
|
904
|
+
type: :string
|
|
905
|
+
}
|
|
906
|
+
},
|
|
907
|
+
auto_enable_in_devcontainer: {
|
|
908
|
+
type: :boolean,
|
|
909
|
+
required: false,
|
|
910
|
+
default: true
|
|
911
|
+
}
|
|
912
|
+
}
|
|
913
|
+
},
|
|
889
914
|
models: {
|
|
890
915
|
type: :array,
|
|
891
916
|
required: false,
|
|
@@ -7,6 +7,8 @@ module Aidp
|
|
|
7
7
|
module Harness
|
|
8
8
|
# Handles loading and validation of harness configuration from aidp.yml
|
|
9
9
|
class Configuration
|
|
10
|
+
attr_reader :project_dir
|
|
11
|
+
|
|
10
12
|
def initialize(project_dir)
|
|
11
13
|
@project_dir = project_dir
|
|
12
14
|
@config = Aidp::Config.load_harness_config(project_dir)
|
|
@@ -600,7 +602,16 @@ module Aidp
|
|
|
600
602
|
|
|
601
603
|
# Get devcontainer configuration
|
|
602
604
|
def devcontainer_config
|
|
603
|
-
@
|
|
605
|
+
return @devcontainer_config if defined?(@devcontainer_config)
|
|
606
|
+
|
|
607
|
+
raw_config = @config[:devcontainer] || @config["devcontainer"]
|
|
608
|
+
base = deep_dup(default_devcontainer_config)
|
|
609
|
+
|
|
610
|
+
@devcontainer_config = if raw_config.is_a?(Hash)
|
|
611
|
+
deep_merge_hashes(base, deep_symbolize_keys(raw_config))
|
|
612
|
+
else
|
|
613
|
+
base
|
|
614
|
+
end
|
|
604
615
|
end
|
|
605
616
|
|
|
606
617
|
# Check if devcontainer features are enabled
|
|
@@ -629,7 +640,10 @@ module Aidp
|
|
|
629
640
|
|
|
630
641
|
# Get devcontainer permissions config
|
|
631
642
|
def devcontainer_permissions
|
|
632
|
-
devcontainer_config[:permissions]
|
|
643
|
+
permissions = devcontainer_config[:permissions]
|
|
644
|
+
return {} unless permissions.is_a?(Hash)
|
|
645
|
+
|
|
646
|
+
permissions.transform_keys { |key| key.to_sym }
|
|
633
647
|
end
|
|
634
648
|
|
|
635
649
|
# Check if dangerous filesystem operations are allowed in devcontainer
|
|
@@ -639,7 +653,15 @@ module Aidp
|
|
|
639
653
|
|
|
640
654
|
# Get list of providers that should skip permission checks in devcontainer
|
|
641
655
|
def devcontainer_skip_permission_checks
|
|
642
|
-
|
|
656
|
+
permissions = devcontainer_config[:permissions]
|
|
657
|
+
list = nil
|
|
658
|
+
|
|
659
|
+
if permissions.is_a?(Hash)
|
|
660
|
+
list = permissions[:skip_permission_checks] || permissions["skip_permission_checks"]
|
|
661
|
+
end
|
|
662
|
+
|
|
663
|
+
list = default_skip_permission_checks if list.nil?
|
|
664
|
+
Array(list).map(&:to_s)
|
|
643
665
|
end
|
|
644
666
|
|
|
645
667
|
# Check if a specific provider should skip permission checks in devcontainer
|
|
@@ -843,7 +865,7 @@ module Aidp
|
|
|
843
865
|
max_backoff_seconds: 1800,
|
|
844
866
|
next: {
|
|
845
867
|
success: :agentic,
|
|
846
|
-
failure: :
|
|
868
|
+
failure: :diagnose_failures,
|
|
847
869
|
else: :decide_whats_next
|
|
848
870
|
}
|
|
849
871
|
},
|
|
@@ -856,7 +878,7 @@ module Aidp
|
|
|
856
878
|
max_backoff_seconds: 1800,
|
|
857
879
|
next: {
|
|
858
880
|
success: :agentic,
|
|
859
|
-
failure: :
|
|
881
|
+
failure: :diagnose_failures,
|
|
860
882
|
else: :decide_whats_next
|
|
861
883
|
}
|
|
862
884
|
},
|
|
@@ -1055,7 +1077,7 @@ module Aidp
|
|
|
1055
1077
|
force_detection: nil,
|
|
1056
1078
|
permissions: {
|
|
1057
1079
|
dangerous_filesystem_ops: false,
|
|
1058
|
-
skip_permission_checks: []
|
|
1080
|
+
skip_permission_checks: ["claude"]
|
|
1059
1081
|
},
|
|
1060
1082
|
settings: {
|
|
1061
1083
|
timeout_multiplier: 1.0,
|
|
@@ -1065,6 +1087,47 @@ module Aidp
|
|
|
1065
1087
|
}
|
|
1066
1088
|
end
|
|
1067
1089
|
|
|
1090
|
+
def default_skip_permission_checks
|
|
1091
|
+
Array(default_devcontainer_config.dig(:permissions, :skip_permission_checks)).map(&:to_s)
|
|
1092
|
+
end
|
|
1093
|
+
|
|
1094
|
+
def deep_symbolize_keys(value)
|
|
1095
|
+
case value
|
|
1096
|
+
when Hash
|
|
1097
|
+
value.each_with_object({}) do |(key, val), memo|
|
|
1098
|
+
memo[key.to_sym] = deep_symbolize_keys(val)
|
|
1099
|
+
end
|
|
1100
|
+
when Array
|
|
1101
|
+
value.map { |item| deep_symbolize_keys(item) }
|
|
1102
|
+
else
|
|
1103
|
+
value
|
|
1104
|
+
end
|
|
1105
|
+
end
|
|
1106
|
+
|
|
1107
|
+
def deep_merge_hashes(base, overrides)
|
|
1108
|
+
overrides.each do |key, value|
|
|
1109
|
+
base[key] = if base[key].is_a?(Hash) && value.is_a?(Hash)
|
|
1110
|
+
deep_merge_hashes(base[key], value)
|
|
1111
|
+
else
|
|
1112
|
+
value
|
|
1113
|
+
end
|
|
1114
|
+
end
|
|
1115
|
+
base
|
|
1116
|
+
end
|
|
1117
|
+
|
|
1118
|
+
def deep_dup(value)
|
|
1119
|
+
case value
|
|
1120
|
+
when Hash
|
|
1121
|
+
value.each_with_object({}) do |(key, val), memo|
|
|
1122
|
+
memo[key] = deep_dup(val)
|
|
1123
|
+
end
|
|
1124
|
+
when Array
|
|
1125
|
+
value.map { |item| deep_dup(item) }
|
|
1126
|
+
else
|
|
1127
|
+
value
|
|
1128
|
+
end
|
|
1129
|
+
end
|
|
1130
|
+
|
|
1068
1131
|
# Custom error class for configuration issues
|
|
1069
1132
|
class ConfigurationError < StandardError; end
|
|
1070
1133
|
end
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
require "net/http"
|
|
4
4
|
require_relative "../debug_mixin"
|
|
5
5
|
require_relative "../concurrency"
|
|
6
|
+
require_relative "../providers/error_taxonomy"
|
|
6
7
|
|
|
7
8
|
module Aidp
|
|
8
9
|
module Harness
|
|
@@ -252,9 +253,10 @@ module Aidp
|
|
|
252
253
|
# Check if we should retry based on error type and strategy
|
|
253
254
|
def should_retry?(error_info, strategy)
|
|
254
255
|
return false unless strategy[:enabled]
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
256
|
+
|
|
257
|
+
# Use ErrorTaxonomy to determine if error is retryable
|
|
258
|
+
error_type = error_info[:error_type]
|
|
259
|
+
return false unless Aidp::Providers::ErrorTaxonomy.retryable?(error_type)
|
|
258
260
|
|
|
259
261
|
# Check circuit breaker
|
|
260
262
|
circuit_breaker_key = "#{error_info[:provider]}:#{error_info[:model]}"
|
|
@@ -337,7 +339,62 @@ module Aidp
|
|
|
337
339
|
|
|
338
340
|
def initialize_retry_strategies
|
|
339
341
|
@retry_strategies = {
|
|
340
|
-
#
|
|
342
|
+
# Transient errors - retry with exponential backoff
|
|
343
|
+
transient: {
|
|
344
|
+
name: "transient",
|
|
345
|
+
enabled: true,
|
|
346
|
+
max_retries: 3,
|
|
347
|
+
backoff_strategy: :exponential,
|
|
348
|
+
base_delay: 1.0,
|
|
349
|
+
max_delay: 30.0,
|
|
350
|
+
jitter: true
|
|
351
|
+
},
|
|
352
|
+
|
|
353
|
+
# Rate limited errors - no retry, immediate switch
|
|
354
|
+
rate_limited: {
|
|
355
|
+
name: "rate_limited",
|
|
356
|
+
enabled: false,
|
|
357
|
+
max_retries: 0,
|
|
358
|
+
backoff_strategy: :none,
|
|
359
|
+
base_delay: 0.0,
|
|
360
|
+
max_delay: 0.0,
|
|
361
|
+
jitter: false
|
|
362
|
+
},
|
|
363
|
+
|
|
364
|
+
# Authentication expired - no retry, switch provider
|
|
365
|
+
auth_expired: {
|
|
366
|
+
name: "auth_expired",
|
|
367
|
+
enabled: false,
|
|
368
|
+
max_retries: 0,
|
|
369
|
+
backoff_strategy: :none,
|
|
370
|
+
base_delay: 0.0,
|
|
371
|
+
max_delay: 0.0,
|
|
372
|
+
jitter: false
|
|
373
|
+
},
|
|
374
|
+
|
|
375
|
+
# Quota exceeded - no retry, switch provider
|
|
376
|
+
quota_exceeded: {
|
|
377
|
+
name: "quota_exceeded",
|
|
378
|
+
enabled: false,
|
|
379
|
+
max_retries: 0,
|
|
380
|
+
backoff_strategy: :none,
|
|
381
|
+
base_delay: 0.0,
|
|
382
|
+
max_delay: 0.0,
|
|
383
|
+
jitter: false
|
|
384
|
+
},
|
|
385
|
+
|
|
386
|
+
# Permanent errors - no retry, escalate
|
|
387
|
+
permanent: {
|
|
388
|
+
name: "permanent",
|
|
389
|
+
enabled: false,
|
|
390
|
+
max_retries: 0,
|
|
391
|
+
backoff_strategy: :none,
|
|
392
|
+
base_delay: 0.0,
|
|
393
|
+
max_delay: 0.0,
|
|
394
|
+
jitter: false
|
|
395
|
+
},
|
|
396
|
+
|
|
397
|
+
# Legacy aliases for backward compatibility
|
|
341
398
|
network_error: {
|
|
342
399
|
name: "network_error",
|
|
343
400
|
enabled: true,
|
|
@@ -347,8 +404,6 @@ module Aidp
|
|
|
347
404
|
max_delay: 30.0,
|
|
348
405
|
jitter: true
|
|
349
406
|
},
|
|
350
|
-
|
|
351
|
-
# Server errors - retry with linear backoff
|
|
352
407
|
server_error: {
|
|
353
408
|
name: "server_error",
|
|
354
409
|
enabled: true,
|
|
@@ -358,8 +413,6 @@ module Aidp
|
|
|
358
413
|
max_delay: 10.0,
|
|
359
414
|
jitter: true
|
|
360
415
|
},
|
|
361
|
-
|
|
362
|
-
# Timeout errors - retry with exponential backoff
|
|
363
416
|
timeout: {
|
|
364
417
|
name: "timeout",
|
|
365
418
|
enabled: true,
|
|
@@ -369,8 +422,6 @@ module Aidp
|
|
|
369
422
|
max_delay: 15.0,
|
|
370
423
|
jitter: true
|
|
371
424
|
},
|
|
372
|
-
|
|
373
|
-
# Rate limit errors - no retry, immediate switch
|
|
374
425
|
rate_limit: {
|
|
375
426
|
name: "rate_limit",
|
|
376
427
|
enabled: false,
|
|
@@ -380,8 +431,6 @@ module Aidp
|
|
|
380
431
|
max_delay: 0.0,
|
|
381
432
|
jitter: false
|
|
382
433
|
},
|
|
383
|
-
|
|
384
|
-
# Authentication errors - no retry, escalate
|
|
385
434
|
authentication: {
|
|
386
435
|
name: "authentication",
|
|
387
436
|
enabled: false,
|
|
@@ -391,8 +440,6 @@ module Aidp
|
|
|
391
440
|
max_delay: 0.0,
|
|
392
441
|
jitter: false
|
|
393
442
|
},
|
|
394
|
-
|
|
395
|
-
# Permission denied - no retry, escalate
|
|
396
443
|
permission_denied: {
|
|
397
444
|
name: "permission_denied",
|
|
398
445
|
enabled: false,
|
|
@@ -586,41 +633,36 @@ module Aidp
|
|
|
586
633
|
private
|
|
587
634
|
|
|
588
635
|
def classify_error_type(error)
|
|
589
|
-
return :
|
|
636
|
+
return :transient if error.nil?
|
|
637
|
+
|
|
638
|
+
# Use standardized error taxonomy for classification
|
|
639
|
+
message = error.message.to_s
|
|
590
640
|
|
|
641
|
+
# First, use ErrorTaxonomy to classify by message
|
|
642
|
+
category = Aidp::Providers::ErrorTaxonomy.classify_message(message)
|
|
643
|
+
|
|
644
|
+
# Override with more specific classification based on error type
|
|
591
645
|
case error
|
|
592
646
|
when Timeout::Error
|
|
593
|
-
:
|
|
647
|
+
:transient
|
|
594
648
|
when Net::HTTPError
|
|
595
649
|
case error.response.code.to_i
|
|
596
650
|
when 429
|
|
597
|
-
:
|
|
651
|
+
:rate_limited
|
|
598
652
|
when 401, 403
|
|
599
|
-
:
|
|
653
|
+
:auth_expired
|
|
600
654
|
when 500..599
|
|
601
|
-
:
|
|
655
|
+
:transient
|
|
656
|
+
when 400
|
|
657
|
+
:permanent
|
|
602
658
|
else
|
|
603
|
-
:
|
|
659
|
+
:transient
|
|
604
660
|
end
|
|
605
661
|
when SocketError, Errno::ECONNREFUSED, Errno::EHOSTUNREACH
|
|
606
|
-
:
|
|
607
|
-
when StandardError
|
|
608
|
-
# Check error message for common patterns
|
|
609
|
-
message = error.message.downcase
|
|
610
|
-
|
|
611
|
-
if message.include?("rate limit") || message.include?("quota")
|
|
612
|
-
:rate_limit
|
|
613
|
-
elsif message.include?("timeout")
|
|
614
|
-
:timeout
|
|
615
|
-
elsif message.include?("auth") || message.include?("permission")
|
|
616
|
-
:authentication
|
|
617
|
-
elsif message.include?("server") || message.include?("internal")
|
|
618
|
-
:server_error
|
|
619
|
-
else
|
|
620
|
-
:default
|
|
621
|
-
end
|
|
662
|
+
:transient
|
|
622
663
|
else
|
|
623
|
-
|
|
664
|
+
# Use message-based classification from ErrorTaxonomy
|
|
665
|
+
category
|
|
624
666
|
end
|
|
625
667
|
end
|
|
626
668
|
end
|
|
@@ -629,22 +671,41 @@ module Aidp
|
|
|
629
671
|
def create_recovery_plan(error_info, _context = {})
|
|
630
672
|
error_type = error_info[:error_type]
|
|
631
673
|
|
|
674
|
+
# Use ErrorTaxonomy to determine recovery strategy
|
|
632
675
|
case error_type
|
|
633
|
-
when :
|
|
676
|
+
when :rate_limited
|
|
634
677
|
{
|
|
635
678
|
action: :switch_provider,
|
|
636
679
|
reason: "Rate limit reached, switching provider",
|
|
637
680
|
priority: :high
|
|
638
681
|
}
|
|
639
|
-
when :
|
|
640
|
-
#
|
|
641
|
-
#
|
|
642
|
-
# while the user resolves credentials for the failing provider.
|
|
682
|
+
when :auth_expired
|
|
683
|
+
# Attempt a provider switch so workflows can continue with alternate providers
|
|
684
|
+
# while the user resolves credentials for the failing provider
|
|
643
685
|
{
|
|
644
686
|
action: :switch_provider,
|
|
645
|
-
reason: "Authentication
|
|
687
|
+
reason: "Authentication expired – switching provider to continue",
|
|
646
688
|
priority: :critical
|
|
647
689
|
}
|
|
690
|
+
when :quota_exceeded
|
|
691
|
+
{
|
|
692
|
+
action: :switch_provider,
|
|
693
|
+
reason: "Quota exceeded, switching provider",
|
|
694
|
+
priority: :high
|
|
695
|
+
}
|
|
696
|
+
when :transient
|
|
697
|
+
{
|
|
698
|
+
action: :switch_model,
|
|
699
|
+
reason: "Transient error, trying alternate model",
|
|
700
|
+
priority: :medium
|
|
701
|
+
}
|
|
702
|
+
when :permanent
|
|
703
|
+
{
|
|
704
|
+
action: :escalate,
|
|
705
|
+
reason: "Permanent error, requires manual intervention",
|
|
706
|
+
priority: :critical
|
|
707
|
+
}
|
|
708
|
+
# Legacy error type mappings for backward compatibility
|
|
648
709
|
when :timeout
|
|
649
710
|
{
|
|
650
711
|
action: :switch_model,
|
|
@@ -655,7 +716,7 @@ module Aidp
|
|
|
655
716
|
{
|
|
656
717
|
action: :switch_provider,
|
|
657
718
|
reason: "Network error, switching provider",
|
|
658
|
-
priority: :
|
|
719
|
+
priority: :medium
|
|
659
720
|
}
|
|
660
721
|
when :server_error
|
|
661
722
|
{
|
|
@@ -663,6 +724,18 @@ module Aidp
|
|
|
663
724
|
reason: "Server error, switching provider",
|
|
664
725
|
priority: :medium
|
|
665
726
|
}
|
|
727
|
+
when :authentication, :permission_denied
|
|
728
|
+
{
|
|
729
|
+
action: :switch_provider,
|
|
730
|
+
reason: "Authentication/permission issue – switching provider to continue",
|
|
731
|
+
priority: :critical
|
|
732
|
+
}
|
|
733
|
+
when :rate_limit
|
|
734
|
+
{
|
|
735
|
+
action: :switch_provider,
|
|
736
|
+
reason: "Rate limit reached, switching provider",
|
|
737
|
+
priority: :high
|
|
738
|
+
}
|
|
666
739
|
else
|
|
667
740
|
{
|
|
668
741
|
action: :switch_provider,
|