aidp 0.34.1 → 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.
Files changed (132) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +14 -14
  3. data/lib/aidp/agent_harness_adapter.rb +309 -0
  4. data/lib/aidp/cli/issue_importer.rb +20 -6
  5. data/lib/aidp/cli/jobs_command.rb +1 -1
  6. data/lib/aidp/cli/models_command.rb +9 -16
  7. data/lib/aidp/cli/prompts_command.rb +253 -0
  8. data/lib/aidp/cli/security_command.rb +4 -10
  9. data/lib/aidp/cli/storage_command.rb +277 -0
  10. data/lib/aidp/cli/temporal_command.rb +460 -0
  11. data/lib/aidp/cli.rb +24 -2
  12. data/lib/aidp/config.rb +15 -0
  13. data/lib/aidp/config_paths.rb +12 -2
  14. data/lib/aidp/database/migrations.rb +114 -0
  15. data/lib/aidp/database/repositories/checkpoint_repository.rb +174 -0
  16. data/lib/aidp/database/repositories/deprecated_models_repository.rb +206 -0
  17. data/lib/aidp/database/repositories/evaluation_repository.rb +208 -0
  18. data/lib/aidp/database/repositories/harness_state_repository.rb +140 -0
  19. data/lib/aidp/database/repositories/job_repository.rb +278 -0
  20. data/lib/aidp/database/repositories/model_cache_repository.rb +194 -0
  21. data/lib/aidp/database/repositories/progress_repository.rb +210 -0
  22. data/lib/aidp/database/repositories/prompt_archive_repository.rb +195 -0
  23. data/lib/aidp/database/repositories/prompt_feedback_repository.rb +266 -0
  24. data/lib/aidp/database/repositories/provider_info_cache_repository.rb +208 -0
  25. data/lib/aidp/database/repositories/provider_metrics_repository.rb +245 -0
  26. data/lib/aidp/database/repositories/secrets_repository.rb +187 -0
  27. data/lib/aidp/database/repositories/task_repository.rb +231 -0
  28. data/lib/aidp/database/repositories/template_version_repository.rb +383 -0
  29. data/lib/aidp/database/repositories/watch_state_repository.rb +472 -0
  30. data/lib/aidp/database/repositories/workstream_repository.rb +310 -0
  31. data/lib/aidp/database/repositories/worktree_repository.rb +240 -0
  32. data/lib/aidp/database/repository.rb +166 -0
  33. data/lib/aidp/database/schema.rb +347 -0
  34. data/lib/aidp/database/storage_migrator.rb +693 -0
  35. data/lib/aidp/database.rb +139 -0
  36. data/lib/aidp/debug_mixin.rb +2 -2
  37. data/lib/aidp/execute/checkpoint.rb +26 -0
  38. data/lib/aidp/execute/checkpoint_display.rb +16 -0
  39. data/lib/aidp/execute/prompt_evaluator.rb +30 -57
  40. data/lib/aidp/execute/work_loop_runner.rb +294 -46
  41. data/lib/aidp/firewall/provider_requirements_collector.rb +7 -23
  42. data/lib/aidp/harness/agent_harness_provider_manager.rb +275 -0
  43. data/lib/aidp/harness/ai_decision_engine.rb +78 -296
  44. data/lib/aidp/harness/config_schema.rb +59 -0
  45. data/lib/aidp/harness/configuration.rb +229 -10
  46. data/lib/aidp/harness/deprecation_cache.rb +53 -11
  47. data/lib/aidp/harness/enhanced_runner.rb +1 -1
  48. data/lib/aidp/harness/error_handler.rb +11 -5
  49. data/lib/aidp/harness/provider_config.rb +36 -0
  50. data/lib/aidp/harness/provider_factory.rb +26 -24
  51. data/lib/aidp/harness/provider_info.rb +9 -5
  52. data/lib/aidp/harness/provider_manager.rb +2 -2
  53. data/lib/aidp/harness/provider_metrics.rb +103 -1
  54. data/lib/aidp/harness/rspec_command_optimizer.rb +166 -0
  55. data/lib/aidp/harness/ruby_llm_registry.rb +97 -8
  56. data/lib/aidp/harness/runner.rb +7 -2
  57. data/lib/aidp/harness/state_manager.rb +1 -1
  58. data/lib/aidp/harness/test_runner.rb +167 -11
  59. data/lib/aidp/harness/thinking_depth_manager.rb +564 -5
  60. data/lib/aidp/harness/usage_limit.rb +179 -0
  61. data/lib/aidp/harness/usage_limit_enforcer.rb +240 -0
  62. data/lib/aidp/harness/usage_limit_tracker.rb +308 -0
  63. data/lib/aidp/harness/usage_period.rb +234 -0
  64. data/lib/aidp/init/agent_instructions_generator.rb +317 -0
  65. data/lib/aidp/init/runner.rb +25 -4
  66. data/lib/aidp/interfaces/activity_monitor_interface.rb +234 -0
  67. data/lib/aidp/interfaces/binary_checker_interface.rb +232 -0
  68. data/lib/aidp/interfaces/command_executor_interface.rb +216 -0
  69. data/lib/aidp/interfaces/logger_interface.rb +150 -0
  70. data/lib/aidp/interfaces/ui_interface.rb +254 -0
  71. data/lib/aidp/logger.rb +25 -7
  72. data/lib/aidp/orchestration_adapter.rb +279 -0
  73. data/lib/aidp/pr_worktree_manager.rb +4 -15
  74. data/lib/aidp/prompts/feedback_collector.rb +198 -0
  75. data/lib/aidp/prompts/prompt_template_manager.rb +415 -0
  76. data/lib/aidp/prompts/template_evolver.rb +305 -0
  77. data/lib/aidp/prompts/template_version_manager.rb +325 -0
  78. data/lib/aidp/setup/in_memory_config_adapter.rb +257 -0
  79. data/lib/aidp/setup/in_memory_config_manager.rb +262 -0
  80. data/lib/aidp/setup/wizard.rb +616 -176
  81. data/lib/aidp/shell_executor.rb +58 -0
  82. data/lib/aidp/style_guide/selector.rb +56 -1
  83. data/lib/aidp/temporal/activities/analyze_issue_activity.rb +198 -0
  84. data/lib/aidp/temporal/activities/analyze_sub_task_activity.rb +163 -0
  85. data/lib/aidp/temporal/activities/base_activity.rb +78 -0
  86. data/lib/aidp/temporal/activities/create_plan_activity.rb +115 -0
  87. data/lib/aidp/temporal/activities/create_pr_activity.rb +193 -0
  88. data/lib/aidp/temporal/activities/create_prompt_activity.rb +118 -0
  89. data/lib/aidp/temporal/activities/diagnose_failure_activity.rb +228 -0
  90. data/lib/aidp/temporal/activities/prepare_next_iteration_activity.rb +147 -0
  91. data/lib/aidp/temporal/activities/record_checkpoint_activity.rb +81 -0
  92. data/lib/aidp/temporal/activities/run_agent_activity.rb +139 -0
  93. data/lib/aidp/temporal/activities/run_tests_activity.rb +114 -0
  94. data/lib/aidp/temporal/activities/run_work_loop_iteration_activity.rb +249 -0
  95. data/lib/aidp/temporal/configuration.rb +169 -0
  96. data/lib/aidp/temporal/connection.rb +93 -0
  97. data/lib/aidp/temporal/worker.rb +119 -0
  98. data/lib/aidp/temporal/workflow_client.rb +171 -0
  99. data/lib/aidp/temporal/workflows/base_workflow.rb +73 -0
  100. data/lib/aidp/temporal/workflows/issue_to_pr_workflow.rb +278 -0
  101. data/lib/aidp/temporal/workflows/sub_issue_workflow.rb +253 -0
  102. data/lib/aidp/temporal/workflows/work_loop_workflow.rb +321 -0
  103. data/lib/aidp/temporal.rb +120 -0
  104. data/lib/aidp/version.rb +1 -1
  105. data/lib/aidp/watch/base_processor.rb +35 -0
  106. data/lib/aidp/watch/build_processor.rb +132 -0
  107. data/lib/aidp/watch/github_state_extractor.rb +0 -8
  108. data/lib/aidp/watch/rebase_processor.rb +245 -0
  109. data/lib/aidp/watch/round_robin_scheduler.rb +150 -0
  110. data/lib/aidp/watch/runner.rb +366 -285
  111. data/lib/aidp/watch/state_store.rb +80 -0
  112. data/lib/aidp/watch/work_item.rb +96 -0
  113. data/lib/aidp/watch/worktree_cleanup_job.rb +226 -0
  114. data/lib/aidp/watch/worktree_reconciler.rb +643 -0
  115. data/lib/aidp/workflows/guided_agent.rb +1 -1
  116. data/lib/aidp/worktree.rb +62 -0
  117. data/templates/aidp.yml.example +70 -0
  118. metadata +117 -16
  119. data/lib/aidp/analyze/json_file_storage.rb +0 -292
  120. data/lib/aidp/execute/future_work_backlog.rb +0 -411
  121. data/lib/aidp/providers/adapter.rb +0 -239
  122. data/lib/aidp/providers/aider.rb +0 -264
  123. data/lib/aidp/providers/anthropic.rb +0 -614
  124. data/lib/aidp/providers/base.rb +0 -617
  125. data/lib/aidp/providers/capability_registry.rb +0 -204
  126. data/lib/aidp/providers/codex.rb +0 -267
  127. data/lib/aidp/providers/cursor.rb +0 -304
  128. data/lib/aidp/providers/error_taxonomy.rb +0 -195
  129. data/lib/aidp/providers/gemini.rb +0 -110
  130. data/lib/aidp/providers/github_copilot.rb +0 -227
  131. data/lib/aidp/providers/kilocode.rb +0 -157
  132. data/lib/aidp/providers/opencode.rb +0 -139
