aidp 0.23.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/lib/aidp/cli.rb +3 -0
- 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 +90 -29
- 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/provider_registry.rb +107 -0
- data/lib/aidp/setup/wizard.rb +115 -31
- data/lib/aidp/version.rb +1 -1
- data/lib/aidp/watch/build_processor.rb +263 -23
- data/lib/aidp/watch/repository_client.rb +4 -4
- data/lib/aidp/watch/runner.rb +37 -5
- 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
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: e2ec07f1c36212b7ace8e467e098fc3bcd917dc0738e27125bbddcef9bbcc30e
|
|
4
|
+
data.tar.gz: edbc40f6c50b581729d185186eae3510e8c23885f12655c46868117af361f31a
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: f6472e33b88cf45f0c0abc5131dd608ba20d73480074552275d6d886222357c435023845fa8762b7f527268ba34fc2d3841d504309c8c068adbc38d6ac41f12b
|
|
7
|
+
data.tar.gz: 4283ba15ee02985603adbdba0be476adec35efc9fa6730a01beef1eac50d068647f3560aa03d75fcdb24ffc102e742bc1c3d9ff53fe95662a20241c711b54ae7
|
data/lib/aidp/cli.rb
CHANGED
|
@@ -1264,6 +1264,9 @@ module Aidp
|
|
|
1264
1264
|
end
|
|
1265
1265
|
end
|
|
1266
1266
|
|
|
1267
|
+
# Initialize logger for watch mode
|
|
1268
|
+
setup_logging(Dir.pwd)
|
|
1269
|
+
|
|
1267
1270
|
# Load watch safety configuration
|
|
1268
1271
|
config_manager = Aidp::Harness::ConfigManager.new(Dir.pwd)
|
|
1269
1272
|
config = config_manager.config || {}
|
|
@@ -77,7 +77,7 @@ module Aidp
|
|
|
77
77
|
display_guard_policy_status
|
|
78
78
|
display_pending_tasks
|
|
79
79
|
|
|
80
|
-
@unit_scheduler = WorkLoopUnitScheduler.new(units_config)
|
|
80
|
+
@unit_scheduler = WorkLoopUnitScheduler.new(units_config, project_dir: @project_dir)
|
|
81
81
|
base_context = context.dup
|
|
82
82
|
|
|
83
83
|
loop do
|
|
@@ -97,6 +97,8 @@ module Aidp
|
|
|
97
97
|
|
|
98
98
|
agentic_payload = if unit.name == :decide_whats_next
|
|
99
99
|
run_decider_agentic_unit(enriched_context)
|
|
100
|
+
elsif unit.name == :diagnose_failures
|
|
101
|
+
run_diagnose_agentic_unit(enriched_context)
|
|
100
102
|
else
|
|
101
103
|
run_primary_agentic_unit(step_spec, enriched_context)
|
|
102
104
|
end
|
|
@@ -148,7 +150,27 @@ module Aidp
|
|
|
148
150
|
transition_to(:ready) unless @current_state == :ready
|
|
149
151
|
|
|
150
152
|
transition_to(:apply_patch)
|
|
151
|
-
|
|
153
|
+
|
|
154
|
+
# Wrap agent call in exception handling for true fix-forward
|
|
155
|
+
begin
|
|
156
|
+
agent_result = apply_patch
|
|
157
|
+
rescue => e
|
|
158
|
+
# Convert exception to error result for fix-forward handling
|
|
159
|
+
Aidp.logger.error("work_loop", "Exception during agent call",
|
|
160
|
+
step: @step_name,
|
|
161
|
+
iteration: @iteration_count,
|
|
162
|
+
error: e.message,
|
|
163
|
+
error_class: e.class.name,
|
|
164
|
+
backtrace: e.backtrace&.first(5))
|
|
165
|
+
|
|
166
|
+
display_message(" ⚠️ Exception during agent call: #{e.class.name}: #{e.message}", type: :error)
|
|
167
|
+
|
|
168
|
+
# Append exception to PROMPT.md so agent can see and fix it
|
|
169
|
+
append_exception_to_prompt(e)
|
|
170
|
+
|
|
171
|
+
# Continue to next iteration with fix-forward pattern
|
|
172
|
+
next
|
|
173
|
+
end
|
|
152
174
|
|
|
153
175
|
# Process agent output for task filing signals
|
|
154
176
|
process_task_filing(agent_result)
|
|
@@ -223,6 +245,34 @@ module Aidp
|
|
|
223
245
|
)
|
|
224
246
|
end
|
|
225
247
|
|
|
248
|
+
def run_diagnose_agentic_unit(context)
|
|
249
|
+
Aidp.logger.info("work_loop", "Running diagnose_failures agentic unit", step: @step_name)
|
|
250
|
+
|
|
251
|
+
prompt = build_diagnose_prompt(context)
|
|
252
|
+
|
|
253
|
+
agent_result = @provider_manager.execute_with_provider(
|
|
254
|
+
@provider_manager.current_provider,
|
|
255
|
+
prompt,
|
|
256
|
+
{
|
|
257
|
+
step_name: @step_name,
|
|
258
|
+
iteration: @iteration_count,
|
|
259
|
+
project_dir: @project_dir,
|
|
260
|
+
mode: :diagnose_failures
|
|
261
|
+
}
|
|
262
|
+
)
|
|
263
|
+
|
|
264
|
+
requested = AgentSignalParser.extract_next_unit(agent_result[:output])
|
|
265
|
+
|
|
266
|
+
build_agentic_payload(
|
|
267
|
+
agent_result: agent_result,
|
|
268
|
+
response: agent_result,
|
|
269
|
+
summary: agent_result[:output],
|
|
270
|
+
completed: false,
|
|
271
|
+
terminate: false,
|
|
272
|
+
requested_next: requested
|
|
273
|
+
)
|
|
274
|
+
end
|
|
275
|
+
|
|
226
276
|
def units_config
|
|
227
277
|
if @config.respond_to?(:work_loop_units_config)
|
|
228
278
|
@config.work_loop_units_config
|
|
@@ -243,41 +293,94 @@ module Aidp
|
|
|
243
293
|
end
|
|
244
294
|
|
|
245
295
|
def build_decider_prompt(context)
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
sections << ""
|
|
254
|
-
sections << "## Recent Deterministic Outputs"
|
|
255
|
-
|
|
256
|
-
if outputs.empty?
|
|
257
|
-
sections << "- None recorded yet."
|
|
258
|
-
else
|
|
259
|
-
outputs.each do |entry|
|
|
260
|
-
sections << "- #{entry[:name]} (status: #{entry[:status]}, finished_at: #{entry[:finished_at]})"
|
|
261
|
-
sections << " Output: #{entry[:output_path] || "n/a"}"
|
|
262
|
-
end
|
|
263
|
-
end
|
|
296
|
+
template = load_work_loop_template("decide_whats_next.md", default_decider_template)
|
|
297
|
+
replacements = {
|
|
298
|
+
"{{DETERMINISTIC_OUTPUTS}}" => format_deterministic_outputs(context[:deterministic_outputs]),
|
|
299
|
+
"{{PREVIOUS_AGENT_SUMMARY}}" => format_previous_agent_summary(context[:previous_agent_summary])
|
|
300
|
+
}
|
|
301
|
+
replacements.reduce(template) { |body, (token, value)| body.gsub(token, value) }
|
|
302
|
+
end
|
|
264
303
|
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
304
|
+
def build_diagnose_prompt(context)
|
|
305
|
+
template = load_work_loop_template("diagnose_failures.md", default_diagnose_template)
|
|
306
|
+
replacements = {
|
|
307
|
+
"{{DETERMINISTIC_OUTPUTS}}" => format_deterministic_outputs(context[:deterministic_outputs]),
|
|
308
|
+
"{{PREVIOUS_AGENT_SUMMARY}}" => format_previous_agent_summary(context[:previous_agent_summary])
|
|
309
|
+
}
|
|
310
|
+
replacements.reduce(template) { |body, (token, value)| body.gsub(token, value) }
|
|
311
|
+
end
|
|
312
|
+
|
|
313
|
+
def load_work_loop_template(relative_path, fallback)
|
|
314
|
+
template_path = File.join(@project_dir, "templates", "work_loop", relative_path)
|
|
315
|
+
return File.read(template_path) if File.exist?(template_path)
|
|
270
316
|
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
317
|
+
fallback
|
|
318
|
+
rescue => e
|
|
319
|
+
Aidp.logger.warn("work_loop", "Unable to load #{relative_path}", error: e.message)
|
|
320
|
+
fallback
|
|
321
|
+
end
|
|
322
|
+
|
|
323
|
+
def default_decider_template
|
|
324
|
+
<<~TEMPLATE
|
|
325
|
+
# Decide Next Work Loop Unit
|
|
326
|
+
|
|
327
|
+
## Deterministic Outputs
|
|
328
|
+
|
|
329
|
+
{{DETERMINISTIC_OUTPUTS}}
|
|
330
|
+
|
|
331
|
+
## Previous Agent Summary
|
|
332
|
+
|
|
333
|
+
{{PREVIOUS_AGENT_SUMMARY}}
|
|
334
|
+
|
|
335
|
+
## Guidance
|
|
336
|
+
- Decide whether to run another deterministic unit or resume agentic editing.
|
|
337
|
+
- Announce your decision with `NEXT_UNIT: <unit_name>`.
|
|
338
|
+
- Valid values: names defined in configuration, `agentic`, or `wait_for_github`.
|
|
339
|
+
- Provide a concise rationale below.
|
|
279
340
|
|
|
280
|
-
|
|
341
|
+
## Rationale
|
|
342
|
+
TEMPLATE
|
|
343
|
+
end
|
|
344
|
+
|
|
345
|
+
def default_diagnose_template
|
|
346
|
+
<<~TEMPLATE
|
|
347
|
+
# Diagnose Failures
|
|
348
|
+
|
|
349
|
+
## Recent Deterministic Outputs
|
|
350
|
+
|
|
351
|
+
{{DETERMINISTIC_OUTPUTS}}
|
|
352
|
+
|
|
353
|
+
## Previous Agent Summary
|
|
354
|
+
|
|
355
|
+
{{PREVIOUS_AGENT_SUMMARY}}
|
|
356
|
+
|
|
357
|
+
## Instructions
|
|
358
|
+
- Identify the root cause of the failures above.
|
|
359
|
+
- Recommend the next concrete action (another deterministic unit, agentic editing, or waiting).
|
|
360
|
+
- Emit `NEXT_UNIT: <unit_name>` on its own line.
|
|
361
|
+
|
|
362
|
+
## Analysis
|
|
363
|
+
TEMPLATE
|
|
364
|
+
end
|
|
365
|
+
|
|
366
|
+
def format_deterministic_outputs(entries)
|
|
367
|
+
data = Array(entries)
|
|
368
|
+
return "- None recorded yet." if data.empty?
|
|
369
|
+
|
|
370
|
+
data.map do |entry|
|
|
371
|
+
name = entry[:name] || "unknown_unit"
|
|
372
|
+
status = entry[:status] || "unknown"
|
|
373
|
+
finished_at = entry[:finished_at]&.to_s || "unknown"
|
|
374
|
+
output = entry[:output_path] || "n/a"
|
|
375
|
+
"- #{name} (status: #{status}, finished_at: #{finished_at})\n Output: #{output}"
|
|
376
|
+
end.join("\n")
|
|
377
|
+
end
|
|
378
|
+
|
|
379
|
+
def format_previous_agent_summary(summary)
|
|
380
|
+
content = summary.to_s.strip
|
|
381
|
+
return "_No previous agent summary._" if content.empty?
|
|
382
|
+
|
|
383
|
+
content
|
|
281
384
|
end
|
|
282
385
|
|
|
283
386
|
# Transition to a new state in the fix-forward state machine
|
|
@@ -544,16 +647,50 @@ module Aidp
|
|
|
544
647
|
prompt_content = @prompt_manager.read
|
|
545
648
|
return {status: "error", message: "PROMPT.md not found"} unless prompt_content
|
|
546
649
|
|
|
547
|
-
#
|
|
548
|
-
@
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
650
|
+
# Prepend work loop instructions to every iteration
|
|
651
|
+
full_prompt = build_work_loop_header(@step_name, @iteration_count) + "\n\n" + prompt_content
|
|
652
|
+
|
|
653
|
+
# CRITICAL: Change to project directory before calling provider
|
|
654
|
+
# This ensures Claude CLI runs in the correct directory and can create files
|
|
655
|
+
Dir.chdir(@project_dir) do
|
|
656
|
+
# Send to provider via provider_manager
|
|
657
|
+
@provider_manager.execute_with_provider(
|
|
658
|
+
@provider_manager.current_provider,
|
|
659
|
+
full_prompt,
|
|
660
|
+
{
|
|
661
|
+
step_name: @step_name,
|
|
662
|
+
iteration: @iteration_count,
|
|
663
|
+
project_dir: @project_dir
|
|
664
|
+
}
|
|
665
|
+
)
|
|
666
|
+
end
|
|
667
|
+
end
|
|
668
|
+
|
|
669
|
+
def build_work_loop_header(step_name, iteration)
|
|
670
|
+
parts = []
|
|
671
|
+
parts << "# Work Loop: #{step_name} (Iteration #{iteration})"
|
|
672
|
+
parts << ""
|
|
673
|
+
parts << "## Instructions"
|
|
674
|
+
parts << "You are working in a work loop. Your responsibilities:"
|
|
675
|
+
parts << "1. Read the task description below to understand what needs to be done"
|
|
676
|
+
parts << "2. **Write/edit code files** to implement the required changes"
|
|
677
|
+
parts << "3. Run tests to verify your changes work correctly"
|
|
678
|
+
parts << "4. Update the task list in PROMPT.md as you complete items"
|
|
679
|
+
parts << "5. When ALL tasks are complete and tests pass, mark the step COMPLETE"
|
|
680
|
+
parts << ""
|
|
681
|
+
parts << "## Important Notes"
|
|
682
|
+
parts << "- You have full file system access - create and edit files as needed"
|
|
683
|
+
parts << "- The working directory is: #{@project_dir}"
|
|
684
|
+
parts << "- After you finish, tests and linters will run automatically"
|
|
685
|
+
parts << "- If tests/linters fail, you'll see the errors in the next iteration and can fix them"
|
|
686
|
+
parts << ""
|
|
687
|
+
parts << "## Completion Criteria"
|
|
688
|
+
parts << "Mark this step COMPLETE by adding this line to PROMPT.md:"
|
|
689
|
+
parts << "```"
|
|
690
|
+
parts << "STATUS: COMPLETE"
|
|
691
|
+
parts << "```"
|
|
692
|
+
parts << ""
|
|
693
|
+
parts.join("\n")
|
|
557
694
|
end
|
|
558
695
|
|
|
559
696
|
def prompt_marked_complete?
|
|
@@ -598,6 +735,9 @@ module Aidp
|
|
|
598
735
|
failures << ""
|
|
599
736
|
end
|
|
600
737
|
|
|
738
|
+
strategy = build_failure_strategy(test_results, lint_results)
|
|
739
|
+
failures.concat(strategy) unless strategy.empty?
|
|
740
|
+
|
|
601
741
|
failures << "**Fix-forward instructions**: Do not rollback changes. Build on what exists and fix the failures above."
|
|
602
742
|
failures << ""
|
|
603
743
|
|
|
@@ -608,7 +748,48 @@ module Aidp
|
|
|
608
748
|
updated_prompt = current_prompt + "\n\n---\n\n" + failures.join("\n")
|
|
609
749
|
@prompt_manager.write(updated_prompt, step_name: @step_name)
|
|
610
750
|
|
|
611
|
-
display_message(" [NEXT_PATCH] Added failure reports and diagnostic to PROMPT.md", type: :warning)
|
|
751
|
+
display_message(" [NEXT_PATCH] Added failure reports, strategy, and diagnostic to PROMPT.md", type: :warning)
|
|
752
|
+
end
|
|
753
|
+
|
|
754
|
+
# Append exception details to PROMPT.md for fix-forward handling
|
|
755
|
+
# This allows the agent to see and fix errors that occur during execution
|
|
756
|
+
def append_exception_to_prompt(exception)
|
|
757
|
+
error_report = []
|
|
758
|
+
|
|
759
|
+
error_report << "## Fix-Forward Exception in Iteration #{@iteration_count}"
|
|
760
|
+
error_report << ""
|
|
761
|
+
error_report << "**CRITICAL**: An exception occurred during this iteration. Please analyze and fix the underlying issue."
|
|
762
|
+
error_report << ""
|
|
763
|
+
error_report << "### Exception Details"
|
|
764
|
+
error_report << "- **Type**: `#{exception.class.name}`"
|
|
765
|
+
error_report << "- **Message**: #{exception.message}"
|
|
766
|
+
error_report << ""
|
|
767
|
+
|
|
768
|
+
if exception.backtrace && !exception.backtrace.empty?
|
|
769
|
+
error_report << "### Stack Trace (First 10 lines)"
|
|
770
|
+
error_report << "```"
|
|
771
|
+
exception.backtrace.first(10).each do |line|
|
|
772
|
+
error_report << line
|
|
773
|
+
end
|
|
774
|
+
error_report << "```"
|
|
775
|
+
error_report << ""
|
|
776
|
+
end
|
|
777
|
+
|
|
778
|
+
error_report << "### Required Action"
|
|
779
|
+
error_report << "1. Analyze the exception type and message"
|
|
780
|
+
error_report << "2. Review the stack trace to identify the source"
|
|
781
|
+
error_report << "3. Fix the underlying code issue"
|
|
782
|
+
error_report << "4. Ensure the fix doesn't break existing functionality"
|
|
783
|
+
error_report << ""
|
|
784
|
+
error_report << "**Fix-forward instructions**: Do not rollback changes. Identify the root cause and fix it in the next iteration."
|
|
785
|
+
error_report << ""
|
|
786
|
+
|
|
787
|
+
# Append to PROMPT.md
|
|
788
|
+
current_prompt = @prompt_manager.read
|
|
789
|
+
updated_prompt = current_prompt + "\n\n---\n\n" + error_report.join("\n")
|
|
790
|
+
@prompt_manager.write(updated_prompt, step_name: @step_name)
|
|
791
|
+
|
|
792
|
+
display_message(" [EXCEPTION] Added exception details to PROMPT.md for fix-forward", type: :error)
|
|
612
793
|
end
|
|
613
794
|
|
|
614
795
|
# Check if we should reinject the style guide at this iteration
|
|
@@ -651,6 +832,32 @@ module Aidp
|
|
|
651
832
|
reminder.join("\n")
|
|
652
833
|
end
|
|
653
834
|
|
|
835
|
+
def build_failure_strategy(test_results, lint_results)
|
|
836
|
+
return [] if test_results[:success] && lint_results[:success]
|
|
837
|
+
|
|
838
|
+
lines = ["### Recovery Strategy", ""]
|
|
839
|
+
|
|
840
|
+
unless test_results[:success]
|
|
841
|
+
commands = format_command_list(test_results[:failures])
|
|
842
|
+
lines << "- Re-run #{commands} locally to reproduce the failing specs listed above."
|
|
843
|
+
lines << "- Triage the exact failures before moving on to new work."
|
|
844
|
+
end
|
|
845
|
+
|
|
846
|
+
unless lint_results[:success]
|
|
847
|
+
commands = format_command_list(lint_results[:failures])
|
|
848
|
+
lines << "- Execute #{commands} and fix each reported offense."
|
|
849
|
+
end
|
|
850
|
+
|
|
851
|
+
lines << ""
|
|
852
|
+
lines
|
|
853
|
+
end
|
|
854
|
+
|
|
855
|
+
def format_command_list(failures)
|
|
856
|
+
commands = Array(failures).map { |failure| failure[:command] }.compact
|
|
857
|
+
commands = ["the configured command"] if commands.empty?
|
|
858
|
+
commands.map { |cmd| "`#{cmd}`" }.join(" or ")
|
|
859
|
+
end
|
|
860
|
+
|
|
654
861
|
# Load current step's template content
|
|
655
862
|
def load_current_template
|
|
656
863
|
return nil unless @step_name
|
|
@@ -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,
|