aidp 0.7.0 → 0.8.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 +60 -214
- data/bin/aidp +1 -1
- data/lib/aidp/analysis/kb_inspector.rb +38 -23
- data/lib/aidp/analysis/seams.rb +2 -31
- data/lib/aidp/analysis/tree_sitter_grammar_loader.rb +0 -13
- data/lib/aidp/analysis/tree_sitter_scan.rb +3 -20
- data/lib/aidp/analyze/error_handler.rb +2 -75
- data/lib/aidp/analyze/json_file_storage.rb +292 -0
- data/lib/aidp/analyze/progress.rb +12 -0
- data/lib/aidp/analyze/progress_visualizer.rb +12 -17
- data/lib/aidp/analyze/ruby_maat_integration.rb +13 -31
- data/lib/aidp/analyze/runner.rb +256 -87
- data/lib/aidp/cli/jobs_command.rb +100 -432
- data/lib/aidp/cli.rb +309 -239
- data/lib/aidp/config.rb +298 -10
- data/lib/aidp/debug_logger.rb +195 -0
- data/lib/aidp/debug_mixin.rb +187 -0
- data/lib/aidp/execute/progress.rb +9 -0
- data/lib/aidp/execute/runner.rb +221 -40
- data/lib/aidp/execute/steps.rb +17 -7
- data/lib/aidp/execute/workflow_selector.rb +211 -0
- data/lib/aidp/harness/completion_checker.rb +268 -0
- data/lib/aidp/harness/condition_detector.rb +1526 -0
- data/lib/aidp/harness/config_loader.rb +373 -0
- data/lib/aidp/harness/config_manager.rb +382 -0
- data/lib/aidp/harness/config_schema.rb +1006 -0
- data/lib/aidp/harness/config_validator.rb +355 -0
- data/lib/aidp/harness/configuration.rb +477 -0
- data/lib/aidp/harness/enhanced_runner.rb +494 -0
- data/lib/aidp/harness/error_handler.rb +616 -0
- data/lib/aidp/harness/provider_config.rb +423 -0
- data/lib/aidp/harness/provider_factory.rb +306 -0
- data/lib/aidp/harness/provider_manager.rb +1269 -0
- data/lib/aidp/harness/provider_type_checker.rb +88 -0
- data/lib/aidp/harness/runner.rb +411 -0
- data/lib/aidp/harness/state/errors.rb +28 -0
- data/lib/aidp/harness/state/metrics.rb +219 -0
- data/lib/aidp/harness/state/persistence.rb +128 -0
- data/lib/aidp/harness/state/provider_state.rb +132 -0
- data/lib/aidp/harness/state/ui_state.rb +68 -0
- data/lib/aidp/harness/state/workflow_state.rb +123 -0
- data/lib/aidp/harness/state_manager.rb +586 -0
- data/lib/aidp/harness/status_display.rb +888 -0
- data/lib/aidp/harness/ui/base.rb +16 -0
- data/lib/aidp/harness/ui/enhanced_tui.rb +545 -0
- data/lib/aidp/harness/ui/enhanced_workflow_selector.rb +252 -0
- data/lib/aidp/harness/ui/error_handler.rb +132 -0
- data/lib/aidp/harness/ui/frame_manager.rb +361 -0
- data/lib/aidp/harness/ui/job_monitor.rb +500 -0
- data/lib/aidp/harness/ui/navigation/main_menu.rb +311 -0
- data/lib/aidp/harness/ui/navigation/menu_formatter.rb +120 -0
- data/lib/aidp/harness/ui/navigation/menu_item.rb +142 -0
- data/lib/aidp/harness/ui/navigation/menu_state.rb +139 -0
- data/lib/aidp/harness/ui/navigation/submenu.rb +202 -0
- data/lib/aidp/harness/ui/navigation/workflow_selector.rb +176 -0
- data/lib/aidp/harness/ui/progress_display.rb +280 -0
- data/lib/aidp/harness/ui/question_collector.rb +141 -0
- data/lib/aidp/harness/ui/spinner_group.rb +184 -0
- data/lib/aidp/harness/ui/spinner_helper.rb +152 -0
- data/lib/aidp/harness/ui/status_manager.rb +312 -0
- data/lib/aidp/harness/ui/status_widget.rb +280 -0
- data/lib/aidp/harness/ui/workflow_controller.rb +312 -0
- data/lib/aidp/harness/user_interface.rb +2381 -0
- data/lib/aidp/provider_manager.rb +131 -7
- data/lib/aidp/providers/anthropic.rb +28 -103
- data/lib/aidp/providers/base.rb +170 -0
- data/lib/aidp/providers/cursor.rb +52 -181
- data/lib/aidp/providers/gemini.rb +24 -107
- data/lib/aidp/providers/macos_ui.rb +99 -5
- data/lib/aidp/providers/opencode.rb +194 -0
- data/lib/aidp/storage/csv_storage.rb +172 -0
- data/lib/aidp/storage/file_manager.rb +214 -0
- data/lib/aidp/storage/json_storage.rb +140 -0
- data/lib/aidp/version.rb +1 -1
- data/lib/aidp.rb +54 -39
- data/templates/COMMON/AGENT_BASE.md +11 -0
- data/templates/EXECUTE/00_PRD.md +4 -4
- data/templates/EXECUTE/02_ARCHITECTURE.md +5 -4
- data/templates/EXECUTE/07_TEST_PLAN.md +4 -1
- data/templates/EXECUTE/08_TASKS.md +4 -4
- data/templates/EXECUTE/10_IMPLEMENTATION_AGENT.md +4 -4
- data/templates/README.md +279 -0
- data/templates/aidp-development.yml.example +373 -0
- data/templates/aidp-minimal.yml.example +48 -0
- data/templates/aidp-production.yml.example +475 -0
- data/templates/aidp.yml.example +598 -0
- metadata +93 -69
- data/lib/aidp/analyze/agent_personas.rb +0 -71
- data/lib/aidp/analyze/agent_tool_executor.rb +0 -439
- data/lib/aidp/analyze/data_retention_manager.rb +0 -421
- data/lib/aidp/analyze/database.rb +0 -260
- data/lib/aidp/analyze/dependencies.rb +0 -335
- data/lib/aidp/analyze/export_manager.rb +0 -418
- data/lib/aidp/analyze/focus_guidance.rb +0 -517
- data/lib/aidp/analyze/incremental_analyzer.rb +0 -533
- data/lib/aidp/analyze/language_analysis_strategies.rb +0 -897
- data/lib/aidp/analyze/large_analysis_progress.rb +0 -499
- data/lib/aidp/analyze/memory_manager.rb +0 -339
- data/lib/aidp/analyze/metrics_storage.rb +0 -336
- data/lib/aidp/analyze/parallel_processor.rb +0 -454
- data/lib/aidp/analyze/performance_optimizer.rb +0 -691
- data/lib/aidp/analyze/repository_chunker.rb +0 -697
- data/lib/aidp/analyze/static_analysis_detector.rb +0 -577
- data/lib/aidp/analyze/storage.rb +0 -655
- data/lib/aidp/analyze/tool_configuration.rb +0 -441
- data/lib/aidp/analyze/tool_modernization.rb +0 -750
- data/lib/aidp/database/pg_adapter.rb +0 -148
- data/lib/aidp/database_config.rb +0 -69
- data/lib/aidp/database_connection.rb +0 -72
- data/lib/aidp/job_manager.rb +0 -41
- data/lib/aidp/jobs/base_job.rb +0 -45
- data/lib/aidp/jobs/provider_execution_job.rb +0 -83
- data/lib/aidp/project_detector.rb +0 -117
- data/lib/aidp/providers/agent_supervisor.rb +0 -348
- data/lib/aidp/providers/supervised_base.rb +0 -317
- data/lib/aidp/providers/supervised_cursor.rb +0 -22
- data/lib/aidp/sync.rb +0 -13
- data/lib/aidp/workspace.rb +0 -19
@@ -0,0 +1,1526 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Aidp
|
4
|
+
module Harness
|
5
|
+
# Detects run conditions (rate limits, user feedback, completion, errors)
|
6
|
+
class ConditionDetector
|
7
|
+
def initialize
|
8
|
+
# Enhanced rate limit patterns for different providers
|
9
|
+
@rate_limit_patterns = {
|
10
|
+
# Common patterns
|
11
|
+
common: [
|
12
|
+
/rate limit/i,
|
13
|
+
/too many requests/i,
|
14
|
+
/quota exceeded/i,
|
15
|
+
/429/i,
|
16
|
+
/rate.{0,20}exceeded/i,
|
17
|
+
/throttled/i,
|
18
|
+
/limit.{0,20}exceeded/i
|
19
|
+
],
|
20
|
+
# Anthropic/Claude specific
|
21
|
+
anthropic: [
|
22
|
+
/rate limit exceeded/i,
|
23
|
+
/too many requests/i,
|
24
|
+
/quota.{0,20}exceeded/i,
|
25
|
+
/anthropic.{0,20}rate.{0,20}limit/i
|
26
|
+
],
|
27
|
+
# OpenAI specific
|
28
|
+
openai: [
|
29
|
+
/rate limit exceeded/i,
|
30
|
+
/requests per minute/i,
|
31
|
+
/tokens per minute/i,
|
32
|
+
/openai.{0,20}rate.{0,20}limit/i
|
33
|
+
],
|
34
|
+
# Google/Gemini specific
|
35
|
+
google: [
|
36
|
+
/quota exceeded/i,
|
37
|
+
/rate limit exceeded/i,
|
38
|
+
/google.{0,20}api.{0,20}limit/i,
|
39
|
+
/gemini.{0,20}rate.{0,20}limit/i
|
40
|
+
],
|
41
|
+
# Cursor specific
|
42
|
+
cursor: [
|
43
|
+
/cursor.{0,20}rate.{0,20}limit/i,
|
44
|
+
/package.{0,20}limit/i,
|
45
|
+
/usage.{0,20}limit/i
|
46
|
+
]
|
47
|
+
}
|
48
|
+
|
49
|
+
# Enhanced user feedback patterns
|
50
|
+
@user_feedback_patterns = {
|
51
|
+
# Direct requests for input
|
52
|
+
direct_requests: [
|
53
|
+
/please provide/i,
|
54
|
+
/can you provide/i,
|
55
|
+
/could you provide/i,
|
56
|
+
/i need.{0,20}input/i,
|
57
|
+
/i require.{0,20}input/i,
|
58
|
+
/please give me/i,
|
59
|
+
/can you give me/i
|
60
|
+
],
|
61
|
+
# Clarification requests
|
62
|
+
clarification: [
|
63
|
+
/can you clarify/i,
|
64
|
+
/could you clarify/i,
|
65
|
+
/please clarify/i,
|
66
|
+
/i need clarification/i,
|
67
|
+
/can you explain/i,
|
68
|
+
/could you explain/i
|
69
|
+
],
|
70
|
+
# Choice/decision requests
|
71
|
+
choices: [
|
72
|
+
/what would you like/i,
|
73
|
+
/what do you prefer/i,
|
74
|
+
/which.{0,20}would you prefer/i,
|
75
|
+
/which.{0,20}do you want/i,
|
76
|
+
/do you want/i,
|
77
|
+
/should i/i,
|
78
|
+
/would you like/i,
|
79
|
+
/which option/i,
|
80
|
+
/choose between/i,
|
81
|
+
/select.{0,20}from/i
|
82
|
+
],
|
83
|
+
# Confirmation requests
|
84
|
+
confirmation: [
|
85
|
+
/is this correct/i,
|
86
|
+
/does this look right/i,
|
87
|
+
/should i proceed/i,
|
88
|
+
/can i continue/i,
|
89
|
+
/is this what you want/i,
|
90
|
+
/confirm.{0,20}this/i,
|
91
|
+
/approve.{0,20}this/i
|
92
|
+
],
|
93
|
+
# File/input requests
|
94
|
+
file_requests: [
|
95
|
+
/please upload/i,
|
96
|
+
/can you upload/i,
|
97
|
+
/i need.{0,20}file/i,
|
98
|
+
/please provide.{0,20}file/i,
|
99
|
+
/attach.{0,20}file/i,
|
100
|
+
/send.{0,20}file/i
|
101
|
+
],
|
102
|
+
# Specific information requests
|
103
|
+
information: [
|
104
|
+
/what is.{0,20}name/i,
|
105
|
+
/what is.{0,20}email/i,
|
106
|
+
/what is.{0,20}url/i,
|
107
|
+
/what is.{0,20}path/i,
|
108
|
+
/enter.{0,20}name/i,
|
109
|
+
/enter.{0,20}email/i,
|
110
|
+
/enter.{0,20}url/i,
|
111
|
+
/enter.{0,20}path/i
|
112
|
+
]
|
113
|
+
}
|
114
|
+
|
115
|
+
# Enhanced question patterns
|
116
|
+
@question_patterns = [
|
117
|
+
# Numbered questions
|
118
|
+
/^\d+\.\s+(.+)\?/,
|
119
|
+
/^(\d+)\)\s+(.+)\?/,
|
120
|
+
/^(\d+\.\s+.+)\?$/m,
|
121
|
+
# Bullet point questions
|
122
|
+
/^[-*]\s+(.+)\?/,
|
123
|
+
# Lettered questions
|
124
|
+
/^[a-z]\)\s+(.+)\?/i,
|
125
|
+
/^[A-Z]\)\s+(.+)\?/,
|
126
|
+
# Questions with colons
|
127
|
+
/^(\d+):\s+(.+)\?/,
|
128
|
+
# Questions in quotes
|
129
|
+
/"([^"]+\?)"/,
|
130
|
+
/'([^']+\?)'/
|
131
|
+
]
|
132
|
+
|
133
|
+
# Context patterns that indicate user interaction is needed
|
134
|
+
@context_patterns = [
|
135
|
+
/waiting for.{0,20}input/i,
|
136
|
+
/awaiting.{0,20}response/i,
|
137
|
+
/need.{0,20}feedback/i,
|
138
|
+
/require.{0,20}confirmation/i,
|
139
|
+
/pending.{0,20}approval/i,
|
140
|
+
/user.{0,20}interaction.{0,20}required/i,
|
141
|
+
/manual.{0,20}intervention/i
|
142
|
+
]
|
143
|
+
|
144
|
+
# Rate limit reset time patterns
|
145
|
+
@reset_time_patterns = [
|
146
|
+
/reset.{0,20}in.{0,20}(\d+).{0,20}seconds/i,
|
147
|
+
/retry.{0,20}after.{0,20}(\d+).{0,20}seconds/i,
|
148
|
+
/wait[^\d]*(\d+)[^\d]*seconds/i,
|
149
|
+
/(\d+).{0,20}seconds.{0,20}until.{0,20}reset/i,
|
150
|
+
/reset.{0,20}at.{0,20}(\d{4}-\d{2}-\d{2}\s+\d{2}:\d{2}:\d{2})/i,
|
151
|
+
/retry.{0,20}after.{0,20}(\d{4}-\d{2}-\d{2}\s+\d{2}:\d{2}:\d{2})/i
|
152
|
+
]
|
153
|
+
end
|
154
|
+
|
155
|
+
# Check if result indicates rate limiting
|
156
|
+
def is_rate_limited?(result, provider = nil)
|
157
|
+
return false unless result.is_a?(Hash)
|
158
|
+
|
159
|
+
# Check HTTP status codes
|
160
|
+
if result[:status_code] == 429 || result[:http_status] == 429
|
161
|
+
return true
|
162
|
+
end
|
163
|
+
|
164
|
+
# Get all text content to check
|
165
|
+
text_content = [
|
166
|
+
result[:error],
|
167
|
+
result[:message],
|
168
|
+
result[:output],
|
169
|
+
result[:response],
|
170
|
+
result[:body]
|
171
|
+
].compact.join(" ")
|
172
|
+
|
173
|
+
return false if text_content.empty?
|
174
|
+
|
175
|
+
# Check provider-specific patterns first
|
176
|
+
if provider && @rate_limit_patterns[provider.to_sym]
|
177
|
+
return true if @rate_limit_patterns[provider.to_sym].any? { |pattern| text_content.match?(pattern) }
|
178
|
+
end
|
179
|
+
|
180
|
+
# Check common patterns
|
181
|
+
@rate_limit_patterns[:common].any? { |pattern| text_content.match?(pattern) }
|
182
|
+
end
|
183
|
+
|
184
|
+
# Extract rate limit information from result
|
185
|
+
def extract_rate_limit_info(result, provider = nil)
|
186
|
+
return nil unless is_rate_limited?(result, provider)
|
187
|
+
|
188
|
+
text_content = [
|
189
|
+
result[:error],
|
190
|
+
result[:message],
|
191
|
+
result[:output],
|
192
|
+
result[:response],
|
193
|
+
result[:body]
|
194
|
+
].compact.join(" ")
|
195
|
+
|
196
|
+
{
|
197
|
+
provider: provider,
|
198
|
+
detected_at: Time.now,
|
199
|
+
reset_time: extract_reset_time(text_content),
|
200
|
+
retry_after: extract_retry_after(text_content),
|
201
|
+
limit_type: detect_limit_type(text_content, provider),
|
202
|
+
message: text_content
|
203
|
+
}
|
204
|
+
end
|
205
|
+
|
206
|
+
# Extract reset time from rate limit message
|
207
|
+
def extract_reset_time(text_content)
|
208
|
+
@reset_time_patterns.each do |pattern|
|
209
|
+
match = text_content.match(pattern)
|
210
|
+
next unless match
|
211
|
+
|
212
|
+
if match[1].match?(/^\d+$/)
|
213
|
+
# Seconds from now
|
214
|
+
Time.now + match[1].to_i
|
215
|
+
else
|
216
|
+
# Specific timestamp
|
217
|
+
begin
|
218
|
+
Time.parse(match[1])
|
219
|
+
rescue ArgumentError
|
220
|
+
nil
|
221
|
+
end
|
222
|
+
end
|
223
|
+
end
|
224
|
+
|
225
|
+
# Default to 60 seconds if no specific time found
|
226
|
+
Time.now + 60
|
227
|
+
end
|
228
|
+
|
229
|
+
# Extract retry-after value
|
230
|
+
def extract_retry_after(text_content)
|
231
|
+
# Look for retry-after header or similar
|
232
|
+
retry_patterns = [
|
233
|
+
/retry\s+after\s+(\d{1,6})/i,
|
234
|
+
/wait\s+(\d{1,6})\s+seconds/i,
|
235
|
+
/(\d{1,6})\s+seconds\s+until/i
|
236
|
+
]
|
237
|
+
|
238
|
+
retry_patterns.each do |pattern|
|
239
|
+
match = text_content.match(pattern)
|
240
|
+
return match[1].to_i if match
|
241
|
+
end
|
242
|
+
|
243
|
+
# Default retry time
|
244
|
+
60
|
245
|
+
end
|
246
|
+
|
247
|
+
# Detect the type of rate limit
|
248
|
+
def detect_limit_type(text_content, provider)
|
249
|
+
case provider&.to_s&.downcase
|
250
|
+
when "anthropic", "claude"
|
251
|
+
if text_content.match?(/requests per minute/i)
|
252
|
+
"requests_per_minute"
|
253
|
+
elsif text_content.match?(/tokens per minute/i)
|
254
|
+
"tokens_per_minute"
|
255
|
+
else
|
256
|
+
"general_rate_limit"
|
257
|
+
end
|
258
|
+
when "openai"
|
259
|
+
if text_content.match?(/requests per minute/i)
|
260
|
+
"requests_per_minute"
|
261
|
+
elsif text_content.match?(/tokens per minute/i)
|
262
|
+
"tokens_per_minute"
|
263
|
+
else
|
264
|
+
"general_rate_limit"
|
265
|
+
end
|
266
|
+
when "google", "gemini"
|
267
|
+
if text_content.match?(/quota exceeded/i)
|
268
|
+
"quota_exceeded"
|
269
|
+
else
|
270
|
+
"general_rate_limit"
|
271
|
+
end
|
272
|
+
when "cursor"
|
273
|
+
if text_content.match?(/package\s+limit/i)
|
274
|
+
"package_limit"
|
275
|
+
elsif text_content.match?(/usage\s+limit/i)
|
276
|
+
"usage_limit"
|
277
|
+
else
|
278
|
+
"general_rate_limit"
|
279
|
+
end
|
280
|
+
else
|
281
|
+
"general_rate_limit"
|
282
|
+
end
|
283
|
+
end
|
284
|
+
|
285
|
+
# Check if result needs user feedback
|
286
|
+
def needs_user_feedback?(result)
|
287
|
+
return false unless result.is_a?(Hash)
|
288
|
+
|
289
|
+
# Get all text content to check
|
290
|
+
text_content = [
|
291
|
+
result[:output],
|
292
|
+
result[:message],
|
293
|
+
result[:response],
|
294
|
+
result[:body]
|
295
|
+
].compact.join(" ")
|
296
|
+
|
297
|
+
return false if text_content.empty?
|
298
|
+
|
299
|
+
# Check for context patterns first
|
300
|
+
return true if @context_patterns.any? { |pattern| text_content.match?(pattern) }
|
301
|
+
|
302
|
+
# Check for any user feedback patterns
|
303
|
+
@user_feedback_patterns.values.flatten.any? { |pattern| text_content.match?(pattern) }
|
304
|
+
end
|
305
|
+
|
306
|
+
# Get detailed user feedback information
|
307
|
+
def extract_user_feedback_info(result)
|
308
|
+
return nil unless needs_user_feedback?(result)
|
309
|
+
|
310
|
+
# Get all text content to analyze
|
311
|
+
text_content = [
|
312
|
+
result[:output],
|
313
|
+
result[:message],
|
314
|
+
result[:response],
|
315
|
+
result[:body]
|
316
|
+
].compact.join(" ")
|
317
|
+
|
318
|
+
{
|
319
|
+
detected_at: Time.now,
|
320
|
+
feedback_type: detect_feedback_type(text_content),
|
321
|
+
questions: extract_questions(result),
|
322
|
+
context: extract_context(text_content),
|
323
|
+
urgency: detect_urgency(text_content),
|
324
|
+
input_type: detect_input_type(text_content)
|
325
|
+
}
|
326
|
+
end
|
327
|
+
|
328
|
+
# Detect the type of feedback needed
|
329
|
+
def detect_feedback_type(text_content)
|
330
|
+
@user_feedback_patterns.each do |type, patterns|
|
331
|
+
if patterns.any? { |pattern| text_content.match?(pattern) }
|
332
|
+
return type.to_s
|
333
|
+
end
|
334
|
+
end
|
335
|
+
|
336
|
+
"general"
|
337
|
+
end
|
338
|
+
|
339
|
+
# Extract context information
|
340
|
+
def extract_context(text_content)
|
341
|
+
context_matches = []
|
342
|
+
@context_patterns.each do |pattern|
|
343
|
+
matches = text_content.scan(pattern)
|
344
|
+
context_matches.concat(matches)
|
345
|
+
end
|
346
|
+
context_matches.uniq
|
347
|
+
end
|
348
|
+
|
349
|
+
# Detect urgency level
|
350
|
+
def detect_urgency(text_content)
|
351
|
+
urgent_patterns = [
|
352
|
+
/urgent/i,
|
353
|
+
/asap/i,
|
354
|
+
/immediately/i,
|
355
|
+
/right now/i,
|
356
|
+
/critical/i,
|
357
|
+
/important/i
|
358
|
+
]
|
359
|
+
|
360
|
+
low_urgency_patterns = [
|
361
|
+
/when you have time/i,
|
362
|
+
/when convenient/i,
|
363
|
+
/at your convenience/i,
|
364
|
+
/when possible/i,
|
365
|
+
/no rush/i,
|
366
|
+
/take your time/i
|
367
|
+
]
|
368
|
+
|
369
|
+
if urgent_patterns.any? { |pattern| text_content.match?(pattern) }
|
370
|
+
"high"
|
371
|
+
elsif low_urgency_patterns.any? { |pattern| text_content.match?(pattern) }
|
372
|
+
"low"
|
373
|
+
elsif text_content.match?(/please/i) || text_content.match?(/can you/i)
|
374
|
+
"medium"
|
375
|
+
else
|
376
|
+
"low"
|
377
|
+
end
|
378
|
+
end
|
379
|
+
|
380
|
+
# Detect the type of input expected
|
381
|
+
def detect_input_type(text_content)
|
382
|
+
if text_content.match?(/file/i) || text_content.match?(/upload/i) || text_content.match?(/attach/i)
|
383
|
+
"file"
|
384
|
+
elsif text_content.match?(/email/i)
|
385
|
+
"email"
|
386
|
+
elsif text_content.match?(/url/i) || text_content.match?(/link/i)
|
387
|
+
"url"
|
388
|
+
elsif text_content.match?(/path/i) || text_content.match?(/directory/i)
|
389
|
+
"path"
|
390
|
+
elsif text_content.match?(/number/i) || text_content.match?(/\d+/) ||
|
391
|
+
text_content.match?(/how many/i) || text_content.match?(/how much/i) ||
|
392
|
+
text_content.match?(/count/i) || text_content.match?(/quantity/i) ||
|
393
|
+
text_content.match?(/amount/i)
|
394
|
+
"number"
|
395
|
+
elsif text_content.match?(/yes/i) || text_content.match?(/no/i) || text_content.match?(/confirm/i) ||
|
396
|
+
text_content.match?(/should i/i) || text_content.match?(/can i/i) || text_content.match?(/may i/i) ||
|
397
|
+
text_content.match?(/proceed/i) || text_content.match?(/continue/i) || text_content.match?(/approve/i)
|
398
|
+
"boolean"
|
399
|
+
else
|
400
|
+
"text"
|
401
|
+
end
|
402
|
+
end
|
403
|
+
|
404
|
+
# Extract questions from result output
|
405
|
+
def extract_questions(result)
|
406
|
+
return [] unless result.is_a?(Hash)
|
407
|
+
|
408
|
+
# Get all text content to analyze
|
409
|
+
text_content = [
|
410
|
+
result[:output],
|
411
|
+
result[:message],
|
412
|
+
result[:response],
|
413
|
+
result[:body]
|
414
|
+
].compact.join(" ")
|
415
|
+
|
416
|
+
return [] if text_content.empty?
|
417
|
+
|
418
|
+
questions = []
|
419
|
+
|
420
|
+
# Extract structured questions using patterns
|
421
|
+
@question_patterns.each do |pattern|
|
422
|
+
matches = text_content.scan(pattern)
|
423
|
+
matches.each do |match|
|
424
|
+
if match.is_a?(Array)
|
425
|
+
if match.length == 2
|
426
|
+
# Pattern with two capture groups (number and question)
|
427
|
+
number = match[0]
|
428
|
+
question = match[1]
|
429
|
+
next if number.nil? || question.nil?
|
430
|
+
|
431
|
+
questions << {
|
432
|
+
number: number,
|
433
|
+
question: question.strip,
|
434
|
+
type: detect_question_type(question),
|
435
|
+
input_type: detect_input_type(question)
|
436
|
+
}
|
437
|
+
elsif match.length == 1
|
438
|
+
# Pattern with one capture group (just question)
|
439
|
+
question = match[0]
|
440
|
+
next if question.nil?
|
441
|
+
|
442
|
+
questions << {
|
443
|
+
question: question.strip,
|
444
|
+
type: detect_question_type(question),
|
445
|
+
input_type: detect_input_type(question)
|
446
|
+
}
|
447
|
+
end
|
448
|
+
else
|
449
|
+
# Single capture group
|
450
|
+
next if match.nil?
|
451
|
+
|
452
|
+
questions << {
|
453
|
+
question: match.strip,
|
454
|
+
type: detect_question_type(match),
|
455
|
+
input_type: detect_input_type(match)
|
456
|
+
}
|
457
|
+
end
|
458
|
+
end
|
459
|
+
end
|
460
|
+
|
461
|
+
# If no structured questions found, look for general questions
|
462
|
+
if questions.empty?
|
463
|
+
general_questions = text_content.scan(/([^.!?]{1,500}\?)/)
|
464
|
+
general_questions.each_with_index do |match, index|
|
465
|
+
question_text = match[0].strip
|
466
|
+
next if question_text.length < 10 # Skip very short questions
|
467
|
+
|
468
|
+
questions << {
|
469
|
+
number: index + 1,
|
470
|
+
question: question_text,
|
471
|
+
type: detect_question_type(question_text),
|
472
|
+
input_type: detect_input_type(question_text)
|
473
|
+
}
|
474
|
+
end
|
475
|
+
end
|
476
|
+
|
477
|
+
# Remove duplicates and clean up
|
478
|
+
questions.uniq { |q| q[:question] }
|
479
|
+
end
|
480
|
+
|
481
|
+
# Detect the type of question
|
482
|
+
def detect_question_type(question_text)
|
483
|
+
question_lower = question_text.downcase
|
484
|
+
|
485
|
+
if question_lower.match?(/what.{0,20}name/i) || question_lower.match?(/what.{0,20}email/i)
|
486
|
+
"information"
|
487
|
+
elsif question_lower.match?(/which.{0,20}prefer/i) || question_lower.match?(/which.{0,20}want/i)
|
488
|
+
"choice"
|
489
|
+
elsif question_lower.match?(/should.{0,20}i/i) || question_lower.match?(/can.{0,20}i/i)
|
490
|
+
"permission"
|
491
|
+
elsif question_lower.match?(/is.{0,20}correct/i) || question_lower.match?(/does.{0,20}look/i)
|
492
|
+
"confirmation"
|
493
|
+
elsif question_lower.match?(/can.{0,20}you/i) || question_lower.match?(/could.{0,20}you/i)
|
494
|
+
"request"
|
495
|
+
elsif question_lower.match?(/how.{0,20}many/i) || question_lower.match?(/how.{0,20}much/i)
|
496
|
+
"quantity"
|
497
|
+
elsif question_lower.match?(/when/i)
|
498
|
+
"time"
|
499
|
+
elsif question_lower.match?(/where/i)
|
500
|
+
"location"
|
501
|
+
elsif question_lower.match?(/why/i)
|
502
|
+
"explanation"
|
503
|
+
else
|
504
|
+
"general"
|
505
|
+
end
|
506
|
+
end
|
507
|
+
|
508
|
+
# Check if work is complete
|
509
|
+
def is_work_complete?(result, progress)
|
510
|
+
return false unless result.is_a?(Hash)
|
511
|
+
|
512
|
+
# Check if all steps are completed
|
513
|
+
if progress&.completed_steps && progress.total_steps &&
|
514
|
+
progress.completed_steps.size == progress.total_steps
|
515
|
+
return true
|
516
|
+
end
|
517
|
+
|
518
|
+
# Get all text content to analyze
|
519
|
+
text_content = [
|
520
|
+
result[:output],
|
521
|
+
result[:message],
|
522
|
+
result[:response],
|
523
|
+
result[:body]
|
524
|
+
].compact.join(" ")
|
525
|
+
|
526
|
+
return false if text_content.empty?
|
527
|
+
|
528
|
+
# Check for completion indicators
|
529
|
+
completion_info = extract_completion_info(result, progress)
|
530
|
+
completion_info[:is_complete]
|
531
|
+
end
|
532
|
+
|
533
|
+
# Extract comprehensive completion information
|
534
|
+
def extract_completion_info(result, progress)
|
535
|
+
# Get all text content to analyze
|
536
|
+
text_content = [
|
537
|
+
result[:output],
|
538
|
+
result[:message],
|
539
|
+
result[:response],
|
540
|
+
result[:body]
|
541
|
+
].compact.join(" ")
|
542
|
+
|
543
|
+
completion_info = {
|
544
|
+
is_complete: false,
|
545
|
+
completion_type: nil,
|
546
|
+
confidence: 0.0,
|
547
|
+
indicators: [],
|
548
|
+
progress_status: nil,
|
549
|
+
next_actions: []
|
550
|
+
}
|
551
|
+
|
552
|
+
# Check progress-based completion
|
553
|
+
if progress&.completed_steps && progress.total_steps &&
|
554
|
+
progress.completed_steps.size == progress.total_steps
|
555
|
+
completion_info[:is_complete] = true
|
556
|
+
completion_info[:completion_type] = "all_steps_completed"
|
557
|
+
completion_info[:confidence] = 1.0
|
558
|
+
completion_info[:progress_status] = "all_steps_completed"
|
559
|
+
return completion_info
|
560
|
+
end
|
561
|
+
|
562
|
+
# Check for explicit completion indicators
|
563
|
+
explicit_completion = detect_explicit_completion(text_content)
|
564
|
+
if explicit_completion[:found]
|
565
|
+
completion_info[:is_complete] = true
|
566
|
+
completion_info[:completion_type] = explicit_completion[:type]
|
567
|
+
completion_info[:confidence] = explicit_completion[:confidence]
|
568
|
+
completion_info[:indicators] = explicit_completion[:indicators]
|
569
|
+
return completion_info
|
570
|
+
end
|
571
|
+
|
572
|
+
# Check for implicit completion indicators
|
573
|
+
implicit_completion = detect_implicit_completion(text_content, progress)
|
574
|
+
if implicit_completion[:found]
|
575
|
+
completion_info[:is_complete] = true
|
576
|
+
completion_info[:completion_type] = implicit_completion[:type]
|
577
|
+
completion_info[:confidence] = implicit_completion[:confidence]
|
578
|
+
completion_info[:indicators] = implicit_completion[:indicators]
|
579
|
+
return completion_info
|
580
|
+
end
|
581
|
+
|
582
|
+
# If no completion indicators found, check for partial completion
|
583
|
+
partial_completion = detect_partial_completion(text_content, progress)
|
584
|
+
completion_info[:progress_status] = partial_completion[:status]
|
585
|
+
completion_info[:next_actions] = partial_completion[:next_actions]
|
586
|
+
|
587
|
+
# Only consider work complete if we have explicit or implicit completion indicators
|
588
|
+
completion_info[:is_complete] = false
|
589
|
+
|
590
|
+
completion_info
|
591
|
+
end
|
592
|
+
|
593
|
+
# Detect explicit completion indicators
|
594
|
+
def detect_explicit_completion(text_content)
|
595
|
+
completion_patterns = {
|
596
|
+
# High confidence completion indicators
|
597
|
+
high_confidence: [
|
598
|
+
/all steps completed/i,
|
599
|
+
/workflow complete/i,
|
600
|
+
/analysis complete/i,
|
601
|
+
/execution finished/i,
|
602
|
+
/task completed/i,
|
603
|
+
/all done/i,
|
604
|
+
/finished successfully/i,
|
605
|
+
/completed successfully/i,
|
606
|
+
/workflow finished/i,
|
607
|
+
/analysis finished/i,
|
608
|
+
/execution completed/i
|
609
|
+
],
|
610
|
+
# Medium confidence completion indicators
|
611
|
+
medium_confidence: [
|
612
|
+
/complete/i,
|
613
|
+
/finished/i,
|
614
|
+
/done/i,
|
615
|
+
/success/i,
|
616
|
+
/ready/i,
|
617
|
+
/final/i
|
618
|
+
],
|
619
|
+
# Low confidence completion indicators
|
620
|
+
low_confidence: [
|
621
|
+
/end/i,
|
622
|
+
/stop/i,
|
623
|
+
/close/i,
|
624
|
+
/finalize/i
|
625
|
+
]
|
626
|
+
}
|
627
|
+
|
628
|
+
found_indicators = []
|
629
|
+
max_confidence = 0.0
|
630
|
+
completion_type = nil
|
631
|
+
|
632
|
+
completion_patterns.each do |confidence_level, patterns|
|
633
|
+
patterns.each do |pattern|
|
634
|
+
if text_content.match?(pattern)
|
635
|
+
found_indicators << pattern.source
|
636
|
+
case confidence_level
|
637
|
+
when :high_confidence
|
638
|
+
if max_confidence < 0.9
|
639
|
+
max_confidence = 0.9
|
640
|
+
completion_type = "explicit_high_confidence"
|
641
|
+
end
|
642
|
+
when :medium_confidence
|
643
|
+
if max_confidence < 0.7
|
644
|
+
max_confidence = 0.7
|
645
|
+
completion_type = "explicit_medium_confidence"
|
646
|
+
end
|
647
|
+
when :low_confidence
|
648
|
+
if max_confidence < 0.5
|
649
|
+
max_confidence = 0.5
|
650
|
+
completion_type = "explicit_low_confidence"
|
651
|
+
end
|
652
|
+
end
|
653
|
+
end
|
654
|
+
end
|
655
|
+
end
|
656
|
+
|
657
|
+
{
|
658
|
+
found: max_confidence > 0.0,
|
659
|
+
type: completion_type,
|
660
|
+
confidence: max_confidence,
|
661
|
+
indicators: found_indicators
|
662
|
+
}
|
663
|
+
end
|
664
|
+
|
665
|
+
# Detect implicit completion indicators
|
666
|
+
def detect_implicit_completion(text_content, progress)
|
667
|
+
# Check for summary or conclusion patterns
|
668
|
+
summary_patterns = [
|
669
|
+
/summary/i,
|
670
|
+
/conclusion/i,
|
671
|
+
/overview/i,
|
672
|
+
/results/i,
|
673
|
+
/findings/i,
|
674
|
+
/recommendations/i,
|
675
|
+
/next steps/i
|
676
|
+
]
|
677
|
+
|
678
|
+
# Check for deliverable patterns
|
679
|
+
deliverable_patterns = [
|
680
|
+
/report generated/i,
|
681
|
+
/document created/i,
|
682
|
+
/file created/i,
|
683
|
+
/output generated/i,
|
684
|
+
/result saved/i,
|
685
|
+
/analysis saved/i
|
686
|
+
]
|
687
|
+
|
688
|
+
# Check for status patterns
|
689
|
+
status_patterns = [
|
690
|
+
/status: complete/i,
|
691
|
+
/status: finished/i,
|
692
|
+
/status: done/i,
|
693
|
+
/phase.{0,20}complete/i,
|
694
|
+
/stage.{0,20}complete/i
|
695
|
+
]
|
696
|
+
|
697
|
+
found_indicators = []
|
698
|
+
max_confidence = 0.0
|
699
|
+
completion_type = nil
|
700
|
+
|
701
|
+
# Check summary patterns
|
702
|
+
if summary_patterns.any? { |pattern| text_content.match?(pattern) }
|
703
|
+
found_indicators << "summary_patterns"
|
704
|
+
max_confidence = [max_confidence, 0.8].max
|
705
|
+
completion_type = "implicit_summary"
|
706
|
+
end
|
707
|
+
|
708
|
+
# Check deliverable patterns
|
709
|
+
if deliverable_patterns.any? { |pattern| text_content.match?(pattern) }
|
710
|
+
found_indicators << "deliverable_patterns"
|
711
|
+
max_confidence = [max_confidence, 0.8].max
|
712
|
+
completion_type = "implicit_deliverable"
|
713
|
+
end
|
714
|
+
|
715
|
+
# Check status patterns
|
716
|
+
if status_patterns.any? { |pattern| text_content.match?(pattern) }
|
717
|
+
found_indicators << "status_patterns"
|
718
|
+
max_confidence = [max_confidence, 0.7].max
|
719
|
+
completion_type = "implicit_status"
|
720
|
+
end
|
721
|
+
|
722
|
+
# Check for progress completion
|
723
|
+
if progress && progress.completed_steps.size > 0
|
724
|
+
completion_ratio = progress.completed_steps.size.to_f / progress.total_steps
|
725
|
+
if completion_ratio >= 0.8 # 80% or more complete
|
726
|
+
found_indicators << "high_progress_ratio"
|
727
|
+
max_confidence = [max_confidence, 0.6].max
|
728
|
+
completion_type = "implicit_high_progress"
|
729
|
+
end
|
730
|
+
end
|
731
|
+
|
732
|
+
{
|
733
|
+
found: max_confidence > 0.0,
|
734
|
+
type: completion_type,
|
735
|
+
confidence: max_confidence,
|
736
|
+
indicators: found_indicators
|
737
|
+
}
|
738
|
+
end
|
739
|
+
|
740
|
+
# Detect partial completion and next actions
|
741
|
+
def detect_partial_completion(text_content, progress)
|
742
|
+
next_actions = []
|
743
|
+
status = "in_progress"
|
744
|
+
|
745
|
+
# Check for next action indicators
|
746
|
+
next_action_patterns = [
|
747
|
+
/next step/i,
|
748
|
+
/next action/i,
|
749
|
+
/continue with/i,
|
750
|
+
/proceed to/i,
|
751
|
+
/move to/i,
|
752
|
+
/now\s+will/i,
|
753
|
+
/next\s+will/i
|
754
|
+
]
|
755
|
+
|
756
|
+
if next_action_patterns.any? { |pattern| text_content.match?(pattern) }
|
757
|
+
status = "has_next_actions"
|
758
|
+
next_actions << "continue_execution"
|
759
|
+
end
|
760
|
+
|
761
|
+
# Check for waiting patterns
|
762
|
+
waiting_patterns = [
|
763
|
+
/waiting for/i,
|
764
|
+
/pending/i,
|
765
|
+
/awaiting/i,
|
766
|
+
/need\s+input/i,
|
767
|
+
/require\s+input/i
|
768
|
+
]
|
769
|
+
|
770
|
+
if waiting_patterns.any? { |pattern| text_content.match?(pattern) }
|
771
|
+
status = "waiting_for_input"
|
772
|
+
next_actions << "collect_user_input"
|
773
|
+
end
|
774
|
+
|
775
|
+
# Check for error patterns
|
776
|
+
error_patterns = [
|
777
|
+
/error/i,
|
778
|
+
/failed/i,
|
779
|
+
/issue/i,
|
780
|
+
/problem/i,
|
781
|
+
/exception/i
|
782
|
+
]
|
783
|
+
|
784
|
+
if error_patterns.any? { |pattern| text_content.match?(pattern) }
|
785
|
+
status = "has_errors"
|
786
|
+
next_actions << "handle_errors"
|
787
|
+
end
|
788
|
+
|
789
|
+
# Check progress status only if no specific status was detected from text
|
790
|
+
if status == "in_progress" && progress
|
791
|
+
completion_ratio = progress.completed_steps.size.to_f / progress.total_steps
|
792
|
+
if completion_ratio >= 0.8
|
793
|
+
status = "near_completion"
|
794
|
+
next_actions << "continue_to_completion"
|
795
|
+
elsif completion_ratio >= 0.5
|
796
|
+
status = "half_complete"
|
797
|
+
next_actions << "continue_execution"
|
798
|
+
elsif completion_ratio >= 0.2
|
799
|
+
status = "early_stage"
|
800
|
+
next_actions << "continue_execution"
|
801
|
+
else
|
802
|
+
status = "just_started"
|
803
|
+
next_actions << "continue_execution"
|
804
|
+
end
|
805
|
+
end
|
806
|
+
|
807
|
+
{
|
808
|
+
status: status,
|
809
|
+
next_actions: next_actions
|
810
|
+
}
|
811
|
+
end
|
812
|
+
|
813
|
+
# Classify error type with comprehensive analysis
|
814
|
+
def classify_error(error)
|
815
|
+
return :unknown unless error.is_a?(StandardError)
|
816
|
+
|
817
|
+
error_info = extract_error_info(error)
|
818
|
+
error_info[:type]
|
819
|
+
end
|
820
|
+
|
821
|
+
# Extract comprehensive error information
|
822
|
+
def extract_error_info(error)
|
823
|
+
return {type: :unknown, severity: :low, recoverable: true} unless error.is_a?(StandardError)
|
824
|
+
|
825
|
+
error_message = error.message.downcase
|
826
|
+
error_class = error.class.name.downcase
|
827
|
+
|
828
|
+
# Get error classification
|
829
|
+
error_type = classify_error_type(error_message, error_class)
|
830
|
+
severity = determine_error_severity(error_type, error_message)
|
831
|
+
recoverable = determine_recoverability(error_type, error_message)
|
832
|
+
retry_strategy = determine_retry_strategy(error_type, error_message)
|
833
|
+
|
834
|
+
{
|
835
|
+
type: error_type,
|
836
|
+
severity: severity,
|
837
|
+
recoverable: recoverable,
|
838
|
+
retry_strategy: retry_strategy,
|
839
|
+
message: error.message,
|
840
|
+
class: error.class.name,
|
841
|
+
backtrace: error.backtrace&.first(5)
|
842
|
+
}
|
843
|
+
end
|
844
|
+
|
845
|
+
# Classify error type based on message and class
|
846
|
+
def classify_error_type(error_message, error_class)
|
847
|
+
# Network and connectivity errors
|
848
|
+
if error_message.match?(/timeout/i) || error_class.include?("timeout")
|
849
|
+
:timeout
|
850
|
+
elsif error_message.match?(/connection/i) || error_message.match?(/network/i) ||
|
851
|
+
error_class.include?("connection") || error_class.include?("network")
|
852
|
+
:network
|
853
|
+
elsif error_message.match?(/dns/i) || error_message.match?(/resolve/i)
|
854
|
+
:dns_resolution
|
855
|
+
elsif error_message.match?(/ssl/i) || error_message.match?(/tls/i) || error_message.match?(/certificate/i)
|
856
|
+
:ssl_tls
|
857
|
+
|
858
|
+
# Authentication and authorization errors
|
859
|
+
elsif error_message.match?(/authentication/i) || error_message.match?(/unauthorized/i) ||
|
860
|
+
error_message.match?(/401/i) || error_class.include?("authentication")
|
861
|
+
:authentication
|
862
|
+
elsif error_message.match?(/permission/i) || error_message.match?(/forbidden/i) ||
|
863
|
+
error_message.match?(/403/i) || error_class.include?("permission")
|
864
|
+
:permission
|
865
|
+
elsif error_message.match?(/access.{0,20}denied/i) || error_message.match?(/insufficient.{0,20}privileges/i)
|
866
|
+
:access_denied
|
867
|
+
|
868
|
+
# File and I/O errors (check these first as they're more specific)
|
869
|
+
elsif error_message.match?(/file.{0,20}not.{0,20}found/i) || error_message.match?(/no.{0,20}such.{0,20}file/i)
|
870
|
+
:file_not_found
|
871
|
+
|
872
|
+
# HTTP and API errors
|
873
|
+
elsif error_message.match?(/not found/i) || error_message.match?(/404/i)
|
874
|
+
:not_found
|
875
|
+
elsif error_message.match?(/server error/i) || error_message.match?(/500/i) ||
|
876
|
+
error_message.match?(/internal.{0,20}error/i)
|
877
|
+
:server_error
|
878
|
+
elsif error_message.match?(/bad request/i) || error_message.match?(/400/i) ||
|
879
|
+
error_message.match?(/invalid.{0,20}request/i)
|
880
|
+
:bad_request
|
881
|
+
elsif error_message.match?(/rate limit/i) || error_message.match?(/429/i) ||
|
882
|
+
error_message.match?(/too many requests/i)
|
883
|
+
:rate_limit
|
884
|
+
elsif error_message.match?(/quota.{0,20}exceeded/i) || error_message.match?(/usage.{0,20}limit/i)
|
885
|
+
:quota_exceeded
|
886
|
+
elsif error_message.match?(/permission.{0,20}denied/i) || error_message.match?(/eacces/i)
|
887
|
+
:file_permission
|
888
|
+
elsif error_message.match?(/disk.{0,20}full/i) || error_message.match?(/no.{0,20}space/i)
|
889
|
+
:disk_full
|
890
|
+
elsif error_message.match?(/read.{0,20}only/i) || error_message.match?(/eacces/i)
|
891
|
+
:read_only_filesystem
|
892
|
+
|
893
|
+
# Memory and resource errors
|
894
|
+
elsif error_message.match?(/memory/i) || error_message.match?(/out of memory/i) ||
|
895
|
+
error_class.include?("memory")
|
896
|
+
:memory_error
|
897
|
+
elsif error_message.match?(/resource.{0,20}unavailable/i) || error_message.match?(/resource.{0,20}exhausted/i)
|
898
|
+
:resource_exhausted
|
899
|
+
|
900
|
+
# Configuration and setup errors
|
901
|
+
elsif error_message.match?(/configuration/i) || error_message.match?(/config/i) ||
|
902
|
+
error_class.include?("configuration")
|
903
|
+
:configuration
|
904
|
+
elsif error_message.match?(/missing.{0,20}dependency/i) || error_message.match?(/gem.{0,20}not found/i)
|
905
|
+
:missing_dependency
|
906
|
+
elsif error_message.match?(/environment/i) || error_message.match?(/env/i)
|
907
|
+
:environment
|
908
|
+
|
909
|
+
# Provider-specific errors
|
910
|
+
elsif error_message.match?(/anthropic/i) || error_message.match?(/claude/i)
|
911
|
+
:anthropic_error
|
912
|
+
elsif error_message.match?(/openai/i) || error_message.match?(/gpt/i)
|
913
|
+
:openai_error
|
914
|
+
elsif error_message.match?(/google/i) || error_message.match?(/gemini/i)
|
915
|
+
:google_error
|
916
|
+
elsif error_message.match?(/cursor/i)
|
917
|
+
:cursor_error
|
918
|
+
|
919
|
+
# Parsing and format errors
|
920
|
+
elsif error_message.match?(/parse/i) || error_message.match?(/json/i) ||
|
921
|
+
error_message.match?(/syntax/i) || error_class.include?("parse")
|
922
|
+
:parsing_error
|
923
|
+
elsif error_message.match?(/format/i) || error_message.match?(/invalid.{0,20}format/i)
|
924
|
+
:format_error
|
925
|
+
|
926
|
+
# Validation errors
|
927
|
+
elsif error_message.match?(/validation/i) || error_message.match?(/invalid.{0,20}input/i) ||
|
928
|
+
error_class.include?("validation")
|
929
|
+
:validation_error
|
930
|
+
elsif error_message.match?(/argument/i) || error_message.match?(/parameter/i)
|
931
|
+
:argument_error
|
932
|
+
|
933
|
+
# System errors
|
934
|
+
elsif error_message.match?(/system/i) || error_class.include?("system")
|
935
|
+
:system_error
|
936
|
+
elsif error_message.match?(/interrupt/i) || error_message.match?(/sigint/i) ||
|
937
|
+
error_class.include?("interrupt")
|
938
|
+
:interrupted
|
939
|
+
|
940
|
+
else
|
941
|
+
:unknown
|
942
|
+
end
|
943
|
+
end
|
944
|
+
|
945
|
+
# Determine error severity
|
946
|
+
def determine_error_severity(error_type, _error_message)
|
947
|
+
case error_type
|
948
|
+
when :authentication, :permission, :access_denied, :configuration, :missing_dependency
|
949
|
+
:critical
|
950
|
+
when :rate_limit, :quota_exceeded, :disk_full, :memory_error, :resource_exhausted
|
951
|
+
:high
|
952
|
+
when :timeout, :network, :dns_resolution, :ssl_tls, :server_error, :bad_request
|
953
|
+
:medium
|
954
|
+
when :not_found, :file_not_found, :file_permission, :read_only_filesystem
|
955
|
+
:medium
|
956
|
+
when :parsing_error, :format_error, :validation_error, :argument_error
|
957
|
+
:low
|
958
|
+
when :interrupted, :system_error
|
959
|
+
:high
|
960
|
+
else
|
961
|
+
:medium
|
962
|
+
end
|
963
|
+
end
|
964
|
+
|
965
|
+
# Determine if error is recoverable
|
966
|
+
def determine_recoverability(error_type, _error_message)
|
967
|
+
case error_type
|
968
|
+
when :authentication, :permission, :access_denied, :configuration, :missing_dependency
|
969
|
+
false
|
970
|
+
when :rate_limit, :quota_exceeded, :timeout, :network, :dns_resolution, :ssl_tls
|
971
|
+
true
|
972
|
+
when :server_error, :bad_request, :not_found, :file_not_found
|
973
|
+
true
|
974
|
+
when :disk_full, :memory_error, :resource_exhausted
|
975
|
+
false
|
976
|
+
when :file_permission, :read_only_filesystem
|
977
|
+
false
|
978
|
+
when :parsing_error, :format_error, :validation_error, :argument_error
|
979
|
+
true
|
980
|
+
when :interrupted, :system_error
|
981
|
+
false
|
982
|
+
else
|
983
|
+
# Unknown errors are considered recoverable with caution
|
984
|
+
true
|
985
|
+
end
|
986
|
+
end
|
987
|
+
|
988
|
+
# Determine retry strategy
|
989
|
+
def determine_retry_strategy(error_type, _error_message)
|
990
|
+
case error_type
|
991
|
+
when :timeout, :network, :dns_resolution, :ssl_tls
|
992
|
+
{strategy: :exponential_backoff, max_retries: 3, base_delay: 5}
|
993
|
+
when :rate_limit, :quota_exceeded
|
994
|
+
{strategy: :fixed_delay, max_retries: 2, delay: 60}
|
995
|
+
when :server_error, :bad_request
|
996
|
+
{strategy: :exponential_backoff, max_retries: 2, base_delay: 10}
|
997
|
+
when :not_found, :file_not_found
|
998
|
+
{strategy: :no_retry, max_retries: 0, delay: 0}
|
999
|
+
when :parsing_error, :format_error, :validation_error, :argument_error
|
1000
|
+
{strategy: :no_retry, max_retries: 0, delay: 0}
|
1001
|
+
when :authentication, :permission, :access_denied, :configuration, :missing_dependency
|
1002
|
+
{strategy: :no_retry, max_retries: 0, delay: 0}
|
1003
|
+
when :disk_full, :memory_error, :resource_exhausted
|
1004
|
+
{strategy: :no_retry, max_retries: 0, delay: 0}
|
1005
|
+
when :interrupted, :system_error
|
1006
|
+
{strategy: :no_retry, max_retries: 0, delay: 0}
|
1007
|
+
else
|
1008
|
+
{strategy: :exponential_backoff, max_retries: 1, base_delay: 5}
|
1009
|
+
end
|
1010
|
+
end
|
1011
|
+
|
1012
|
+
# Check if error is recoverable
|
1013
|
+
def recoverable_error?(error)
|
1014
|
+
error_info = extract_error_info(error)
|
1015
|
+
error_info[:recoverable]
|
1016
|
+
end
|
1017
|
+
|
1018
|
+
# Get retry delay for error type
|
1019
|
+
def retry_delay_for_error(error, attempt_number)
|
1020
|
+
error_info = extract_error_info(error)
|
1021
|
+
retry_strategy = error_info[:retry_strategy]
|
1022
|
+
|
1023
|
+
case retry_strategy[:strategy]
|
1024
|
+
when :exponential_backoff
|
1025
|
+
retry_strategy[:base_delay] * (2**(attempt_number - 1))
|
1026
|
+
when :fixed_delay
|
1027
|
+
retry_strategy[:delay]
|
1028
|
+
when :no_retry
|
1029
|
+
0
|
1030
|
+
else
|
1031
|
+
5 # Default delay
|
1032
|
+
end
|
1033
|
+
end
|
1034
|
+
|
1035
|
+
# Get maximum retries for error type
|
1036
|
+
def max_retries_for_error(error)
|
1037
|
+
error_info = extract_error_info(error)
|
1038
|
+
error_info[:retry_strategy][:max_retries]
|
1039
|
+
end
|
1040
|
+
|
1041
|
+
# Get error severity
|
1042
|
+
def get_error_severity(error)
|
1043
|
+
error_info = extract_error_info(error)
|
1044
|
+
error_info[:severity]
|
1045
|
+
end
|
1046
|
+
|
1047
|
+
# Check if error is high severity
|
1048
|
+
def high_severity_error?(error)
|
1049
|
+
[:critical, :high].include?(get_error_severity(error))
|
1050
|
+
end
|
1051
|
+
|
1052
|
+
# Get error description
|
1053
|
+
def get_error_description(error)
|
1054
|
+
error_info = extract_error_info(error)
|
1055
|
+
error_type = error_info[:type]
|
1056
|
+
|
1057
|
+
case error_type
|
1058
|
+
when :timeout
|
1059
|
+
"Request timed out"
|
1060
|
+
when :network
|
1061
|
+
"Network connection error"
|
1062
|
+
when :dns_resolution
|
1063
|
+
"DNS resolution failed"
|
1064
|
+
when :ssl_tls
|
1065
|
+
"SSL/TLS connection error"
|
1066
|
+
when :authentication
|
1067
|
+
"Authentication failed"
|
1068
|
+
when :permission
|
1069
|
+
"Permission denied"
|
1070
|
+
when :access_denied
|
1071
|
+
"Access denied"
|
1072
|
+
when :not_found
|
1073
|
+
"Resource not found"
|
1074
|
+
when :server_error
|
1075
|
+
"Server error"
|
1076
|
+
when :bad_request
|
1077
|
+
"Bad request"
|
1078
|
+
when :rate_limit
|
1079
|
+
"Rate limit exceeded"
|
1080
|
+
when :quota_exceeded
|
1081
|
+
"Quota exceeded"
|
1082
|
+
when :file_not_found
|
1083
|
+
"File not found"
|
1084
|
+
when :file_permission
|
1085
|
+
"File permission denied"
|
1086
|
+
when :disk_full
|
1087
|
+
"Disk full"
|
1088
|
+
when :read_only_filesystem
|
1089
|
+
"Read-only filesystem"
|
1090
|
+
when :memory_error
|
1091
|
+
"Memory error"
|
1092
|
+
when :resource_exhausted
|
1093
|
+
"Resource exhausted"
|
1094
|
+
when :configuration
|
1095
|
+
"Configuration error"
|
1096
|
+
when :missing_dependency
|
1097
|
+
"Missing dependency"
|
1098
|
+
when :environment
|
1099
|
+
"Environment error"
|
1100
|
+
when :anthropic_error
|
1101
|
+
"Anthropic API error"
|
1102
|
+
when :openai_error
|
1103
|
+
"OpenAI API error"
|
1104
|
+
when :google_error
|
1105
|
+
"Google API error"
|
1106
|
+
when :cursor_error
|
1107
|
+
"Cursor API error"
|
1108
|
+
when :parsing_error
|
1109
|
+
"Parsing error"
|
1110
|
+
when :format_error
|
1111
|
+
"Format error"
|
1112
|
+
when :validation_error
|
1113
|
+
"Validation error"
|
1114
|
+
when :argument_error
|
1115
|
+
"Argument error"
|
1116
|
+
when :system_error
|
1117
|
+
"System error"
|
1118
|
+
when :interrupted
|
1119
|
+
"Operation interrupted"
|
1120
|
+
else
|
1121
|
+
"Unknown error"
|
1122
|
+
end
|
1123
|
+
end
|
1124
|
+
|
1125
|
+
# Get error recovery suggestions
|
1126
|
+
def get_error_recovery_suggestions(error)
|
1127
|
+
error_info = extract_error_info(error)
|
1128
|
+
error_type = error_info[:type]
|
1129
|
+
|
1130
|
+
case error_type
|
1131
|
+
when :timeout, :network, :dns_resolution, :ssl_tls
|
1132
|
+
["Check network connection", "Retry the operation", "Check firewall settings"]
|
1133
|
+
when :authentication, :permission, :access_denied
|
1134
|
+
["Check credentials", "Verify permissions", "Contact administrator"]
|
1135
|
+
when :rate_limit, :quota_exceeded
|
1136
|
+
["Wait before retrying", "Check usage limits", "Consider upgrading plan"]
|
1137
|
+
when :file_not_found, :file_permission
|
1138
|
+
["Check file path", "Verify file permissions", "Ensure file exists"]
|
1139
|
+
when :disk_full, :memory_error, :resource_exhausted
|
1140
|
+
["Free up disk space", "Increase memory", "Check system resources"]
|
1141
|
+
when :configuration, :missing_dependency, :environment
|
1142
|
+
["Check configuration", "Install missing dependencies", "Verify environment setup"]
|
1143
|
+
when :parsing_error, :format_error, :validation_error, :argument_error
|
1144
|
+
["Check input format", "Validate parameters", "Review data structure"]
|
1145
|
+
when :server_error, :bad_request
|
1146
|
+
["Retry the operation", "Check request format", "Contact service provider"]
|
1147
|
+
else
|
1148
|
+
["Review error details", "Check logs", "Contact support"]
|
1149
|
+
end
|
1150
|
+
end
|
1151
|
+
|
1152
|
+
# Check if a provider is currently rate limited
|
1153
|
+
def is_provider_rate_limited?(provider, rate_limit_info)
|
1154
|
+
return false unless rate_limit_info && rate_limit_info[:provider] == provider
|
1155
|
+
|
1156
|
+
# Check if the rate limit has expired
|
1157
|
+
if rate_limit_info[:reset_time] && rate_limit_info[:reset_time] > Time.now
|
1158
|
+
return true
|
1159
|
+
end
|
1160
|
+
|
1161
|
+
false
|
1162
|
+
end
|
1163
|
+
|
1164
|
+
# Get time until rate limit resets
|
1165
|
+
def time_until_reset(rate_limit_info)
|
1166
|
+
return 0 unless rate_limit_info && rate_limit_info[:reset_time]
|
1167
|
+
|
1168
|
+
reset_time = rate_limit_info[:reset_time]
|
1169
|
+
remaining = reset_time - Time.now
|
1170
|
+
[remaining, 0].max
|
1171
|
+
end
|
1172
|
+
|
1173
|
+
# Check if rate limit has expired
|
1174
|
+
def rate_limit_expired?(rate_limit_info)
|
1175
|
+
return true unless rate_limit_info && rate_limit_info[:reset_time]
|
1176
|
+
|
1177
|
+
rate_limit_info[:reset_time] <= Time.now
|
1178
|
+
end
|
1179
|
+
|
1180
|
+
# Get all available rate limit patterns for a provider
|
1181
|
+
def get_rate_limit_patterns(provider = nil)
|
1182
|
+
if provider && @rate_limit_patterns[provider.to_sym]
|
1183
|
+
@rate_limit_patterns[:common] + @rate_limit_patterns[provider.to_sym]
|
1184
|
+
else
|
1185
|
+
@rate_limit_patterns[:common]
|
1186
|
+
end
|
1187
|
+
end
|
1188
|
+
|
1189
|
+
# Check if operation has timed out
|
1190
|
+
def is_timeout?(result, start_time, timeout_duration = nil)
|
1191
|
+
return false unless result.is_a?(Hash) && start_time.is_a?(Time)
|
1192
|
+
|
1193
|
+
# Check for explicit timeout indicators in result
|
1194
|
+
if has_timeout_indicators?(result)
|
1195
|
+
return true
|
1196
|
+
end
|
1197
|
+
|
1198
|
+
# Check if operation duration exceeds timeout
|
1199
|
+
if timeout_duration && (Time.now - start_time) > timeout_duration
|
1200
|
+
return true
|
1201
|
+
end
|
1202
|
+
|
1203
|
+
false
|
1204
|
+
end
|
1205
|
+
|
1206
|
+
# Check for timeout indicators in result
|
1207
|
+
def has_timeout_indicators?(result)
|
1208
|
+
return false unless result.is_a?(Hash)
|
1209
|
+
|
1210
|
+
# Get all text content to analyze
|
1211
|
+
text_content = [
|
1212
|
+
result[:output],
|
1213
|
+
result[:message],
|
1214
|
+
result[:response],
|
1215
|
+
result[:body],
|
1216
|
+
result[:error]
|
1217
|
+
].compact.join(" ")
|
1218
|
+
|
1219
|
+
return false if text_content.empty?
|
1220
|
+
|
1221
|
+
# Check for timeout patterns
|
1222
|
+
timeout_patterns = [
|
1223
|
+
/timeout/i,
|
1224
|
+
/timed out/i,
|
1225
|
+
/time.{0,20}out/i,
|
1226
|
+
/request.{0,20}timeout/i,
|
1227
|
+
/connection.{0,20}timeout/i,
|
1228
|
+
/read.{0,20}timeout/i,
|
1229
|
+
/write.{0,20}timeout/i,
|
1230
|
+
/operation.{0,20}timeout/i,
|
1231
|
+
/execution.{0,20}timeout/i,
|
1232
|
+
/deadline.{0,20}exceeded/i,
|
1233
|
+
/time.{0,20}limit.{0,20}exceeded/i,
|
1234
|
+
/time.{0,20}expired/i
|
1235
|
+
]
|
1236
|
+
|
1237
|
+
timeout_patterns.any? { |pattern| text_content.match?(pattern) }
|
1238
|
+
end
|
1239
|
+
|
1240
|
+
# Extract timeout information from result
|
1241
|
+
def extract_timeout_info(result, start_time, timeout_duration = nil)
|
1242
|
+
timeout_info = {
|
1243
|
+
is_timeout: false,
|
1244
|
+
timeout_type: nil,
|
1245
|
+
duration: nil,
|
1246
|
+
timeout_duration: timeout_duration,
|
1247
|
+
exceeded_by: nil,
|
1248
|
+
indicators: []
|
1249
|
+
}
|
1250
|
+
|
1251
|
+
return timeout_info unless result.is_a?(Hash) && start_time.is_a?(Time)
|
1252
|
+
|
1253
|
+
# Check for explicit timeout indicators
|
1254
|
+
if has_timeout_indicators?(result)
|
1255
|
+
timeout_info[:is_timeout] = true
|
1256
|
+
timeout_info[:timeout_type] = "explicit"
|
1257
|
+
timeout_info[:indicators] = extract_timeout_indicators(result)
|
1258
|
+
end
|
1259
|
+
|
1260
|
+
# Check for duration-based timeout
|
1261
|
+
if timeout_duration
|
1262
|
+
duration = Time.now - start_time
|
1263
|
+
timeout_info[:duration] = duration
|
1264
|
+
|
1265
|
+
if duration > timeout_duration
|
1266
|
+
timeout_info[:is_timeout] = true
|
1267
|
+
timeout_info[:timeout_type] = "duration"
|
1268
|
+
timeout_info[:exceeded_by] = duration - timeout_duration
|
1269
|
+
end
|
1270
|
+
end
|
1271
|
+
|
1272
|
+
timeout_info
|
1273
|
+
end
|
1274
|
+
|
1275
|
+
# Extract timeout indicators from result
|
1276
|
+
def extract_timeout_indicators(result)
|
1277
|
+
text_content = [
|
1278
|
+
result[:output],
|
1279
|
+
result[:message],
|
1280
|
+
result[:response],
|
1281
|
+
result[:body],
|
1282
|
+
result[:error]
|
1283
|
+
].compact.join(" ")
|
1284
|
+
|
1285
|
+
timeout_patterns = [
|
1286
|
+
/timeout/i,
|
1287
|
+
/timed out/i,
|
1288
|
+
/time.{0,20}out/i,
|
1289
|
+
/request.{0,20}timeout/i,
|
1290
|
+
/connection.{0,20}timeout/i,
|
1291
|
+
/read.{0,20}timeout/i,
|
1292
|
+
/write.{0,20}timeout/i,
|
1293
|
+
/operation.{0,20}timeout/i,
|
1294
|
+
/execution.{0,20}timeout/i,
|
1295
|
+
/deadline.{0,20}exceeded/i,
|
1296
|
+
/time.{0,20}limit.{0,20}exceeded/i,
|
1297
|
+
/time.{0,20}expired/i
|
1298
|
+
]
|
1299
|
+
|
1300
|
+
found_indicators = []
|
1301
|
+
timeout_patterns.each do |pattern|
|
1302
|
+
if text_content.match?(pattern)
|
1303
|
+
found_indicators << pattern.source
|
1304
|
+
end
|
1305
|
+
end
|
1306
|
+
|
1307
|
+
found_indicators
|
1308
|
+
end
|
1309
|
+
|
1310
|
+
# Get timeout duration for operation type
|
1311
|
+
def get_timeout_duration(operation_type, configuration = nil)
|
1312
|
+
default_timeouts = {
|
1313
|
+
analyze: 300, # 5 minutes
|
1314
|
+
execute: 600, # 10 minutes
|
1315
|
+
provider_call: 120, # 2 minutes
|
1316
|
+
file_operation: 30, # 30 seconds
|
1317
|
+
network_request: 60, # 1 minute
|
1318
|
+
user_input: 300, # 5 minutes
|
1319
|
+
default: 120 # 2 minutes
|
1320
|
+
}
|
1321
|
+
|
1322
|
+
# Get timeout from configuration if available
|
1323
|
+
if configuration && configuration[:timeouts] && configuration[:timeouts][operation_type]
|
1324
|
+
return configuration[:timeouts][operation_type]
|
1325
|
+
end
|
1326
|
+
|
1327
|
+
# Return default timeout for operation type
|
1328
|
+
default_timeouts[operation_type] || default_timeouts[:default]
|
1329
|
+
end
|
1330
|
+
|
1331
|
+
# Get time remaining until timeout
|
1332
|
+
def time_until_timeout(start_time, timeout_duration)
|
1333
|
+
return 0 unless start_time.is_a?(Time) && timeout_duration
|
1334
|
+
|
1335
|
+
elapsed = Time.now - start_time
|
1336
|
+
remaining = timeout_duration - elapsed
|
1337
|
+
[remaining, 0].max
|
1338
|
+
end
|
1339
|
+
|
1340
|
+
# Get timeout status description
|
1341
|
+
def get_timeout_status_description(timeout_info)
|
1342
|
+
return "No timeout" unless timeout_info && timeout_info[:is_timeout]
|
1343
|
+
|
1344
|
+
case timeout_info[:timeout_type]
|
1345
|
+
when "explicit"
|
1346
|
+
"Operation timed out (explicit timeout detected)"
|
1347
|
+
when "duration"
|
1348
|
+
if timeout_info[:exceeded_by]
|
1349
|
+
"Operation timed out (exceeded by #{timeout_info[:exceeded_by].round(2)}s)"
|
1350
|
+
else
|
1351
|
+
"Operation timed out (duration exceeded)"
|
1352
|
+
end
|
1353
|
+
else
|
1354
|
+
"Operation timed out"
|
1355
|
+
end
|
1356
|
+
end
|
1357
|
+
|
1358
|
+
# Get timeout recovery suggestions
|
1359
|
+
def get_timeout_recovery_suggestions(timeout_info, operation_type = nil)
|
1360
|
+
suggestions = []
|
1361
|
+
|
1362
|
+
case timeout_info[:timeout_type]
|
1363
|
+
when "explicit"
|
1364
|
+
suggestions << "Check network connection"
|
1365
|
+
suggestions << "Verify service availability"
|
1366
|
+
suggestions << "Retry with longer timeout"
|
1367
|
+
when "duration"
|
1368
|
+
suggestions << "Increase timeout duration"
|
1369
|
+
suggestions << "Optimize operation performance"
|
1370
|
+
suggestions << "Break operation into smaller chunks"
|
1371
|
+
end
|
1372
|
+
|
1373
|
+
# Add operation-specific suggestions
|
1374
|
+
case operation_type
|
1375
|
+
when :analyze
|
1376
|
+
suggestions << "Reduce analysis scope"
|
1377
|
+
suggestions << "Use incremental analysis"
|
1378
|
+
when :execute
|
1379
|
+
suggestions << "Break execution into smaller steps"
|
1380
|
+
suggestions << "Optimize execution performance"
|
1381
|
+
when :provider_call
|
1382
|
+
suggestions << "Check provider status"
|
1383
|
+
suggestions << "Try different provider"
|
1384
|
+
when :file_operation
|
1385
|
+
suggestions << "Check file system performance"
|
1386
|
+
suggestions << "Verify file permissions"
|
1387
|
+
when :network_request
|
1388
|
+
suggestions << "Check network connectivity"
|
1389
|
+
suggestions << "Verify endpoint availability"
|
1390
|
+
end
|
1391
|
+
|
1392
|
+
suggestions.uniq
|
1393
|
+
end
|
1394
|
+
|
1395
|
+
# Validate user response based on expected input type
|
1396
|
+
def validate_user_response(response, expected_input_type)
|
1397
|
+
return false if response.nil? || response.strip.empty?
|
1398
|
+
|
1399
|
+
case expected_input_type
|
1400
|
+
when "email"
|
1401
|
+
response.match?(/\A[\w+\-.]+@[a-z\d-]+(\.[a-z\d-]+)*\.[a-z]+\z/i)
|
1402
|
+
when "url"
|
1403
|
+
response.match?(/\Ahttps?:\/\/.+/i)
|
1404
|
+
when "number"
|
1405
|
+
response.match?(/^\d+$/)
|
1406
|
+
when "boolean"
|
1407
|
+
response.match?(/^(yes|no|true|false|y|n|1|0)$/i)
|
1408
|
+
when "file"
|
1409
|
+
response.match?(/^@/) || File.exist?(response)
|
1410
|
+
when "path"
|
1411
|
+
File.exist?(response) || Dir.exist?(response)
|
1412
|
+
else
|
1413
|
+
# For text input, just check it's not empty
|
1414
|
+
!response.strip.empty?
|
1415
|
+
end
|
1416
|
+
end
|
1417
|
+
|
1418
|
+
# Get user feedback patterns for a specific type
|
1419
|
+
def get_user_feedback_patterns(feedback_type = nil)
|
1420
|
+
if feedback_type && @user_feedback_patterns[feedback_type.to_sym]
|
1421
|
+
@user_feedback_patterns[feedback_type.to_sym]
|
1422
|
+
else
|
1423
|
+
@user_feedback_patterns.values.flatten
|
1424
|
+
end
|
1425
|
+
end
|
1426
|
+
|
1427
|
+
# Get completion confidence level
|
1428
|
+
def get_completion_confidence(completion_info)
|
1429
|
+
return 0.0 unless completion_info && completion_info[:confidence]
|
1430
|
+
|
1431
|
+
completion_info[:confidence]
|
1432
|
+
end
|
1433
|
+
|
1434
|
+
# Check if completion is high confidence
|
1435
|
+
def high_confidence_completion?(completion_info)
|
1436
|
+
get_completion_confidence(completion_info) >= 0.8
|
1437
|
+
end
|
1438
|
+
|
1439
|
+
# Check if completion is medium confidence
|
1440
|
+
def medium_confidence_completion?(completion_info)
|
1441
|
+
confidence = get_completion_confidence(completion_info)
|
1442
|
+
confidence >= 0.5 && confidence < 0.8
|
1443
|
+
end
|
1444
|
+
|
1445
|
+
# Check if completion is low confidence
|
1446
|
+
def low_confidence_completion?(completion_info)
|
1447
|
+
confidence = get_completion_confidence(completion_info)
|
1448
|
+
confidence > 0.0 && confidence < 0.5
|
1449
|
+
end
|
1450
|
+
|
1451
|
+
# Get next actions from completion info
|
1452
|
+
def get_next_actions(completion_info)
|
1453
|
+
return [] unless completion_info && completion_info[:next_actions]
|
1454
|
+
|
1455
|
+
completion_info[:next_actions]
|
1456
|
+
end
|
1457
|
+
|
1458
|
+
# Check if work is in progress
|
1459
|
+
def is_work_in_progress?(completion_info)
|
1460
|
+
return false unless completion_info
|
1461
|
+
|
1462
|
+
!completion_info[:is_complete] &&
|
1463
|
+
completion_info[:progress_status] != "waiting_for_input" &&
|
1464
|
+
completion_info[:progress_status] != "has_errors"
|
1465
|
+
end
|
1466
|
+
|
1467
|
+
# Check if work is waiting for input
|
1468
|
+
def is_waiting_for_input?(completion_info)
|
1469
|
+
return false unless completion_info
|
1470
|
+
|
1471
|
+
if completion_info[:progress_status] == "waiting_for_input"
|
1472
|
+
return true
|
1473
|
+
end
|
1474
|
+
|
1475
|
+
if completion_info[:next_actions]&.include?("collect_user_input")
|
1476
|
+
return true
|
1477
|
+
end
|
1478
|
+
|
1479
|
+
false
|
1480
|
+
end
|
1481
|
+
|
1482
|
+
# Check if work has errors
|
1483
|
+
def has_errors?(completion_info)
|
1484
|
+
return false unless completion_info
|
1485
|
+
|
1486
|
+
if completion_info[:progress_status] == "has_errors"
|
1487
|
+
return true
|
1488
|
+
end
|
1489
|
+
|
1490
|
+
if completion_info[:next_actions]&.include?("handle_errors")
|
1491
|
+
return true
|
1492
|
+
end
|
1493
|
+
|
1494
|
+
false
|
1495
|
+
end
|
1496
|
+
|
1497
|
+
# Get progress status description
|
1498
|
+
def get_progress_status_description(completion_info)
|
1499
|
+
return "unknown" unless completion_info
|
1500
|
+
|
1501
|
+
case completion_info[:progress_status]
|
1502
|
+
when "all_steps_completed"
|
1503
|
+
"All steps completed successfully"
|
1504
|
+
when "near_completion"
|
1505
|
+
"Near completion (80%+ done)"
|
1506
|
+
when "half_complete"
|
1507
|
+
"Half complete (50%+ done)"
|
1508
|
+
when "early_stage"
|
1509
|
+
"Early stage (20%+ done)"
|
1510
|
+
when "just_started"
|
1511
|
+
"Just started (0-20% done)"
|
1512
|
+
when "has_next_actions"
|
1513
|
+
"Has next actions to perform"
|
1514
|
+
when "waiting_for_input"
|
1515
|
+
"Waiting for user input"
|
1516
|
+
when "has_errors"
|
1517
|
+
"Has errors that need attention"
|
1518
|
+
when "in_progress"
|
1519
|
+
"Work in progress"
|
1520
|
+
else
|
1521
|
+
"Status unknown"
|
1522
|
+
end
|
1523
|
+
end
|
1524
|
+
end
|
1525
|
+
end
|
1526
|
+
end
|