aidp 0.34.0 → 0.39.3
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 +14 -14
- data/lib/aidp/agent_harness_adapter.rb +309 -0
- data/lib/aidp/cli/issue_importer.rb +20 -6
- data/lib/aidp/cli/jobs_command.rb +1 -1
- data/lib/aidp/cli/models_command.rb +9 -16
- data/lib/aidp/cli/prompts_command.rb +253 -0
- data/lib/aidp/cli/security_command.rb +4 -10
- data/lib/aidp/cli/storage_command.rb +277 -0
- data/lib/aidp/cli/temporal_command.rb +460 -0
- data/lib/aidp/cli.rb +24 -2
- data/lib/aidp/config.rb +15 -0
- data/lib/aidp/config_paths.rb +12 -2
- data/lib/aidp/database/migrations.rb +114 -0
- data/lib/aidp/database/repositories/checkpoint_repository.rb +174 -0
- data/lib/aidp/database/repositories/deprecated_models_repository.rb +206 -0
- data/lib/aidp/database/repositories/evaluation_repository.rb +208 -0
- data/lib/aidp/database/repositories/harness_state_repository.rb +140 -0
- data/lib/aidp/database/repositories/job_repository.rb +278 -0
- data/lib/aidp/database/repositories/model_cache_repository.rb +194 -0
- data/lib/aidp/database/repositories/progress_repository.rb +210 -0
- data/lib/aidp/database/repositories/prompt_archive_repository.rb +195 -0
- data/lib/aidp/database/repositories/prompt_feedback_repository.rb +266 -0
- data/lib/aidp/database/repositories/provider_info_cache_repository.rb +208 -0
- data/lib/aidp/database/repositories/provider_metrics_repository.rb +245 -0
- data/lib/aidp/database/repositories/secrets_repository.rb +187 -0
- data/lib/aidp/database/repositories/task_repository.rb +231 -0
- data/lib/aidp/database/repositories/template_version_repository.rb +383 -0
- data/lib/aidp/database/repositories/watch_state_repository.rb +472 -0
- data/lib/aidp/database/repositories/workstream_repository.rb +310 -0
- data/lib/aidp/database/repositories/worktree_repository.rb +240 -0
- data/lib/aidp/database/repository.rb +166 -0
- data/lib/aidp/database/schema.rb +347 -0
- data/lib/aidp/database/storage_migrator.rb +693 -0
- data/lib/aidp/database.rb +139 -0
- data/lib/aidp/debug_mixin.rb +2 -2
- data/lib/aidp/execute/checkpoint.rb +26 -0
- data/lib/aidp/execute/checkpoint_display.rb +16 -0
- data/lib/aidp/execute/prompt_evaluator.rb +30 -57
- data/lib/aidp/execute/work_loop_runner.rb +294 -46
- data/lib/aidp/firewall/provider_requirements_collector.rb +7 -23
- data/lib/aidp/harness/agent_harness_provider_manager.rb +275 -0
- data/lib/aidp/harness/ai_decision_engine.rb +78 -296
- data/lib/aidp/harness/config_schema.rb +59 -0
- data/lib/aidp/harness/configuration.rb +229 -10
- data/lib/aidp/harness/deprecation_cache.rb +53 -11
- data/lib/aidp/harness/enhanced_runner.rb +1 -1
- data/lib/aidp/harness/error_handler.rb +11 -5
- data/lib/aidp/harness/provider_config.rb +36 -0
- data/lib/aidp/harness/provider_factory.rb +26 -24
- data/lib/aidp/harness/provider_info.rb +9 -5
- data/lib/aidp/harness/provider_manager.rb +2 -2
- data/lib/aidp/harness/provider_metrics.rb +103 -1
- data/lib/aidp/harness/rspec_command_optimizer.rb +166 -0
- data/lib/aidp/harness/ruby_llm_registry.rb +97 -8
- data/lib/aidp/harness/runner.rb +7 -2
- data/lib/aidp/harness/state_manager.rb +1 -1
- data/lib/aidp/harness/test_runner.rb +167 -11
- data/lib/aidp/harness/thinking_depth_manager.rb +564 -5
- data/lib/aidp/harness/ui.rb +11 -0
- data/lib/aidp/harness/usage_limit.rb +179 -0
- data/lib/aidp/harness/usage_limit_enforcer.rb +240 -0
- data/lib/aidp/harness/usage_limit_tracker.rb +308 -0
- data/lib/aidp/harness/usage_period.rb +234 -0
- data/lib/aidp/init/agent_instructions_generator.rb +317 -0
- data/lib/aidp/init/runner.rb +25 -4
- data/lib/aidp/interfaces/activity_monitor_interface.rb +234 -0
- data/lib/aidp/interfaces/binary_checker_interface.rb +232 -0
- data/lib/aidp/interfaces/command_executor_interface.rb +216 -0
- data/lib/aidp/interfaces/logger_interface.rb +150 -0
- data/lib/aidp/interfaces/ui_interface.rb +254 -0
- data/lib/aidp/logger.rb +25 -7
- data/lib/aidp/orchestration_adapter.rb +279 -0
- data/lib/aidp/pr_worktree_manager.rb +4 -15
- data/lib/aidp/prompts/feedback_collector.rb +198 -0
- data/lib/aidp/prompts/prompt_template_manager.rb +415 -0
- data/lib/aidp/prompts/template_evolver.rb +305 -0
- data/lib/aidp/prompts/template_version_manager.rb +325 -0
- data/lib/aidp/setup/in_memory_config_adapter.rb +257 -0
- data/lib/aidp/setup/in_memory_config_manager.rb +262 -0
- data/lib/aidp/setup/wizard.rb +616 -176
- data/lib/aidp/shell_executor.rb +58 -0
- data/lib/aidp/style_guide/selector.rb +56 -1
- data/lib/aidp/temporal/activities/analyze_issue_activity.rb +198 -0
- data/lib/aidp/temporal/activities/analyze_sub_task_activity.rb +163 -0
- data/lib/aidp/temporal/activities/base_activity.rb +78 -0
- data/lib/aidp/temporal/activities/create_plan_activity.rb +115 -0
- data/lib/aidp/temporal/activities/create_pr_activity.rb +193 -0
- data/lib/aidp/temporal/activities/create_prompt_activity.rb +118 -0
- data/lib/aidp/temporal/activities/diagnose_failure_activity.rb +228 -0
- data/lib/aidp/temporal/activities/prepare_next_iteration_activity.rb +147 -0
- data/lib/aidp/temporal/activities/record_checkpoint_activity.rb +81 -0
- data/lib/aidp/temporal/activities/run_agent_activity.rb +139 -0
- data/lib/aidp/temporal/activities/run_tests_activity.rb +114 -0
- data/lib/aidp/temporal/activities/run_work_loop_iteration_activity.rb +249 -0
- data/lib/aidp/temporal/configuration.rb +169 -0
- data/lib/aidp/temporal/connection.rb +93 -0
- data/lib/aidp/temporal/worker.rb +119 -0
- data/lib/aidp/temporal/workflow_client.rb +171 -0
- data/lib/aidp/temporal/workflows/base_workflow.rb +73 -0
- data/lib/aidp/temporal/workflows/issue_to_pr_workflow.rb +278 -0
- data/lib/aidp/temporal/workflows/sub_issue_workflow.rb +253 -0
- data/lib/aidp/temporal/workflows/work_loop_workflow.rb +321 -0
- data/lib/aidp/temporal.rb +120 -0
- data/lib/aidp/version.rb +1 -1
- data/lib/aidp/watch/base_processor.rb +35 -0
- data/lib/aidp/watch/build_processor.rb +132 -0
- data/lib/aidp/watch/github_state_extractor.rb +0 -8
- data/lib/aidp/watch/rebase_processor.rb +245 -0
- data/lib/aidp/watch/round_robin_scheduler.rb +150 -0
- data/lib/aidp/watch/runner.rb +366 -285
- data/lib/aidp/watch/state_store.rb +80 -0
- data/lib/aidp/watch/work_item.rb +96 -0
- data/lib/aidp/watch/worktree_cleanup_job.rb +226 -0
- data/lib/aidp/watch/worktree_reconciler.rb +643 -0
- data/lib/aidp/workflows/guided_agent.rb +1 -1
- data/lib/aidp/worktree.rb +62 -0
- data/templates/aidp.yml.example +70 -0
- metadata +118 -16
- data/lib/aidp/analyze/json_file_storage.rb +0 -292
- data/lib/aidp/execute/future_work_backlog.rb +0 -411
- data/lib/aidp/providers/adapter.rb +0 -239
- data/lib/aidp/providers/aider.rb +0 -264
- data/lib/aidp/providers/anthropic.rb +0 -614
- data/lib/aidp/providers/base.rb +0 -617
- data/lib/aidp/providers/capability_registry.rb +0 -204
- data/lib/aidp/providers/codex.rb +0 -267
- data/lib/aidp/providers/cursor.rb +0 -304
- data/lib/aidp/providers/error_taxonomy.rb +0 -195
- data/lib/aidp/providers/gemini.rb +0 -110
- data/lib/aidp/providers/github_copilot.rb +0 -227
- data/lib/aidp/providers/kilocode.rb +0 -157
- data/lib/aidp/providers/opencode.rb +0 -139
|
@@ -171,29 +171,50 @@ module Aidp
|
|
|
171
171
|
work_loop_config.fetch(:task_completion_required, true)
|
|
172
172
|
end
|
|
173
173
|
|
|
174
|
-
# Get
|
|
174
|
+
# Get all generic deterministic commands
|
|
175
|
+
# Returns array of command configs with normalized structure:
|
|
176
|
+
# [{name:, command:, required:, run_after:, category:, timeout_seconds:}]
|
|
177
|
+
def commands
|
|
178
|
+
@commands ||= load_commands
|
|
179
|
+
end
|
|
180
|
+
|
|
181
|
+
# Get commands filtered by execution phase
|
|
182
|
+
# @param phase [Symbol] :each_unit, :full_loop, or :on_completion
|
|
183
|
+
# @return [Array<Hash>] Commands configured for the specified phase
|
|
184
|
+
def commands_for_phase(phase)
|
|
185
|
+
commands.select { |cmd| cmd[:run_after] == phase.to_sym }
|
|
186
|
+
end
|
|
187
|
+
|
|
188
|
+
# Get commands filtered by category
|
|
189
|
+
# @param category [Symbol] :test, :lint, :formatter, :build, :documentation, etc.
|
|
190
|
+
# @return [Array<Hash>] Commands in the specified category
|
|
191
|
+
def commands_for_category(category)
|
|
192
|
+
commands.select { |cmd| cmd[:category] == category.to_sym }
|
|
193
|
+
end
|
|
194
|
+
|
|
195
|
+
# Get test commands (filtered from generic commands for backwards compatibility)
|
|
175
196
|
def test_commands
|
|
176
|
-
|
|
197
|
+
commands_for_category(:test).map { |cmd| {command: cmd[:command], required: cmd[:required]} }
|
|
177
198
|
end
|
|
178
199
|
|
|
179
|
-
# Get lint commands
|
|
200
|
+
# Get lint commands (filtered from generic commands for backwards compatibility)
|
|
180
201
|
def lint_commands
|
|
181
|
-
|
|
202
|
+
commands_for_category(:lint).map { |cmd| {command: cmd[:command], required: cmd[:required]} }
|
|
182
203
|
end
|
|
183
204
|
|
|
184
|
-
# Get formatter commands
|
|
205
|
+
# Get formatter commands (filtered from generic commands for backwards compatibility)
|
|
185
206
|
def formatter_commands
|
|
186
|
-
|
|
207
|
+
commands_for_category(:formatter).map { |cmd| {command: cmd[:command], required: cmd[:required]} }
|
|
187
208
|
end
|
|
188
209
|
|
|
189
|
-
# Get build commands
|
|
210
|
+
# Get build commands (filtered from generic commands for backwards compatibility)
|
|
190
211
|
def build_commands
|
|
191
|
-
|
|
212
|
+
commands_for_category(:build).map { |cmd| {command: cmd[:command], required: cmd[:required]} }
|
|
192
213
|
end
|
|
193
214
|
|
|
194
|
-
# Get documentation commands
|
|
215
|
+
# Get documentation commands (filtered from generic commands for backwards compatibility)
|
|
195
216
|
def documentation_commands
|
|
196
|
-
|
|
217
|
+
commands_for_category(:documentation).map { |cmd| {command: cmd[:command], required: cmd[:required]} }
|
|
197
218
|
end
|
|
198
219
|
|
|
199
220
|
# Get output filtering configuration
|
|
@@ -440,6 +461,34 @@ module Aidp
|
|
|
440
461
|
thinking_config[:max_tier] || default_thinking_config[:max_tier]
|
|
441
462
|
end
|
|
442
463
|
|
|
464
|
+
# Get maximum tier for autonomous contexts (watch mode, work loops)
|
|
465
|
+
# Restricts escalation to standard tier by default to prevent cost explosion
|
|
466
|
+
# Issue #375: Thinking tiers escalate too quickly in autonomous operations
|
|
467
|
+
def autonomous_max_tier
|
|
468
|
+
thinking_config[:autonomous_max_tier] || default_thinking_config[:autonomous_max_tier]
|
|
469
|
+
end
|
|
470
|
+
|
|
471
|
+
# Get escalation configuration for autonomous contexts
|
|
472
|
+
# Controls how models are tried before tier escalation
|
|
473
|
+
def autonomous_escalation_config
|
|
474
|
+
thinking_config[:autonomous_escalation] || default_autonomous_escalation_config
|
|
475
|
+
end
|
|
476
|
+
|
|
477
|
+
# Minimum attempts per model before trying next model in tier
|
|
478
|
+
def min_attempts_per_model
|
|
479
|
+
autonomous_escalation_config[:min_attempts_per_model] || 2
|
|
480
|
+
end
|
|
481
|
+
|
|
482
|
+
# Minimum total attempts across all models before tier escalation
|
|
483
|
+
def min_total_attempts_before_escalation
|
|
484
|
+
autonomous_escalation_config[:min_total_attempts] || 10
|
|
485
|
+
end
|
|
486
|
+
|
|
487
|
+
# Whether to retry failed models after exhausting untested models
|
|
488
|
+
def retry_failed_models?
|
|
489
|
+
autonomous_escalation_config[:retry_failed_models] != false
|
|
490
|
+
end
|
|
491
|
+
|
|
443
492
|
# Check if provider switching for tier is allowed
|
|
444
493
|
def allow_provider_switch_for_tier?
|
|
445
494
|
thinking_config[:allow_provider_switch] != false
|
|
@@ -857,6 +906,157 @@ module Aidp
|
|
|
857
906
|
end
|
|
858
907
|
end
|
|
859
908
|
|
|
909
|
+
# Load commands from configuration, supporting both new generic format
|
|
910
|
+
# and legacy category-specific format for backwards compatibility
|
|
911
|
+
def load_commands
|
|
912
|
+
commands = []
|
|
913
|
+
|
|
914
|
+
# Load from new generic commands array if present
|
|
915
|
+
raw_commands = work_loop_config[:commands] || []
|
|
916
|
+
commands.concat(normalize_generic_commands(raw_commands))
|
|
917
|
+
|
|
918
|
+
# Load from legacy category-specific arrays for backwards compatibility
|
|
919
|
+
commands.concat(load_legacy_commands)
|
|
920
|
+
|
|
921
|
+
Aidp.log_debug("configuration", "loaded_commands",
|
|
922
|
+
total: commands.size,
|
|
923
|
+
from_generic: raw_commands.size,
|
|
924
|
+
from_legacy: commands.size - raw_commands.size)
|
|
925
|
+
|
|
926
|
+
commands
|
|
927
|
+
end
|
|
928
|
+
|
|
929
|
+
# Normalize generic command format
|
|
930
|
+
# @param commands [Array] Raw command configurations
|
|
931
|
+
# @return [Array<Hash>] Normalized command configs
|
|
932
|
+
def normalize_generic_commands(commands)
|
|
933
|
+
return [] if commands.nil? || commands.empty?
|
|
934
|
+
|
|
935
|
+
commands.each_with_index.map do |cmd, index|
|
|
936
|
+
case cmd
|
|
937
|
+
when String
|
|
938
|
+
{
|
|
939
|
+
name: "command_#{index}",
|
|
940
|
+
command: cmd,
|
|
941
|
+
required: true,
|
|
942
|
+
run_after: :each_unit,
|
|
943
|
+
category: :custom,
|
|
944
|
+
timeout_seconds: nil
|
|
945
|
+
}
|
|
946
|
+
when Hash
|
|
947
|
+
normalize_generic_command_hash(cmd, index)
|
|
948
|
+
else
|
|
949
|
+
raise ConfigurationError, "Command must be a string or hash, got: #{cmd.class}"
|
|
950
|
+
end
|
|
951
|
+
end
|
|
952
|
+
end
|
|
953
|
+
|
|
954
|
+
# Normalize a single generic command hash
|
|
955
|
+
def normalize_generic_command_hash(cmd, index)
|
|
956
|
+
command_value = cmd[:command] || cmd["command"]
|
|
957
|
+
unless command_value.is_a?(String) && !command_value.empty?
|
|
958
|
+
raise ConfigurationError, "Command must be a non-empty string, got: #{command_value.inspect}"
|
|
959
|
+
end
|
|
960
|
+
|
|
961
|
+
name = cmd[:name] || cmd["name"] || "command_#{index}"
|
|
962
|
+
required = fetch_boolean(cmd, :required, true)
|
|
963
|
+
run_after_raw = cmd[:run_after] || cmd["run_after"] || "each_unit"
|
|
964
|
+
run_after = normalize_run_after(run_after_raw)
|
|
965
|
+
category_raw = cmd[:category] || cmd["category"] || "custom"
|
|
966
|
+
category = category_raw.to_s.to_sym
|
|
967
|
+
timeout = cmd[:timeout_seconds] || cmd["timeout_seconds"]
|
|
968
|
+
|
|
969
|
+
{
|
|
970
|
+
name: name.to_s,
|
|
971
|
+
command: command_value,
|
|
972
|
+
required: required,
|
|
973
|
+
run_after: run_after,
|
|
974
|
+
category: category,
|
|
975
|
+
timeout_seconds: timeout
|
|
976
|
+
}
|
|
977
|
+
end
|
|
978
|
+
|
|
979
|
+
# Normalize run_after value to symbol
|
|
980
|
+
def normalize_run_after(value)
|
|
981
|
+
case value.to_s.downcase
|
|
982
|
+
when "each_unit", "each"
|
|
983
|
+
:each_unit
|
|
984
|
+
when "full_loop", "loop"
|
|
985
|
+
:full_loop
|
|
986
|
+
when "on_completion", "completion"
|
|
987
|
+
:on_completion
|
|
988
|
+
else
|
|
989
|
+
:each_unit
|
|
990
|
+
end
|
|
991
|
+
end
|
|
992
|
+
|
|
993
|
+
# Fetch boolean value from hash with symbol/string key support
|
|
994
|
+
def fetch_boolean(hash, key, default)
|
|
995
|
+
if hash.key?(key)
|
|
996
|
+
hash[key]
|
|
997
|
+
elsif hash.key?(key.to_s)
|
|
998
|
+
hash[key.to_s]
|
|
999
|
+
else
|
|
1000
|
+
default
|
|
1001
|
+
end
|
|
1002
|
+
end
|
|
1003
|
+
|
|
1004
|
+
# Load commands from legacy category-specific configuration
|
|
1005
|
+
# Converts old test_commands, lint_commands, etc. to generic format
|
|
1006
|
+
def load_legacy_commands
|
|
1007
|
+
commands = []
|
|
1008
|
+
|
|
1009
|
+
# Map legacy config keys to categories and default run_after
|
|
1010
|
+
legacy_mappings = {
|
|
1011
|
+
test_commands: {category: :test, run_after: :each_unit},
|
|
1012
|
+
lint_commands: {category: :lint, run_after: :each_unit},
|
|
1013
|
+
formatter_commands: {category: :formatter, run_after: :on_completion},
|
|
1014
|
+
build_commands: {category: :build, run_after: :each_unit},
|
|
1015
|
+
documentation_commands: {category: :documentation, run_after: :on_completion}
|
|
1016
|
+
}
|
|
1017
|
+
|
|
1018
|
+
legacy_mappings.each do |config_key, defaults|
|
|
1019
|
+
raw = work_loop_config[config_key] || []
|
|
1020
|
+
next if raw.empty?
|
|
1021
|
+
|
|
1022
|
+
raw.each_with_index do |cmd, index|
|
|
1023
|
+
normalized = normalize_legacy_command(cmd, defaults[:category], index)
|
|
1024
|
+
normalized[:run_after] = defaults[:run_after]
|
|
1025
|
+
commands << normalized
|
|
1026
|
+
end
|
|
1027
|
+
end
|
|
1028
|
+
|
|
1029
|
+
commands
|
|
1030
|
+
end
|
|
1031
|
+
|
|
1032
|
+
# Normalize a legacy command to generic format
|
|
1033
|
+
def normalize_legacy_command(cmd, category, index)
|
|
1034
|
+
case cmd
|
|
1035
|
+
when String
|
|
1036
|
+
{
|
|
1037
|
+
name: "#{category}_#{index}",
|
|
1038
|
+
command: cmd,
|
|
1039
|
+
required: true,
|
|
1040
|
+
run_after: :each_unit,
|
|
1041
|
+
category: category,
|
|
1042
|
+
timeout_seconds: nil
|
|
1043
|
+
}
|
|
1044
|
+
when Hash
|
|
1045
|
+
command_value = cmd[:command] || cmd["command"]
|
|
1046
|
+
required = fetch_boolean(cmd, :required, true)
|
|
1047
|
+
{
|
|
1048
|
+
name: cmd[:name] || cmd["name"] || "#{category}_#{index}",
|
|
1049
|
+
command: command_value,
|
|
1050
|
+
required: required,
|
|
1051
|
+
run_after: :each_unit,
|
|
1052
|
+
category: category,
|
|
1053
|
+
timeout_seconds: cmd[:timeout_seconds] || cmd["timeout_seconds"]
|
|
1054
|
+
}
|
|
1055
|
+
else
|
|
1056
|
+
raise ConfigurationError, "Legacy command must be a string or hash, got: #{cmd.class}"
|
|
1057
|
+
end
|
|
1058
|
+
end
|
|
1059
|
+
|
|
860
1060
|
def validate_configuration!
|
|
861
1061
|
errors = Aidp::Config.validate_harness_config(@config, @project_dir)
|
|
862
1062
|
|
|
@@ -999,8 +1199,15 @@ module Aidp
|
|
|
999
1199
|
{
|
|
1000
1200
|
enabled: true,
|
|
1001
1201
|
max_iterations: 50,
|
|
1202
|
+
# New generic commands array - replaces category-specific arrays
|
|
1203
|
+
# Each command specifies when it runs (each_unit, full_loop, on_completion)
|
|
1204
|
+
commands: [],
|
|
1205
|
+
# Legacy category-specific arrays (for backwards compatibility)
|
|
1002
1206
|
test_commands: [],
|
|
1003
1207
|
lint_commands: [],
|
|
1208
|
+
formatter_commands: [],
|
|
1209
|
+
build_commands: [],
|
|
1210
|
+
documentation_commands: [],
|
|
1004
1211
|
guards: default_guards_config,
|
|
1005
1212
|
units: default_units_config
|
|
1006
1213
|
}
|
|
@@ -1114,15 +1321,27 @@ module Aidp
|
|
|
1114
1321
|
{
|
|
1115
1322
|
default_tier: "mini", # Use mini tier by default for cost optimization
|
|
1116
1323
|
max_tier: "pro", # Max tier rarely needed; pro is sufficient for most tasks
|
|
1324
|
+
autonomous_max_tier: "standard", # Issue #375: restrict autonomous operations to standard tier
|
|
1117
1325
|
allow_provider_switch: true,
|
|
1118
1326
|
auto_escalate: true,
|
|
1119
1327
|
escalation_threshold: 2,
|
|
1120
1328
|
escalation: default_escalation_config,
|
|
1329
|
+
autonomous_escalation: default_autonomous_escalation_config,
|
|
1121
1330
|
permissions_by_tier: {},
|
|
1122
1331
|
overrides: {}
|
|
1123
1332
|
}
|
|
1124
1333
|
end
|
|
1125
1334
|
|
|
1335
|
+
# Default autonomous escalation configuration (issue #375)
|
|
1336
|
+
# Controls tier escalation in watch mode and work loops
|
|
1337
|
+
def default_autonomous_escalation_config
|
|
1338
|
+
{
|
|
1339
|
+
min_attempts_per_model: 2, # Each model needs min 2 attempts before trying next
|
|
1340
|
+
min_total_attempts: 10, # Min 10 total attempts before tier escalation
|
|
1341
|
+
retry_failed_models: true # Retry failed models after exhausting untested
|
|
1342
|
+
}
|
|
1343
|
+
end
|
|
1344
|
+
|
|
1126
1345
|
def default_escalation_config
|
|
1127
1346
|
{
|
|
1128
1347
|
on_fail_attempts: 2,
|
|
@@ -8,15 +8,31 @@ module Aidp
|
|
|
8
8
|
# Manages a dynamic cache of deprecated models detected at runtime
|
|
9
9
|
# When deprecation errors are detected from provider APIs, models are
|
|
10
10
|
# added to this cache with metadata (replacement, detected date, etc.)
|
|
11
|
+
#
|
|
12
|
+
# This class is designed to be extractable to the agent-harness gem.
|
|
13
|
+
# It uses dependency injection for logging to decouple from AIDP.
|
|
11
14
|
class DeprecationCache
|
|
12
15
|
class CacheError < StandardError; end
|
|
13
16
|
|
|
17
|
+
# Component name for logging
|
|
18
|
+
COMPONENT = "deprecation_cache"
|
|
19
|
+
|
|
14
20
|
attr_reader :cache_path
|
|
15
21
|
|
|
16
|
-
|
|
22
|
+
# Initialize the deprecation cache.
|
|
23
|
+
#
|
|
24
|
+
# All parameters are optional for backward compatibility. Existing callers
|
|
25
|
+
# that don't pass a logger will use AIDP's built-in logging.
|
|
26
|
+
#
|
|
27
|
+
# @param cache_path [String, nil] custom cache file path (optional)
|
|
28
|
+
# @param root_dir [String, nil] root directory for default cache path (optional)
|
|
29
|
+
# @param logger [#log_info, #log_warn, #log_error, nil] logger instance (optional,
|
|
30
|
+
# falls back to Aidp.log_* methods if nil)
|
|
31
|
+
def initialize(cache_path: nil, root_dir: nil, logger: nil)
|
|
17
32
|
@root_dir = root_dir || safe_root_dir
|
|
18
33
|
@cache_path = cache_path || default_cache_path
|
|
19
34
|
@cache_data = nil
|
|
35
|
+
@logger = logger
|
|
20
36
|
ensure_cache_directory
|
|
21
37
|
end
|
|
22
38
|
|
|
@@ -36,8 +52,7 @@ module Aidp
|
|
|
36
52
|
}.compact
|
|
37
53
|
|
|
38
54
|
save_cache
|
|
39
|
-
|
|
40
|
-
provider: provider, model: model_id, replacement: replacement)
|
|
55
|
+
log_info("added_deprecated_model", provider: provider, model: model_id, replacement: replacement)
|
|
41
56
|
end
|
|
42
57
|
|
|
43
58
|
# Check if a model is deprecated
|
|
@@ -78,8 +93,7 @@ module Aidp
|
|
|
78
93
|
@cache_data["providers"].delete(provider) if @cache_data["providers"][provider].empty?
|
|
79
94
|
|
|
80
95
|
save_cache
|
|
81
|
-
|
|
82
|
-
provider: provider, model: model_id)
|
|
96
|
+
log_info("removed_deprecated_model", provider: provider, model: model_id)
|
|
83
97
|
end
|
|
84
98
|
|
|
85
99
|
# Get full deprecation info for a model
|
|
@@ -95,7 +109,7 @@ module Aidp
|
|
|
95
109
|
def clear!
|
|
96
110
|
@cache_data = default_cache_structure
|
|
97
111
|
save_cache
|
|
98
|
-
|
|
112
|
+
log_info("cleared_all_deprecations")
|
|
99
113
|
end
|
|
100
114
|
|
|
101
115
|
# Get cache statistics
|
|
@@ -145,16 +159,14 @@ module Aidp
|
|
|
145
159
|
@cache_data = default_cache_structure
|
|
146
160
|
end
|
|
147
161
|
rescue JSON::ParserError => e
|
|
148
|
-
|
|
149
|
-
error: e.message, path: @cache_path)
|
|
162
|
+
log_warn("invalid_cache_file_resetting", error: e.message, path: @cache_path)
|
|
150
163
|
@cache_data = default_cache_structure
|
|
151
164
|
end
|
|
152
165
|
|
|
153
166
|
def save_cache
|
|
154
167
|
File.write(@cache_path, JSON.pretty_generate(@cache_data))
|
|
155
168
|
rescue => e
|
|
156
|
-
|
|
157
|
-
error: e.message, path: @cache_path)
|
|
169
|
+
log_error("failed_to_save_cache", error: e.message, path: @cache_path)
|
|
158
170
|
raise CacheError, "Failed to save deprecation cache: #{e.message}"
|
|
159
171
|
end
|
|
160
172
|
|
|
@@ -168,10 +180,40 @@ module Aidp
|
|
|
168
180
|
|
|
169
181
|
def validate_cache_structure
|
|
170
182
|
unless @cache_data.is_a?(Hash) && @cache_data["providers"].is_a?(Hash)
|
|
171
|
-
|
|
183
|
+
log_warn("invalid_cache_structure_resetting")
|
|
172
184
|
@cache_data = default_cache_structure
|
|
173
185
|
end
|
|
174
186
|
end
|
|
187
|
+
|
|
188
|
+
# Logging helpers with fallback to Aidp.log_* if no logger injected.
|
|
189
|
+
#
|
|
190
|
+
# Design note: We intentionally treat nil and "not provided" the same way.
|
|
191
|
+
# Both mean "use AIDP's built-in logging". If you need to suppress logging
|
|
192
|
+
# entirely, pass a NullLogger that implements LoggerInterface but does nothing.
|
|
193
|
+
# This keeps the interface simple while supporting the gem extraction use case.
|
|
194
|
+
def log_info(message, **metadata)
|
|
195
|
+
if @logger
|
|
196
|
+
@logger.log_info(COMPONENT, message, **metadata)
|
|
197
|
+
else
|
|
198
|
+
Aidp.log_info(COMPONENT, message, **metadata)
|
|
199
|
+
end
|
|
200
|
+
end
|
|
201
|
+
|
|
202
|
+
def log_warn(message, **metadata)
|
|
203
|
+
if @logger
|
|
204
|
+
@logger.log_warn(COMPONENT, message, **metadata)
|
|
205
|
+
else
|
|
206
|
+
Aidp.log_warn(COMPONENT, message, **metadata)
|
|
207
|
+
end
|
|
208
|
+
end
|
|
209
|
+
|
|
210
|
+
def log_error(message, **metadata)
|
|
211
|
+
if @logger
|
|
212
|
+
@logger.log_error(COMPONENT, message, **metadata)
|
|
213
|
+
else
|
|
214
|
+
Aidp.log_error(COMPONENT, message, **metadata)
|
|
215
|
+
end
|
|
216
|
+
end
|
|
175
217
|
end
|
|
176
218
|
end
|
|
177
219
|
end
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require "net/http"
|
|
4
|
+
require "agent_harness"
|
|
4
5
|
require_relative "../debug_mixin"
|
|
5
6
|
require_relative "../concurrency"
|
|
6
|
-
require_relative "../providers/error_taxonomy"
|
|
7
7
|
require_relative "../errors"
|
|
8
8
|
|
|
9
9
|
module Aidp
|
|
@@ -123,7 +123,7 @@ module Aidp
|
|
|
123
123
|
error: error,
|
|
124
124
|
provider: current_provider,
|
|
125
125
|
model: current_model_safely,
|
|
126
|
-
error_type: @error_classifier.classify_error(error)
|
|
126
|
+
error_type: @error_classifier.classify_error(error)[:error_type]
|
|
127
127
|
}
|
|
128
128
|
|
|
129
129
|
strategy = retry_strategy(error_info[:error_type])
|
|
@@ -264,7 +264,7 @@ module Aidp
|
|
|
264
264
|
|
|
265
265
|
# Use ErrorTaxonomy to determine if error is retryable
|
|
266
266
|
error_type = error_info[:error_type]
|
|
267
|
-
return false unless
|
|
267
|
+
return false unless AgentHarness::ErrorTaxonomy.retryable?(error_type)
|
|
268
268
|
|
|
269
269
|
# Check circuit breaker
|
|
270
270
|
circuit_breaker_key = "#{error_info[:provider]}:#{error_info[:model]}"
|
|
@@ -622,7 +622,7 @@ module Aidp
|
|
|
622
622
|
message = error.message.to_s
|
|
623
623
|
|
|
624
624
|
# First, use ErrorTaxonomy to classify by message
|
|
625
|
-
category =
|
|
625
|
+
category = AgentHarness::ErrorTaxonomy.classify_message(message)
|
|
626
626
|
|
|
627
627
|
# Override with more specific classification based on error type
|
|
628
628
|
case error
|
|
@@ -645,7 +645,13 @@ module Aidp
|
|
|
645
645
|
:transient
|
|
646
646
|
else
|
|
647
647
|
# Use message-based classification from ErrorTaxonomy
|
|
648
|
-
|
|
648
|
+
# Map :unknown and :timeout to :transient for backwards compatibility
|
|
649
|
+
case category
|
|
650
|
+
when :unknown, :timeout
|
|
651
|
+
:transient
|
|
652
|
+
else
|
|
653
|
+
category
|
|
654
|
+
end
|
|
649
655
|
end
|
|
650
656
|
end
|
|
651
657
|
end
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
require_relative "config_manager"
|
|
4
4
|
require_relative "provider_type_checker"
|
|
5
|
+
require_relative "usage_limit"
|
|
5
6
|
|
|
6
7
|
module Aidp
|
|
7
8
|
module Harness
|
|
@@ -272,6 +273,41 @@ module Aidp
|
|
|
272
273
|
}
|
|
273
274
|
end
|
|
274
275
|
|
|
276
|
+
# Get usage limits configuration
|
|
277
|
+
#
|
|
278
|
+
# @param options [Hash] Options for config loading
|
|
279
|
+
# @return [Hash] Raw usage limits configuration hash
|
|
280
|
+
def usage_limits_config(options = {})
|
|
281
|
+
config = config(options)
|
|
282
|
+
limits = config[:usage_limits] || config["usage_limits"] || {}
|
|
283
|
+
|
|
284
|
+
{
|
|
285
|
+
enabled: limits[:enabled] == true,
|
|
286
|
+
period: limits[:period] || limits["period"] || "monthly",
|
|
287
|
+
reset_day: limits[:reset_day] || limits["reset_day"] || 1,
|
|
288
|
+
max_tokens: limits[:max_tokens] || limits["max_tokens"],
|
|
289
|
+
max_cost: limits[:max_cost] || limits["max_cost"],
|
|
290
|
+
tier_limits: limits[:tier_limits] || limits["tier_limits"] || {}
|
|
291
|
+
}
|
|
292
|
+
end
|
|
293
|
+
|
|
294
|
+
# Get usage limits as a UsageLimit value object
|
|
295
|
+
#
|
|
296
|
+
# @param options [Hash] Options for config loading
|
|
297
|
+
# @return [UsageLimit] UsageLimit instance
|
|
298
|
+
def usage_limit(options = {})
|
|
299
|
+
config = usage_limits_config(options)
|
|
300
|
+
UsageLimit.from_config(config)
|
|
301
|
+
end
|
|
302
|
+
|
|
303
|
+
# Check if usage limits are enabled for this provider
|
|
304
|
+
#
|
|
305
|
+
# @param options [Hash] Options for config loading
|
|
306
|
+
# @return [Boolean] True if limits are enabled
|
|
307
|
+
def usage_limits_enabled?(options = {})
|
|
308
|
+
usage_limits_config(options)[:enabled]
|
|
309
|
+
end
|
|
310
|
+
|
|
275
311
|
# Get provider-specific harness configuration
|
|
276
312
|
def harness_config(options = {})
|
|
277
313
|
config = config(options)
|
|
@@ -1,34 +1,29 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require "agent_harness"
|
|
3
4
|
require_relative "provider_config"
|
|
4
|
-
require_relative "../providers/base"
|
|
5
|
-
require_relative "../providers/cursor"
|
|
6
|
-
require_relative "../providers/anthropic"
|
|
7
|
-
require_relative "../providers/gemini"
|
|
8
|
-
require_relative "../providers/opencode"
|
|
9
|
-
require_relative "../providers/kilocode"
|
|
10
|
-
require_relative "../providers/github_copilot"
|
|
11
|
-
require_relative "../providers/codex"
|
|
12
|
-
require_relative "../providers/aider"
|
|
13
5
|
|
|
14
6
|
module Aidp
|
|
15
7
|
module Harness
|
|
16
8
|
# Factory for creating configured provider instances
|
|
9
|
+
#
|
|
10
|
+
# Uses AgentHarness providers for all provider instantiation.
|
|
17
11
|
class ProviderFactory
|
|
18
12
|
# Expose for testability
|
|
19
13
|
attr_reader :provider_instances, :provider_configs
|
|
20
14
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
15
|
+
# Provider class lookup - delegates to AgentHarness registry
|
|
16
|
+
def self.provider_classes
|
|
17
|
+
registry = AgentHarness::Providers::Registry.instance
|
|
18
|
+
registry.all.each_with_object({}) do |name, hash|
|
|
19
|
+
hash[name.to_s] = registry.get(name)
|
|
20
|
+
# Add aliases
|
|
21
|
+
hash["anthropic"] = registry.get(:claude) if name == :claude
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
# For backwards compatibility - expose PROVIDER_CLASSES as a method
|
|
26
|
+
PROVIDER_CLASSES = nil # Deprecated - use provider_classes method
|
|
32
27
|
|
|
33
28
|
def initialize(config_manager = nil)
|
|
34
29
|
@config_manager = config_manager || ConfigManager.new
|
|
@@ -122,19 +117,26 @@ module Aidp
|
|
|
122
117
|
@provider_configs[provider_name.to_s] ||= ProviderConfig.new(provider_name, @config_manager)
|
|
123
118
|
end
|
|
124
119
|
|
|
125
|
-
# Get provider class
|
|
120
|
+
# Get provider class from AgentHarness registry
|
|
126
121
|
def provider_class(provider_name)
|
|
127
|
-
|
|
122
|
+
name = provider_name.to_s
|
|
123
|
+
# Handle anthropic -> claude alias
|
|
124
|
+
name = "claude" if name == "anthropic"
|
|
125
|
+
AgentHarness::Providers::Registry.instance.get(name.to_sym)
|
|
126
|
+
rescue AgentHarness::ConfigurationError
|
|
127
|
+
nil
|
|
128
128
|
end
|
|
129
129
|
|
|
130
130
|
# Check if provider is supported
|
|
131
131
|
def provider_supported?(provider_name)
|
|
132
|
-
|
|
132
|
+
name = provider_name.to_s
|
|
133
|
+
name = "claude" if name == "anthropic"
|
|
134
|
+
AgentHarness::Providers::Registry.instance.registered?(name.to_sym)
|
|
133
135
|
end
|
|
134
136
|
|
|
135
137
|
# Get supported provider names
|
|
136
138
|
def supported_providers
|
|
137
|
-
|
|
139
|
+
AgentHarness::Providers::Registry.instance.all.map(&:to_s)
|
|
138
140
|
end
|
|
139
141
|
|
|
140
142
|
# Get configured provider names
|
|
@@ -154,7 +154,7 @@ module Aidp
|
|
|
154
154
|
provider_instance.fetch_mcp_servers
|
|
155
155
|
rescue => e
|
|
156
156
|
log_rescue(e, component: "provider_info", action: "fetch_mcp_servers", fallback: [], provider: @provider_name)
|
|
157
|
-
warn "Failed to fetch MCP servers for #{@provider_name}: #{e.message}" if
|
|
157
|
+
warn "Failed to fetch MCP servers for #{@provider_name}: #{e.message}" if Aidp.debug_env_enabled?
|
|
158
158
|
[]
|
|
159
159
|
end
|
|
160
160
|
|
|
@@ -359,17 +359,21 @@ module Aidp
|
|
|
359
359
|
def provider_instance
|
|
360
360
|
return @provider_instance if @provider_instance
|
|
361
361
|
|
|
362
|
-
#
|
|
363
|
-
|
|
362
|
+
# Get provider class from AgentHarness registry
|
|
363
|
+
require "agent_harness"
|
|
364
364
|
|
|
365
|
-
|
|
365
|
+
name = @provider_name
|
|
366
|
+
name = "claude" if name == "anthropic"
|
|
367
|
+
provider_class = AgentHarness::Providers::Registry.instance.get(name.to_sym)
|
|
366
368
|
return nil unless provider_class
|
|
367
369
|
|
|
368
370
|
# Create provider instance
|
|
369
371
|
@provider_instance = provider_class.new
|
|
372
|
+
rescue AgentHarness::ConfigurationError
|
|
373
|
+
nil
|
|
370
374
|
rescue => e
|
|
371
375
|
log_rescue(e, component: "provider_info", action: "create_provider_instance", fallback: nil, provider: @provider_name)
|
|
372
|
-
warn "Failed to create provider instance for #{@provider_name}: #{e.message}" if
|
|
376
|
+
warn "Failed to create provider instance for #{@provider_name}: #{e.message}" if Aidp.debug_env_enabled?
|
|
373
377
|
nil
|
|
374
378
|
end
|
|
375
379
|
|
|
@@ -1118,8 +1118,8 @@ module Aidp
|
|
|
1118
1118
|
end
|
|
1119
1119
|
installed = !path.nil?
|
|
1120
1120
|
when "cursor"
|
|
1121
|
-
|
|
1122
|
-
installed =
|
|
1121
|
+
require "agent_harness"
|
|
1122
|
+
installed = AgentHarness::Providers::Registry.instance.get(:cursor).available?
|
|
1123
1123
|
end
|
|
1124
1124
|
rescue LoadError => e
|
|
1125
1125
|
log_rescue(e, component: "provider_manager", action: "check_provider_availability", fallback: false, provider: provider_name)
|