@@ -25,6 +25,9 @@ class ClickCoordinator: ObservableObject {
2525 /// Elapsed time manager for real-time tracking
2626 private let timeManager = ElapsedTimeManager . shared
2727
28+ /// Error recovery manager for handling failures
29+ private let errorRecoveryManager = ErrorRecoveryManager ( )
30+
2831 /// Elapsed time since automation started (legacy compatibility)
2932 var elapsedTime : TimeInterval {
3033 return timeManager. currentSessionTime
@@ -237,6 +240,18 @@ class ClickCoordinator: ObservableObject {
237240 )
238241 }
239242
243+ /// Gets current error recovery manager for UI access
244+ /// - Returns: Current error recovery manager
245+ var errorRecovery : ErrorRecoveryManager {
246+ return errorRecoveryManager
247+ }
248+
249+ /// Gets recovery statistics for display
250+ /// - Returns: Current recovery statistics
251+ func getRecoveryStatistics( ) -> RecoveryStatistics {
252+ return errorRecoveryManager. getRecoveryStatistics ( )
253+ }
254+
240255 // MARK: - Private Methods
241256
242257 /// Runs the main automation loop
@@ -287,7 +302,7 @@ class ClickCoordinator: ObservableObject {
287302 }
288303 }
289304
290- /// Executes a single automation step
305+ /// Executes a single automation step with error recovery
291306 /// - Parameter configuration: Automation configuration
292307 /// - Returns: Result of the automation step
293308 private func executeAutomationStep( configuration: AutomationConfiguration ) async -> ClickResult {
@@ -315,30 +330,131 @@ class ClickCoordinator: ObservableObject {
315330
316331 print ( " ClickCoordinator: Executing automation step at \( location) (dynamic: \( configuration. useDynamicMouseTracking) ) " )
317332
318- // Perform the actual click
333+ // Perform the actual click with error recovery
319334 print ( " ClickCoordinator: Performing actual click at \( location) " )
320- let result : ClickResult
321-
322- if let targetApp = configuration. targetApplication {
323- result = await performBackgroundClick (
324- bundleIdentifier: targetApp,
325- at: location,
326- clickType: configuration. clickType
327- )
328- } else {
329- let config = ClickConfiguration (
330- type: configuration. clickType,
331- location: location,
332- targetPID: nil
333- )
334- result = await performSingleClick ( configuration: config)
335- }
335+ let result = await executeClickWithRecovery (
336+ location: location,
337+ configuration: configuration
338+ )
336339
337340 print ( " ClickCoordinator: Click result: success= \( result. success) " )
338341
339342 return result
340343 }
341344
345+ /// Executes a click with integrated error recovery
346+ /// - Parameters:
347+ /// - location: Location to click
348+ /// - configuration: Automation configuration
349+ /// - Returns: Result of the click operation with recovery attempts
350+ private func executeClickWithRecovery(
351+ location: CGPoint ,
352+ configuration: AutomationConfiguration
353+ ) async -> ClickResult {
354+ let clickConfig = ClickConfiguration (
355+ type: configuration. clickType,
356+ location: location,
357+ targetPID: nil
358+ )
359+
360+ var attemptCount = 0
361+ let maxAttempts = 3
362+
363+ while attemptCount < maxAttempts {
364+ let result : ClickResult
365+
366+ // Perform the click
367+ if let targetApp = configuration. targetApplication {
368+ result = await performBackgroundClick (
369+ bundleIdentifier: targetApp,
370+ at: location,
371+ clickType: configuration. clickType
372+ )
373+ } else {
374+ result = await performSingleClick ( configuration: clickConfig)
375+ }
376+
377+ // If successful, return immediately
378+ if result. success {
379+ return result
380+ }
381+
382+ // Handle error with recovery system
383+ if let error = result. error {
384+ let context = ErrorContext (
385+ originalError: error,
386+ attemptCount: attemptCount,
387+ configuration: clickConfig
388+ )
389+
390+ let recoveryAction = await errorRecoveryManager. attemptRecovery ( for: context)
391+ await errorRecoveryManager. recordRecoveryAttempt ( success: false , for: context)
392+
393+ // Check if we should retry
394+ if recoveryAction. shouldRetry && attemptCount < maxAttempts - 1 {
395+ attemptCount += 1
396+
397+ // Wait for recovery delay
398+ if recoveryAction. retryDelay > 0 {
399+ try ? await Task . sleep ( nanoseconds: UInt64 ( recoveryAction. retryDelay * 1_000_000_000 ) )
400+ }
401+
402+ // Apply recovery strategy adjustments
403+ await applyRecoveryStrategy ( recoveryAction. strategy, for: configuration)
404+
405+ continue // Retry the operation
406+ } else {
407+ // Max attempts reached or recovery says don't retry
408+ print ( " ClickCoordinator: Recovery failed or max attempts reached for error: \( error) " )
409+ return result
410+ }
411+ }
412+
413+ attemptCount += 1
414+ }
415+
416+ // This should not be reached, but provide a fallback
417+ return ClickResult (
418+ success: false ,
419+ actualLocation: location,
420+ timestamp: CFAbsoluteTimeGetCurrent ( ) ,
421+ error: . eventPostingFailed
422+ )
423+ }
424+
425+ /// Applies recovery strategy adjustments to the automation configuration
426+ /// - Parameters:
427+ /// - strategy: Recovery strategy to apply
428+ /// - configuration: Current automation configuration
429+ private func applyRecoveryStrategy(
430+ _ strategy: RecoveryStrategy ,
431+ for configuration: AutomationConfiguration
432+ ) async {
433+ switch strategy {
434+ case . resourceCleanup:
435+ // Give system time to clean up resources
436+ try ? await Task . sleep ( nanoseconds: 500_000_000 ) // 500ms
437+
438+ case . adjustPerformanceSettings:
439+ // Could adjust timing or other performance-related settings
440+ // This is a placeholder for future performance adjustments
441+ break
442+
443+ case . recheckPermissions:
444+ // Force permission status update
445+ await PermissionManager . shared. updatePermissionStatus ( )
446+
447+ case . fallbackToSystemWide:
448+ // This would modify the configuration to use system-wide clicks
449+ // For now, we'll just log the intention
450+ print ( " ClickCoordinator: Falling back to system-wide clicks " )
451+
452+ case . automaticRetry, . gracefulDegradation:
453+ // These strategies are handled by the retry loop logic
454+ break
455+ }
456+ }
457+
342458 /// Randomizes a location within specified variance
343459 /// - Parameters:
344460 /// - base: Base location
0 commit comments