@@ -1,227 +0,0 @@
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 GithubCopilot < Base
11
- include Aidp::DebugMixin
12
-
13
- def self.available?
14
- !!Aidp::Util.which("copilot")
15
- end
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
-
34
- def name
35
- "github_copilot"
36
- end
37
-
38
- def display_name
39
- "GitHub Copilot CLI"
40
- end
41
-
42
- def available?
43
- return false unless self.class.available?
44
-
45
- # Additional check to ensure the CLI is properly configured
46
- begin
47
- result = Aidp::Util.execute_command("copilot", ["--version"], timeout: 10)
48
- result.exit_status == 0
49
- rescue
50
- false
51
- end
52
- end
53
-
54
- def send_message(prompt:, session: nil, options: {})
55
- raise "copilot CLI not available" unless self.class.available?
56
-
57
- # Smart timeout calculation
58
- timeout_seconds = calculate_timeout
59
-
60
- debug_provider("copilot", "Starting execution", {timeout: timeout_seconds})
61
- debug_log("📝 Sending prompt to copilot (length: #{prompt.length})", level: :info)
62
-
63
- # Set up activity monitoring
64
- setup_activity_monitoring("copilot", method(:activity_callback))
65
- record_activity("Starting copilot execution")
66
-
67
- # Create a spinner for activity display
68
- spinner = TTY::Spinner.new("[:spinner] :title", format: :dots, hide_cursor: true)
69
- spinner.auto_spin
70
-
71
- activity_display_thread = Thread.new do
72
- start_time = Time.now
73
- loop do
74
- sleep 0.5 # Update every 500ms to reduce spam
75
- elapsed = Time.now - start_time
76
-
77
- # Break if we've been running too long or state changed
78
- break if elapsed > timeout_seconds || @activity_state == :completed || @activity_state == :failed
79
-
80
- update_spinner_status(spinner, elapsed, "🤖 GitHub Copilot CLI")
81
- end
82
- end
83
-
84
- begin
85
- # Use non-interactive mode for automation
86
- args = ["-p", prompt, "--allow-all-tools"]
87
-
88
- # Add session support if provided
89
- if session && !session.empty?
90
- args += ["--resume", session]
91
- end
92
-
93
- # Use debug_execute_command for better debugging (no input since prompt is in args)
94
- result = debug_execute_command("copilot", args: args, timeout: timeout_seconds)
95
-
96
- # Log the results
97
- debug_command("copilot", args: args, input: prompt, output: result.out, error: result.err, exit_code: result.exit_status)
98
-
99
- # Detect authorization/access errors
100
- auth_error = result.err.to_s =~ /not authorized|requires an enterprise|access denied|permission denied|not enabled/i
101
- if auth_error
102
- spinner.error("✗")
103
- mark_failed("copilot authorization error: #{result.err}")
104
- @unavailable = true
105
- debug_error(StandardError.new("copilot authorization error"), {exit_code: result.exit_status, stderr: result.err})
106
- raise Aidp::Providers::ProviderUnavailableError.new("copilot authorization error: #{result.err}")
107
- end
108
-
109
- if result.exit_status == 0
110
- spinner.success("✓")
111
- mark_completed
112
- result.out
113
- else
114
- spinner.error("✗")
115
- mark_failed("copilot failed with exit code #{result.exit_status}")
116
- debug_error(StandardError.new("copilot failed"), {exit_code: result.exit_status, stderr: result.err})
117
- raise "copilot failed with exit code #{result.exit_status}: #{result.err}"
118
- end
119
- rescue Aidp::Providers::ProviderUnavailableError
120
- raise
121
- rescue => e
122
- spinner&.error("✗")
123
- mark_failed("copilot execution failed: #{e.message}")
124
- debug_error(e, {provider: "github_copilot", prompt_length: prompt.length})
125
- raise
126
- ensure
127
- cleanup_activity_display(activity_display_thread, spinner)
128
- end
129
- end
130
-
131
- # Enhanced send method with additional options
132
- def send_with_options(prompt:, session: nil, tools: nil, log_level: nil, config_file: nil, directories: nil)
133
- args = ["-p", prompt]
134
-
135
- # Add session support
136
- if session && !session.empty?
137
- args += ["--resume", session]
138
- end
139
-
140
- # Add tool permissions
141
- if tools && !tools.empty?
142
- if tools.include?("all")
143
- args += ["--allow-all-tools"]
144
- else
145
- tools.each do |tool|
146
- args += ["--allow-tool", tool]
147
- end
148
- end
149
- else
150
- # Default to allowing all tools for automation
151
- args += ["--allow-all-tools"]
152
- end
153
-
154
- # Add logging level
155
- if log_level
156
- args += ["--log-level", log_level]
157
- end
158
-
159
- # Add allowed directories
160
- if directories && !directories.empty?
161
- directories.each do |dir|
162
- args += ["--add-dir", dir]
163
- end
164
- end
165
-
166
- # Use the enhanced version of send
167
- send_with_custom_args(prompt: prompt, args: args)
168
- end
169
-
170
- # Override health check for GitHub Copilot specific considerations
171
- def harness_healthy?
172
- return false unless super
173
-
174
- # Additional health checks specific to GitHub Copilot CLI
175
- # Check if we can access GitHub (basic connectivity test)
176
- begin
177
- result = Aidp::Util.execute_command("copilot", ["--help"], timeout: 5)
178
- result.exit_status == 0
179
- rescue
180
- false
181
- end
182
- end
183
-
184
- private
185
-
186
- def send_with_custom_args(prompt:, args:)
187
- timeout_seconds = calculate_timeout
188
-
189
- debug_provider("copilot", "Starting execution", {timeout: timeout_seconds, args: args})
190
- debug_log("📝 Sending prompt to copilot with custom args", level: :info)
191
-
192
- setup_activity_monitoring("copilot", method(:activity_callback))
193
- record_activity("Starting copilot execution with custom args")
194
-
195
- begin
196
- result = debug_execute_command("copilot", args: args, timeout: timeout_seconds)
197
- debug_command("copilot", args: args, output: result.out, error: result.err, exit_code: result.exit_status)
198
-
199
- if result.exit_status == 0
200
- mark_completed
201
- result.out
202
- else
203
- mark_failed("copilot failed with exit code #{result.exit_status}")
204
- debug_error(StandardError.new("copilot failed"), {exit_code: result.exit_status, stderr: result.err})
205
- raise "copilot failed with exit code #{result.exit_status}: #{result.err}"
206
- end
207
- rescue => e
208
- mark_failed("copilot execution failed: #{e.message}")
209
- debug_error(e, {provider: "github_copilot", prompt_length: prompt.length})
210
- raise
211
- end
212
- end
213
-
214
- def activity_callback(state, message, provider)
215
- # Handle activity state changes
216
- case state
217
- when :stuck
218
- display_message("\n⚠️ GitHub Copilot CLI appears stuck: #{message}", type: :warning)
219
- when :completed
220
- display_message("\n✅ GitHub Copilot CLI completed: #{message}", type: :success)
221
- when :failed
222
- display_message("\n❌ GitHub Copilot CLI failed: #{message}", type: :error)
223
- end
224
- end
225
- end
226
- end
227
- end
@@ -1,157 +0,0 @@
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 Kilocode < Base
11
- include Aidp::DebugMixin
12
-
13
- # Model name pattern for OpenAI models (since Kilocode uses OpenAI)
14
- MODEL_PATTERN = /^gpt-[\d.o-]+(?:-turbo)?(?:-mini)?$/i
15
-
16
- def self.available?
17
- !!Aidp::Util.which("kilocode")
18
- end
19
-
20
- # Check if this provider supports a given model family
21
- #
22
- # @param family_name [String] The model family name
23
- # @return [Boolean] True if it matches OpenAI model pattern
24
- def self.supports_model_family?(family_name)
25
- MODEL_PATTERN.match?(family_name)
26
- end
27
-
28
- # Discover available models from registry
29
- #
30
- # Note: Kilocode CLI doesn't have a standard model listing command
31
- # Returns registry-based models that match OpenAI patterns
32
- #
33
- # @return [Array<Hash>] Array of discovered models
34
- def self.discover_models
35
- return [] unless available?
36
-
37
- discover_models_from_registry(MODEL_PATTERN, "kilocode")
38
- end
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
-
51
- def name
52
- "kilocode"
53
- end
54
-
55
- def display_name
56
- "Kilocode"
57
- end
58
-
59
- def send_message(prompt:, session: nil, options: {})
60
- raise "kilocode not available" unless self.class.available?
61
-
62
- # Smart timeout calculation
63
- timeout_seconds = calculate_timeout
64
-
65
- debug_provider("kilocode", "Starting execution", {timeout: timeout_seconds})
66
- debug_log("📝 Sending prompt to kilocode (length: #{prompt.length})", level: :info)
67
-
68
- # Check if prompt is too large and warn
69
- if prompt.length > 3000
70
- debug_log("⚠️ Large prompt detected (#{prompt.length} chars) - this may cause rate limiting", level: :warn)
71
- end
72
-
73
- # Set up activity monitoring
74
- setup_activity_monitoring("kilocode", method(:activity_callback))
75
- record_activity("Starting kilocode execution")
76
-
77
- # Create a spinner for activity display
78
- spinner = TTY::Spinner.new("[:spinner] :title", format: :dots, hide_cursor: true)
79
- spinner.auto_spin
80
-
81
- activity_display_thread = Thread.new do
82
- start_time = Time.now
83
- loop do
84
- sleep 0.5 # Update every 500ms to reduce spam
85
- elapsed = Time.now - start_time
86
-
87
- # Break if we've been running too long or state changed
88
- break if elapsed > timeout_seconds || @activity_state == :completed || @activity_state == :failed
89
-
90
- update_spinner_status(spinner, elapsed, "🔄 kilocode")
91
- end
92
- end
93
-
94
- begin
95
- # Build kilocode command arguments
96
- args = ["--auto"]
97
-
98
- # Add model if specified
99
- model = ENV["KILOCODE_MODEL"]
100
- if model
101
- args.concat(["-m", model])
102
- end
103
-
104
- # Add workspace detection if needed
105
- if Dir.exist?(".git") && ENV["KILOCODE_WORKSPACE"]
106
- args.concat(["--workspace", ENV["KILOCODE_WORKSPACE"]])
107
- end
108
-
109
- # Set authentication via environment variable
110
- env_vars = {}
111
- if ENV["KILOCODE_TOKEN"]
112
- env_vars["KILOCODE_TOKEN"] = ENV["KILOCODE_TOKEN"]
113
- end
114
-
115
- # Use debug_execute_command for better debugging
116
- result = debug_execute_command("kilocode", args: args, input: prompt, timeout: timeout_seconds, env: env_vars)
117
-
118
- # Log the results
119
- debug_command("kilocode", args: args, input: prompt, output: result.out, error: result.err, exit_code: result.exit_status)
120
-
121
- if result.exit_status == 0
122
- spinner.success("✓")
123
- mark_completed
124
- result.out
125
- else
126
- spinner.error("✗")
127
- mark_failed("kilocode failed with exit code #{result.exit_status}")
128
- debug_error(StandardError.new("kilocode failed"), {exit_code: result.exit_status, stderr: result.err})
129
- raise "kilocode failed with exit code #{result.exit_status}: #{result.err}"
130
- end
131
- rescue => e
132
- spinner&.error("✗")
133
- mark_failed("kilocode execution failed: #{e.message}")
134
- debug_error(e, {provider: "kilocode", prompt_length: prompt.length})
135
- raise
136
- ensure
137
- cleanup_activity_display(activity_display_thread, spinner)
138
- end
139
- end
140
-
141
- private
142
-
143
- def activity_callback(state, message, provider)
144
- # This is now handled by the animated display thread
145
- # Only print static messages for state changes
146
- case state
147
- when :starting
148
- display_message("🚀 Starting kilocode execution...", type: :info)
149
- when :completed
150
- display_message("✅ kilocode execution completed", type: :success)
151
- when :failed
152
- display_message("❌ kilocode execution failed: #{message}", type: :error)
153
- end
154
- end
155
- end
156
- end
157
- end
@@ -1,139 +0,0 @@
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 Opencode < Base
11
- include Aidp::DebugMixin
12
-
13
- # Model name pattern for OpenAI models (since OpenCode uses OpenAI)
14
- MODEL_PATTERN = /^gpt-[\d.o-]+(?:-turbo)?(?:-mini)?$/i
15
-
16
- def self.available?
17
- !!Aidp::Util.which("opencode")
18
- end
19
-
20
- # Check if this provider supports a given model family
21
- #
22
- # @param family_name [String] The model family name
23
- # @return [Boolean] True if it matches OpenAI model pattern
24
- def self.supports_model_family?(family_name)
25
- MODEL_PATTERN.match?(family_name)
26
- end
27
-
28
- # Discover available models from registry
29
- #
30
- # Note: OpenCode CLI doesn't have a standard model listing command
31
- # Returns registry-based models that match OpenAI patterns
32
- #
33
- # @return [Array<Hash>] Array of discovered models
34
- def self.discover_models
35
- return [] unless available?
36
-
37
- discover_models_from_registry(MODEL_PATTERN, "opencode")
38
- end
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
-
51
- def name
52
- "opencode"
53
- end
54
-
55
- def display_name
56
- "OpenCode"
57
- end
58
-
59
- def send_message(prompt:, session: nil, options: {})
60
- raise "opencode not available" unless self.class.available?
61
-
62
- # Smart timeout calculation
63
- timeout_seconds = calculate_timeout
64
-
65
- debug_provider("opencode", "Starting execution", {timeout: timeout_seconds})
66
- debug_log("📝 Sending prompt to opencode (length: #{prompt.length})", level: :info)
67
-
68
- # Check if prompt is too large and warn
69
- if prompt.length > 3000
70
- debug_log("⚠️ Large prompt detected (#{prompt.length} chars) - this may cause rate limiting", level: :warn)
71
- end
72
-
73
- # Set up activity monitoring
74
- setup_activity_monitoring("opencode", method(:activity_callback))
75
- record_activity("Starting opencode execution")
76
-
77
- # Create a spinner for activity display
78
- spinner = TTY::Spinner.new("[:spinner] :title", format: :dots, hide_cursor: true)
79
- spinner.auto_spin
80
-
81
- activity_display_thread = Thread.new do
82
- start_time = Time.now
83
- loop do
84
- sleep 0.5 # Update every 500ms to reduce spam
85
- elapsed = Time.now - start_time
86
-
87
- # Break if we've been running too long or state changed
88
- break if elapsed > timeout_seconds || @activity_state == :completed || @activity_state == :failed
89
-
90
- update_spinner_status(spinner, elapsed, "🔄 opencode")
91
- end
92
- end
93
-
94
- begin
95
- # Use debug_execute_command for better debugging
96
- # opencode run command with prompt and model
97
- model = ENV["OPENCODE_MODEL"] || "github-copilot/claude-3.5-sonnet"
98
- result = debug_execute_command("opencode", args: ["run", "-m", model, prompt], timeout: timeout_seconds)
99
-
100
- # Log the results
101
- debug_command("opencode", args: ["run", "-m", model, prompt], input: nil, output: result.out, error: result.err, exit_code: result.exit_status)
102
-
103
- if result.exit_status == 0
104
- spinner.success("✓")
105
- mark_completed
106
- result.out
107
- else
108
- spinner.error("✗")
109
- mark_failed("opencode failed with exit code #{result.exit_status}")
110
- debug_error(StandardError.new("opencode failed"), {exit_code: result.exit_status, stderr: result.err})
111
- raise "opencode failed with exit code #{result.exit_status}: #{result.err}"
112
- end
113
- rescue => e
114
- spinner&.error("✗")
115
- mark_failed("opencode execution failed: #{e.message}")
116
- debug_error(e, {provider: "opencode", prompt_length: prompt.length})
117
- raise
118
- ensure
119
- cleanup_activity_display(activity_display_thread, spinner)
120
- end
121
- end
122
-
123
- private
124
-
125
- def activity_callback(state, message, provider)
126
- # This is now handled by the animated display thread
127
- # Only print static messages for state changes
128
- case state
129
- when :starting
130
- display_message("🚀 Starting opencode execution...", type: :info)
131
- when :completed
132
- display_message("✅ opencode execution completed", type: :success)
133
- when :failed
134
- display_message("❌ opencode execution failed: #{message}", type: :error)
135
- end
136
- end
137
- end
138
- end
139
- end