aidp 0.27.0 → 0.28.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 +89 -0
- data/lib/aidp/cli/models_command.rb +5 -6
- data/lib/aidp/cli.rb +10 -8
- data/lib/aidp/config.rb +54 -0
- data/lib/aidp/debug_mixin.rb +23 -1
- data/lib/aidp/execute/agent_signal_parser.rb +22 -0
- data/lib/aidp/execute/repl_macros.rb +2 -2
- data/lib/aidp/execute/steps.rb +94 -1
- data/lib/aidp/execute/work_loop_runner.rb +209 -17
- data/lib/aidp/execute/workflow_selector.rb +2 -25
- data/lib/aidp/firewall/provider_requirements_collector.rb +262 -0
- data/lib/aidp/harness/ai_decision_engine.rb +35 -2
- data/lib/aidp/harness/config_manager.rb +0 -5
- data/lib/aidp/harness/config_schema.rb +8 -0
- data/lib/aidp/harness/configuration.rb +27 -19
- data/lib/aidp/harness/enhanced_runner.rb +1 -4
- data/lib/aidp/harness/error_handler.rb +1 -72
- data/lib/aidp/harness/provider_factory.rb +11 -2
- data/lib/aidp/harness/state_manager.rb +0 -7
- data/lib/aidp/harness/thinking_depth_manager.rb +47 -68
- data/lib/aidp/harness/ui/enhanced_tui.rb +8 -18
- data/lib/aidp/harness/ui/enhanced_workflow_selector.rb +0 -18
- data/lib/aidp/harness/ui/progress_display.rb +6 -2
- data/lib/aidp/harness/user_interface.rb +0 -58
- data/lib/aidp/init/runner.rb +7 -2
- data/lib/aidp/planning/analyzers/feedback_analyzer.rb +365 -0
- data/lib/aidp/planning/builders/agile_plan_builder.rb +387 -0
- data/lib/aidp/planning/builders/project_plan_builder.rb +193 -0
- data/lib/aidp/planning/generators/gantt_generator.rb +190 -0
- data/lib/aidp/planning/generators/iteration_plan_generator.rb +392 -0
- data/lib/aidp/planning/generators/legacy_research_planner.rb +473 -0
- data/lib/aidp/planning/generators/marketing_report_generator.rb +348 -0
- data/lib/aidp/planning/generators/mvp_scope_generator.rb +310 -0
- data/lib/aidp/planning/generators/user_test_plan_generator.rb +373 -0
- data/lib/aidp/planning/generators/wbs_generator.rb +259 -0
- data/lib/aidp/planning/mappers/persona_mapper.rb +163 -0
- data/lib/aidp/planning/parsers/document_parser.rb +141 -0
- data/lib/aidp/planning/parsers/feedback_data_parser.rb +252 -0
- data/lib/aidp/provider_manager.rb +8 -32
- data/lib/aidp/providers/aider.rb +264 -0
- data/lib/aidp/providers/anthropic.rb +74 -2
- data/lib/aidp/providers/base.rb +25 -1
- data/lib/aidp/providers/codex.rb +26 -3
- data/lib/aidp/providers/cursor.rb +16 -0
- data/lib/aidp/providers/gemini.rb +13 -0
- data/lib/aidp/providers/github_copilot.rb +17 -0
- data/lib/aidp/providers/kilocode.rb +11 -0
- data/lib/aidp/providers/opencode.rb +11 -0
- data/lib/aidp/setup/wizard.rb +249 -39
- data/lib/aidp/version.rb +1 -1
- data/lib/aidp/watch/build_processor.rb +211 -30
- data/lib/aidp/watch/change_request_processor.rb +128 -14
- data/lib/aidp/watch/ci_fix_processor.rb +103 -37
- data/lib/aidp/watch/ci_log_extractor.rb +258 -0
- data/lib/aidp/watch/github_state_extractor.rb +177 -0
- data/lib/aidp/watch/implementation_verifier.rb +284 -0
- data/lib/aidp/watch/plan_generator.rb +7 -43
- data/lib/aidp/watch/plan_processor.rb +7 -6
- data/lib/aidp/watch/repository_client.rb +245 -17
- data/lib/aidp/watch/review_processor.rb +98 -17
- data/lib/aidp/watch/reviewers/base_reviewer.rb +1 -1
- data/lib/aidp/watch/runner.rb +181 -29
- data/lib/aidp/watch/state_store.rb +22 -1
- data/lib/aidp/workflows/definitions.rb +147 -0
- data/lib/aidp/workstream_cleanup.rb +245 -0
- data/lib/aidp/worktree.rb +19 -0
- data/templates/aidp.yml.example +57 -0
- data/templates/implementation/generate_tdd_specs.md +213 -0
- data/templates/implementation/iterative_implementation.md +122 -0
- data/templates/planning/agile/analyze_feedback.md +183 -0
- data/templates/planning/agile/generate_iteration_plan.md +179 -0
- data/templates/planning/agile/generate_legacy_research_plan.md +171 -0
- data/templates/planning/agile/generate_marketing_report.md +162 -0
- data/templates/planning/agile/generate_mvp_scope.md +127 -0
- data/templates/planning/agile/generate_user_test_plan.md +143 -0
- data/templates/planning/agile/ingest_feedback.md +174 -0
- data/templates/planning/assemble_project_plan.md +113 -0
- data/templates/planning/assign_personas.md +108 -0
- data/templates/planning/create_tasks.md +52 -6
- data/templates/planning/generate_gantt.md +86 -0
- data/templates/planning/generate_wbs.md +85 -0
- data/templates/planning/initialize_planning_mode.md +70 -0
- data/templates/skills/README.md +2 -2
- data/templates/skills/marketing_strategist/SKILL.md +279 -0
- data/templates/skills/product_manager/SKILL.md +177 -0
- data/templates/skills/ruby_aidp_planning/SKILL.md +497 -0
- data/templates/skills/ruby_rspec_tdd/SKILL.md +514 -0
- data/templates/skills/ux_researcher/SKILL.md +222 -0
- metadata +39 -1
|
@@ -0,0 +1,264 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "timeout"
|
|
4
|
+
require_relative "base"
|
|
5
|
+
require_relative "../util"
|
|
6
|
+
require_relative "../debug_mixin"
|
|
7
|
+
|
|
8
|
+
module Aidp
|
|
9
|
+
module Providers
|
|
10
|
+
class Aider < Base
|
|
11
|
+
include Aidp::DebugMixin
|
|
12
|
+
|
|
13
|
+
# Model name pattern for Aider (supports various models via OpenRouter)
|
|
14
|
+
# Aider can use any model, but we'll match common patterns
|
|
15
|
+
MODEL_PATTERN = /^(gpt-|claude-|gemini-|deepseek-|qwen-|o1-)/i
|
|
16
|
+
LONG_PROMPT_THRESHOLD = 8000
|
|
17
|
+
LONG_PROMPT_TIMEOUT = 900 # 15 minutes for large prompts
|
|
18
|
+
|
|
19
|
+
def self.available?
|
|
20
|
+
!!Aidp::Util.which("aider")
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
# Check if this provider supports a given model family
|
|
24
|
+
#
|
|
25
|
+
# @param family_name [String] The model family name
|
|
26
|
+
# @return [Boolean] True if it matches common model patterns
|
|
27
|
+
def self.supports_model_family?(family_name)
|
|
28
|
+
MODEL_PATTERN.match?(family_name)
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
# Discover available models from registry
|
|
32
|
+
#
|
|
33
|
+
# Note: Aider uses its own configuration for models
|
|
34
|
+
# Returns registry-based models that match common patterns
|
|
35
|
+
#
|
|
36
|
+
# @return [Array<Hash>] Array of discovered models
|
|
37
|
+
def self.discover_models
|
|
38
|
+
return [] unless available?
|
|
39
|
+
|
|
40
|
+
discover_models_from_registry(MODEL_PATTERN, "aider")
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
# Get firewall requirements for Aider provider
|
|
44
|
+
# Aider uses aider.chat for updates, openrouter.ai for API access,
|
|
45
|
+
# and pypi.org for version checking
|
|
46
|
+
def self.firewall_requirements
|
|
47
|
+
{
|
|
48
|
+
domains: [
|
|
49
|
+
"aider.chat",
|
|
50
|
+
"openrouter.ai",
|
|
51
|
+
"api.openrouter.ai",
|
|
52
|
+
"pypi.org"
|
|
53
|
+
],
|
|
54
|
+
ip_ranges: []
|
|
55
|
+
}
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def name
|
|
59
|
+
"aider"
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def display_name
|
|
63
|
+
"Aider"
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def available?
|
|
67
|
+
return false unless self.class.available?
|
|
68
|
+
|
|
69
|
+
# Additional check to ensure the CLI is properly configured
|
|
70
|
+
begin
|
|
71
|
+
result = Aidp::Util.execute_command("aider", ["--version"], timeout: 10)
|
|
72
|
+
result.exit_status == 0
|
|
73
|
+
rescue
|
|
74
|
+
false
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def send_message(prompt:, session: nil)
|
|
79
|
+
raise "aider CLI not available" unless self.class.available?
|
|
80
|
+
|
|
81
|
+
# Smart timeout calculation (store prompt length for adaptive logic)
|
|
82
|
+
@current_aider_prompt_length = prompt.length
|
|
83
|
+
timeout_seconds = calculate_timeout
|
|
84
|
+
|
|
85
|
+
debug_provider("aider", "Starting execution", {timeout: timeout_seconds})
|
|
86
|
+
debug_log("📝 Sending prompt to aider (length: #{prompt.length})", level: :info)
|
|
87
|
+
|
|
88
|
+
# Set up activity monitoring
|
|
89
|
+
setup_activity_monitoring("aider", method(:activity_callback))
|
|
90
|
+
record_activity("Starting aider execution")
|
|
91
|
+
|
|
92
|
+
# Create a spinner for activity display
|
|
93
|
+
spinner = TTY::Spinner.new("[:spinner] :title", format: :dots, hide_cursor: true)
|
|
94
|
+
spinner.auto_spin
|
|
95
|
+
|
|
96
|
+
activity_display_thread = Thread.new do
|
|
97
|
+
start_time = Time.now
|
|
98
|
+
loop do
|
|
99
|
+
sleep 0.5 # Update every 500ms to reduce spam
|
|
100
|
+
elapsed = Time.now - start_time
|
|
101
|
+
|
|
102
|
+
# Break if we've been running too long or state changed
|
|
103
|
+
break if elapsed > timeout_seconds || @activity_state == :completed || @activity_state == :failed
|
|
104
|
+
|
|
105
|
+
update_spinner_status(spinner, elapsed, "🤖 Aider")
|
|
106
|
+
end
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
begin
|
|
110
|
+
# Use non-interactive mode with --yes-always flag and --message
|
|
111
|
+
# --yes-always is equivalent to Claude's --dangerously-skip-permissions
|
|
112
|
+
args = ["--yes-always", "--message", prompt]
|
|
113
|
+
|
|
114
|
+
# Disable aider's auto-commits by default - let AIDP handle commits
|
|
115
|
+
# based on work_loop.version_control.behavior configuration
|
|
116
|
+
args += ["--no-auto-commits"]
|
|
117
|
+
|
|
118
|
+
# Add model if configured
|
|
119
|
+
if @model && !@model.empty?
|
|
120
|
+
args += ["--model", @model]
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
# Add session support if provided (aider supports chat history)
|
|
124
|
+
if session && !session.empty?
|
|
125
|
+
args += ["--restore-chat-history"]
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
# In devcontainer, aider should run in non-interactive mode
|
|
129
|
+
if in_devcontainer_or_codespace?
|
|
130
|
+
debug_log("🔓 Running aider in non-interactive mode with --yes-always (devcontainer)", level: :info)
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
# Use debug_execute_command for better debugging
|
|
134
|
+
result = debug_execute_command("aider", args: args, timeout: timeout_seconds)
|
|
135
|
+
|
|
136
|
+
# Log the results
|
|
137
|
+
debug_command("aider", args: args, input: prompt, output: result.out, error: result.err, exit_code: result.exit_status)
|
|
138
|
+
|
|
139
|
+
if result.exit_status == 0
|
|
140
|
+
spinner.success("✓")
|
|
141
|
+
mark_completed
|
|
142
|
+
result.out
|
|
143
|
+
else
|
|
144
|
+
spinner.error("✗")
|
|
145
|
+
mark_failed("aider failed with exit code #{result.exit_status}")
|
|
146
|
+
debug_error(StandardError.new("aider failed"), {exit_code: result.exit_status, stderr: result.err})
|
|
147
|
+
raise "aider failed with exit code #{result.exit_status}: #{result.err}"
|
|
148
|
+
end
|
|
149
|
+
rescue => e
|
|
150
|
+
spinner&.error("✗")
|
|
151
|
+
mark_failed("aider execution failed: #{e.message}")
|
|
152
|
+
debug_error(e, {provider: "aider", prompt_length: prompt.length})
|
|
153
|
+
raise
|
|
154
|
+
ensure
|
|
155
|
+
cleanup_activity_display(activity_display_thread, spinner)
|
|
156
|
+
@current_aider_prompt_length = nil
|
|
157
|
+
end
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
# Enhanced send method with additional options
|
|
161
|
+
def send_with_options(prompt:, session: nil, model: nil, auto_commits: false)
|
|
162
|
+
args = ["--yes-always", "--message", prompt]
|
|
163
|
+
|
|
164
|
+
# Disable auto-commits by default (let AIDP handle commits)
|
|
165
|
+
# unless explicitly enabled via auto_commits parameter
|
|
166
|
+
args += if auto_commits
|
|
167
|
+
["--auto-commits"]
|
|
168
|
+
else
|
|
169
|
+
["--no-auto-commits"]
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
# Add session support
|
|
173
|
+
if session && !session.empty?
|
|
174
|
+
args += ["--restore-chat-history"]
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
# Add model selection (from parameter or configured model)
|
|
178
|
+
model_to_use = model || @model
|
|
179
|
+
if model_to_use
|
|
180
|
+
args += ["--model", model_to_use]
|
|
181
|
+
end
|
|
182
|
+
|
|
183
|
+
# Use the enhanced version of send
|
|
184
|
+
send_with_custom_args(prompt: prompt, args: args)
|
|
185
|
+
end
|
|
186
|
+
|
|
187
|
+
# Override health check for Aider specific considerations
|
|
188
|
+
def harness_healthy?
|
|
189
|
+
return false unless super
|
|
190
|
+
|
|
191
|
+
# Additional health checks specific to Aider
|
|
192
|
+
# Check if we can access the CLI (basic connectivity test)
|
|
193
|
+
begin
|
|
194
|
+
result = Aidp::Util.execute_command("aider", ["--help"], timeout: 5)
|
|
195
|
+
result.exit_status == 0
|
|
196
|
+
rescue
|
|
197
|
+
false
|
|
198
|
+
end
|
|
199
|
+
end
|
|
200
|
+
|
|
201
|
+
private
|
|
202
|
+
|
|
203
|
+
# Internal helper for send_with_options - executes with custom arguments
|
|
204
|
+
def send_with_custom_args(prompt:, args:)
|
|
205
|
+
@current_aider_prompt_length = prompt.length
|
|
206
|
+
timeout_seconds = calculate_timeout
|
|
207
|
+
|
|
208
|
+
debug_provider("aider", "Starting execution", {timeout: timeout_seconds, args: args})
|
|
209
|
+
debug_log("📝 Sending prompt to aider with custom args", level: :info)
|
|
210
|
+
|
|
211
|
+
setup_activity_monitoring("aider", method(:activity_callback))
|
|
212
|
+
record_activity("Starting aider execution with custom args")
|
|
213
|
+
|
|
214
|
+
begin
|
|
215
|
+
result = debug_execute_command("aider", args: args, timeout: timeout_seconds)
|
|
216
|
+
debug_command("aider", args: args, output: result.out, error: result.err, exit_code: result.exit_status)
|
|
217
|
+
|
|
218
|
+
if result.exit_status == 0
|
|
219
|
+
mark_completed
|
|
220
|
+
result.out
|
|
221
|
+
else
|
|
222
|
+
mark_failed("aider failed with exit code #{result.exit_status}")
|
|
223
|
+
debug_error(StandardError.new("aider failed"), {exit_code: result.exit_status, stderr: result.err})
|
|
224
|
+
raise "aider failed with exit code #{result.exit_status}: #{result.err}"
|
|
225
|
+
end
|
|
226
|
+
rescue => e
|
|
227
|
+
mark_failed("aider execution failed: #{e.message}")
|
|
228
|
+
debug_error(e, {provider: "aider", prompt_length: prompt.length})
|
|
229
|
+
raise
|
|
230
|
+
ensure
|
|
231
|
+
@current_aider_prompt_length = nil
|
|
232
|
+
end
|
|
233
|
+
end
|
|
234
|
+
|
|
235
|
+
def activity_callback(state, message, provider)
|
|
236
|
+
# Handle activity state changes
|
|
237
|
+
case state
|
|
238
|
+
when :stuck
|
|
239
|
+
display_message("\n⚠️ Aider appears stuck: #{message}", type: :warning)
|
|
240
|
+
when :completed
|
|
241
|
+
display_message("\n✅ Aider completed: #{message}", type: :success)
|
|
242
|
+
when :failed
|
|
243
|
+
display_message("\n❌ Aider failed: #{message}", type: :error)
|
|
244
|
+
end
|
|
245
|
+
end
|
|
246
|
+
|
|
247
|
+
def calculate_timeout
|
|
248
|
+
env_override = ENV["AIDP_AIDER_TIMEOUT"]
|
|
249
|
+
return env_override.to_i if env_override&.match?(/^\d+$/)
|
|
250
|
+
|
|
251
|
+
base_timeout = super
|
|
252
|
+
|
|
253
|
+
prompt_length = @current_aider_prompt_length
|
|
254
|
+
return base_timeout unless prompt_length && prompt_length >= LONG_PROMPT_THRESHOLD
|
|
255
|
+
|
|
256
|
+
extended_timeout = [base_timeout, LONG_PROMPT_TIMEOUT].max
|
|
257
|
+
if extended_timeout > base_timeout
|
|
258
|
+
display_message("⏱️ Aider prompt length #{prompt_length} detected - extending timeout to #{extended_timeout} seconds", type: :info)
|
|
259
|
+
end
|
|
260
|
+
extended_timeout
|
|
261
|
+
end
|
|
262
|
+
end
|
|
263
|
+
end
|
|
264
|
+
end
|
|
@@ -64,6 +64,18 @@ module Aidp
|
|
|
64
64
|
end
|
|
65
65
|
end
|
|
66
66
|
|
|
67
|
+
# Get firewall requirements for Anthropic provider
|
|
68
|
+
def self.firewall_requirements
|
|
69
|
+
{
|
|
70
|
+
domains: [
|
|
71
|
+
"api.anthropic.com",
|
|
72
|
+
"claude.ai",
|
|
73
|
+
"console.anthropic.com"
|
|
74
|
+
],
|
|
75
|
+
ip_ranges: []
|
|
76
|
+
}
|
|
77
|
+
end
|
|
78
|
+
|
|
67
79
|
class << self
|
|
68
80
|
private
|
|
69
81
|
|
|
@@ -250,6 +262,11 @@ module Aidp
|
|
|
250
262
|
# Build command arguments
|
|
251
263
|
args = ["--print", "--output-format=text"]
|
|
252
264
|
|
|
265
|
+
# Add model if specified
|
|
266
|
+
if @model && !@model.empty?
|
|
267
|
+
args << "--model" << @model
|
|
268
|
+
end
|
|
269
|
+
|
|
253
270
|
# Check if we should skip permissions (devcontainer support)
|
|
254
271
|
if should_skip_permissions?
|
|
255
272
|
args << "--dangerously-skip-permissions"
|
|
@@ -265,14 +282,26 @@ module Aidp
|
|
|
265
282
|
if result.exit_status == 0
|
|
266
283
|
result.out
|
|
267
284
|
else
|
|
268
|
-
# Detect
|
|
285
|
+
# Detect issues in stdout/stderr (Claude sometimes prints to stdout)
|
|
269
286
|
combined = [result.out, result.err].compact.join("\n")
|
|
287
|
+
|
|
288
|
+
# Check for rate limit (Session limit reached)
|
|
289
|
+
if combined.match?(/session limit reached/i)
|
|
290
|
+
Aidp.log_debug("anthropic_provider", "rate_limit_detected",
|
|
291
|
+
exit_code: result.exit_status,
|
|
292
|
+
message: combined)
|
|
293
|
+
notify_rate_limit(combined)
|
|
294
|
+
error_message = "Rate limit reached for Claude CLI.\n#{combined}"
|
|
295
|
+
debug_error(StandardError.new(error_message), {exit_code: result.exit_status, stdout: result.out, stderr: result.err})
|
|
296
|
+
raise error_message
|
|
297
|
+
end
|
|
298
|
+
|
|
299
|
+
# Check for auth issues
|
|
270
300
|
if combined.downcase.include?("oauth token has expired") || combined.downcase.include?("authentication_error")
|
|
271
301
|
error_message = "Authentication error from Claude CLI: token expired or invalid.\n" \
|
|
272
302
|
"Run 'claude /login' or refresh credentials.\n" \
|
|
273
303
|
"Note: Model discovery requires valid authentication."
|
|
274
304
|
debug_error(StandardError.new(error_message), {exit_code: result.exit_status, stdout: result.out, stderr: result.err})
|
|
275
|
-
# Raise a recognizable error for classifier
|
|
276
305
|
raise error_message
|
|
277
306
|
end
|
|
278
307
|
|
|
@@ -287,6 +316,49 @@ module Aidp
|
|
|
287
316
|
|
|
288
317
|
private
|
|
289
318
|
|
|
319
|
+
# Notify harness about rate limit detection
|
|
320
|
+
def notify_rate_limit(message)
|
|
321
|
+
return unless @harness_context
|
|
322
|
+
|
|
323
|
+
# Extract reset time from message (e.g., "resets 4am")
|
|
324
|
+
reset_time = extract_reset_time_from_message(message)
|
|
325
|
+
|
|
326
|
+
# Notify provider manager if available
|
|
327
|
+
if @harness_context.respond_to?(:provider_manager)
|
|
328
|
+
provider_manager = @harness_context.provider_manager
|
|
329
|
+
if provider_manager.respond_to?(:mark_rate_limited)
|
|
330
|
+
provider_manager.mark_rate_limited("anthropic", reset_time)
|
|
331
|
+
Aidp.log_debug("anthropic_provider", "notified_provider_manager",
|
|
332
|
+
reset_time: reset_time)
|
|
333
|
+
end
|
|
334
|
+
end
|
|
335
|
+
rescue => e
|
|
336
|
+
Aidp.log_debug("anthropic_provider", "notify_rate_limit_failed",
|
|
337
|
+
error: e.message)
|
|
338
|
+
end
|
|
339
|
+
|
|
340
|
+
# Extract reset time from rate limit message
|
|
341
|
+
def extract_reset_time_from_message(message)
|
|
342
|
+
# Handle expressions like "resets 4am" or "reset at 4:30pm"
|
|
343
|
+
time_of_day_match = message.match(/reset(?:s)?(?:\s+at)?\s+(\d{1,2})(?::(\d{2}))?\s*(am|pm)/i)
|
|
344
|
+
if time_of_day_match
|
|
345
|
+
hour = time_of_day_match[1].to_i
|
|
346
|
+
minute = time_of_day_match[2] ? time_of_day_match[2].to_i : 0
|
|
347
|
+
meridiem = time_of_day_match[3].downcase
|
|
348
|
+
|
|
349
|
+
hour %= 12
|
|
350
|
+
hour += 12 if meridiem == "pm"
|
|
351
|
+
|
|
352
|
+
now = Time.now
|
|
353
|
+
reset_time = Time.new(now.year, now.month, now.day, hour, minute, 0, now.utc_offset)
|
|
354
|
+
reset_time += 86_400 if reset_time <= now
|
|
355
|
+
return reset_time
|
|
356
|
+
end
|
|
357
|
+
|
|
358
|
+
# Default to 1 hour from now if no specific time found
|
|
359
|
+
Time.now + 3600
|
|
360
|
+
end
|
|
361
|
+
|
|
290
362
|
# Check if we should skip permissions based on devcontainer configuration
|
|
291
363
|
# Overrides base class to add logging and Claude-specific config check
|
|
292
364
|
def should_skip_permissions?
|
data/lib/aidp/providers/base.rb
CHANGED
|
@@ -37,7 +37,7 @@ module Aidp
|
|
|
37
37
|
TIMEOUT_REFACTORING_RECOMMENDATIONS = 600 # 10 minutes - refactoring
|
|
38
38
|
TIMEOUT_IMPLEMENTATION = 900 # 15 minutes - implementation (write files, run tests, fix issues)
|
|
39
39
|
|
|
40
|
-
attr_reader :activity_state, :last_activity_time, :start_time, :step_name
|
|
40
|
+
attr_reader :activity_state, :last_activity_time, :start_time, :step_name, :model
|
|
41
41
|
|
|
42
42
|
def initialize(output: nil, prompt: TTY::Prompt.new)
|
|
43
43
|
@activity_state = :idle
|
|
@@ -52,6 +52,7 @@ module Aidp
|
|
|
52
52
|
@harness_context = nil
|
|
53
53
|
@output = output
|
|
54
54
|
@prompt = prompt
|
|
55
|
+
@model = nil
|
|
55
56
|
@harness_metrics = {
|
|
56
57
|
total_requests: 0,
|
|
57
58
|
successful_requests: 0,
|
|
@@ -74,6 +75,12 @@ module Aidp
|
|
|
74
75
|
name
|
|
75
76
|
end
|
|
76
77
|
|
|
78
|
+
# Configure the provider with options
|
|
79
|
+
# @param config [Hash] Configuration options, may include :model
|
|
80
|
+
def configure(config)
|
|
81
|
+
@model = config[:model] if config[:model]
|
|
82
|
+
end
|
|
83
|
+
|
|
77
84
|
def send_message(prompt:, session: nil)
|
|
78
85
|
raise NotImplementedError, "#{self.class} must implement #send_message"
|
|
79
86
|
end
|
|
@@ -360,6 +367,23 @@ module Aidp
|
|
|
360
367
|
[]
|
|
361
368
|
end
|
|
362
369
|
|
|
370
|
+
# Get firewall requirements for this provider
|
|
371
|
+
#
|
|
372
|
+
# Returns domains and IP ranges that need to be accessible for this provider
|
|
373
|
+
# to function properly. Used by devcontainer firewall configuration.
|
|
374
|
+
#
|
|
375
|
+
# @return [Hash] Firewall requirements with :domains and :ip_ranges keys
|
|
376
|
+
# - domains: Array of domain strings
|
|
377
|
+
# - ip_ranges: Array of CIDR strings
|
|
378
|
+
#
|
|
379
|
+
# Override in subclasses to provide provider-specific requirements
|
|
380
|
+
def self.firewall_requirements
|
|
381
|
+
{
|
|
382
|
+
domains: [],
|
|
383
|
+
ip_ranges: []
|
|
384
|
+
}
|
|
385
|
+
end
|
|
386
|
+
|
|
363
387
|
protected
|
|
364
388
|
|
|
365
389
|
# Log message to job if in background mode
|
data/lib/aidp/providers/codex.rb
CHANGED
|
@@ -39,6 +39,23 @@ module Aidp
|
|
|
39
39
|
discover_models_from_registry(MODEL_PATTERN, "codex")
|
|
40
40
|
end
|
|
41
41
|
|
|
42
|
+
# Get firewall requirements for Codex provider
|
|
43
|
+
# Codex uses OpenAI APIs
|
|
44
|
+
def self.firewall_requirements
|
|
45
|
+
{
|
|
46
|
+
domains: [
|
|
47
|
+
"api.openai.com",
|
|
48
|
+
"auth.openai.com",
|
|
49
|
+
"openai.com",
|
|
50
|
+
"chat.openai.com",
|
|
51
|
+
"chatgpt.com",
|
|
52
|
+
"cdn.openai.com",
|
|
53
|
+
"oaiusercontent.com"
|
|
54
|
+
],
|
|
55
|
+
ip_ranges: []
|
|
56
|
+
}
|
|
57
|
+
end
|
|
58
|
+
|
|
42
59
|
def name
|
|
43
60
|
"codex"
|
|
44
61
|
end
|
|
@@ -94,6 +111,11 @@ module Aidp
|
|
|
94
111
|
# Use non-interactive mode (exec) for automation
|
|
95
112
|
args = ["exec", prompt]
|
|
96
113
|
|
|
114
|
+
# Add model if configured
|
|
115
|
+
if @model && !@model.empty?
|
|
116
|
+
args += ["--model", @model]
|
|
117
|
+
end
|
|
118
|
+
|
|
97
119
|
# Add session support if provided (codex may support session/thread continuation)
|
|
98
120
|
if session && !session.empty?
|
|
99
121
|
args += ["--session", session]
|
|
@@ -149,9 +171,10 @@ module Aidp
|
|
|
149
171
|
args += ["--session", session]
|
|
150
172
|
end
|
|
151
173
|
|
|
152
|
-
# Add model selection
|
|
153
|
-
|
|
154
|
-
|
|
174
|
+
# Add model selection (from parameter or configured model)
|
|
175
|
+
model_to_use = model || @model
|
|
176
|
+
if model_to_use
|
|
177
|
+
args += ["--model", model_to_use]
|
|
155
178
|
end
|
|
156
179
|
|
|
157
180
|
# Add approval flag - but warn about interactive behavior
|
|
@@ -46,6 +46,22 @@ module Aidp
|
|
|
46
46
|
family_name.match?(/^(claude|gpt|cursor)-/)
|
|
47
47
|
end
|
|
48
48
|
|
|
49
|
+
# Get firewall requirements for Cursor provider
|
|
50
|
+
def self.firewall_requirements
|
|
51
|
+
{
|
|
52
|
+
domains: [
|
|
53
|
+
"cursor.com",
|
|
54
|
+
"www.cursor.com",
|
|
55
|
+
"downloads.cursor.com",
|
|
56
|
+
"api.cursor.sh",
|
|
57
|
+
"cursor.sh",
|
|
58
|
+
"app.cursor.sh",
|
|
59
|
+
"www.cursor.sh"
|
|
60
|
+
],
|
|
61
|
+
ip_ranges: []
|
|
62
|
+
}
|
|
63
|
+
end
|
|
64
|
+
|
|
49
65
|
# Discover available models from Cursor
|
|
50
66
|
#
|
|
51
67
|
# Note: Cursor doesn't have a public model listing API
|
|
@@ -55,6 +55,19 @@ module Aidp
|
|
|
55
55
|
discover_models_from_registry(MODEL_PATTERN, "gemini")
|
|
56
56
|
end
|
|
57
57
|
|
|
58
|
+
# Get firewall requirements for Gemini provider
|
|
59
|
+
def self.firewall_requirements
|
|
60
|
+
{
|
|
61
|
+
domains: [
|
|
62
|
+
"generativelanguage.googleapis.com",
|
|
63
|
+
"oauth2.googleapis.com",
|
|
64
|
+
"accounts.google.com",
|
|
65
|
+
"www.googleapis.com"
|
|
66
|
+
],
|
|
67
|
+
ip_ranges: []
|
|
68
|
+
}
|
|
69
|
+
end
|
|
70
|
+
|
|
58
71
|
def name
|
|
59
72
|
"gemini"
|
|
60
73
|
end
|
|
@@ -14,6 +14,23 @@ module Aidp
|
|
|
14
14
|
!!Aidp::Util.which("copilot")
|
|
15
15
|
end
|
|
16
16
|
|
|
17
|
+
# Get firewall requirements for GitHub Copilot provider
|
|
18
|
+
def self.firewall_requirements
|
|
19
|
+
{
|
|
20
|
+
domains: [
|
|
21
|
+
"copilot-proxy.githubusercontent.com",
|
|
22
|
+
"api.githubcopilot.com",
|
|
23
|
+
"copilot-telemetry.githubusercontent.com",
|
|
24
|
+
"default.exp-tas.com",
|
|
25
|
+
"copilot-completions.githubusercontent.com",
|
|
26
|
+
"business.githubcopilot.com",
|
|
27
|
+
"enterprise.githubcopilot.com",
|
|
28
|
+
"individual.githubcopilot.com"
|
|
29
|
+
],
|
|
30
|
+
ip_ranges: []
|
|
31
|
+
}
|
|
32
|
+
end
|
|
33
|
+
|
|
17
34
|
def name
|
|
18
35
|
"github_copilot"
|
|
19
36
|
end
|
|
@@ -37,6 +37,17 @@ module Aidp
|
|
|
37
37
|
discover_models_from_registry(MODEL_PATTERN, "kilocode")
|
|
38
38
|
end
|
|
39
39
|
|
|
40
|
+
# Get firewall requirements for Kilocode provider
|
|
41
|
+
def self.firewall_requirements
|
|
42
|
+
{
|
|
43
|
+
domains: [
|
|
44
|
+
"kilocode.ai",
|
|
45
|
+
"api.kilocode.ai"
|
|
46
|
+
],
|
|
47
|
+
ip_ranges: []
|
|
48
|
+
}
|
|
49
|
+
end
|
|
50
|
+
|
|
40
51
|
def name
|
|
41
52
|
"kilocode"
|
|
42
53
|
end
|
|
@@ -37,6 +37,17 @@ module Aidp
|
|
|
37
37
|
discover_models_from_registry(MODEL_PATTERN, "opencode")
|
|
38
38
|
end
|
|
39
39
|
|
|
40
|
+
# Get firewall requirements for OpenCode provider
|
|
41
|
+
def self.firewall_requirements
|
|
42
|
+
{
|
|
43
|
+
domains: [
|
|
44
|
+
"api.opencode.ai",
|
|
45
|
+
"auth.opencode.ai"
|
|
46
|
+
],
|
|
47
|
+
ip_ranges: []
|
|
48
|
+
}
|
|
49
|
+
end
|
|
50
|
+
|
|
40
51
|
def name
|
|
41
52
|
"opencode"
|
|
42
53
|
end
|