From 316d8145f58cb3fe1fcdd67e3eaf1c570a5418e1 Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Thu, 22 May 2025 22:56:34 +0200 Subject: [PATCH] Clean build --- Package.resolved | 54 +++++++ Sources/AXorcist/AXorcist.swift | 17 ++- .../Core/AccessibilityConstants.swift | 1 + Sources/AXorcist/Core/Element.swift | 5 +- Sources/AXorcist/Core/Models.swift | 37 ++++- .../Handlers/AXorcist+ActionHandlers.swift | 43 +++++- .../Handlers/AXorcist+BatchHandler.swift | 2 +- .../Handlers/AXorcist+CollectAllHandler.swift | 43 ++++-- .../Handlers/AXorcist+QueryHandlers.swift | 144 +++++++++++++++--- Sources/AXorcist/Handlers/QueryHandlers.swift | 31 ++-- Sources/AXorcist/Search/ElementSearch.swift | 69 ++++++--- Sources/axorc/Core/CommandExecutor.swift | 112 +++++++------- 12 files changed, 414 insertions(+), 144 deletions(-) diff --git a/Package.resolved b/Package.resolved index 0fb601d..284feb7 100644 --- a/Package.resolved +++ b/Package.resolved @@ -1,5 +1,41 @@ { "pins" : [ + { + "identity" : "defaults", + "kind" : "remoteSourceControl", + "location" : "https://github.com/sindresorhus/Defaults", + "state" : { + "revision" : "3efef5a28ebdbbe922d4a2049493733ed14475a6", + "version" : "7.3.1" + } + }, + { + "identity" : "keyboardshortcuts", + "kind" : "remoteSourceControl", + "location" : "https://github.com/sindresorhus/KeyboardShortcuts", + "state" : { + "revision" : "045cf174010beb335fa1d2567d18c057b8787165", + "version" : "2.3.0" + } + }, + { + "identity" : "launchatlogin", + "kind" : "remoteSourceControl", + "location" : "https://github.com/sindresorhus/LaunchAtLogin", + "state" : { + "revision" : "9a894d799269cb591037f9f9cb0961510d4dca81", + "version" : "5.0.2" + } + }, + { + "identity" : "sparkle", + "kind" : "remoteSourceControl", + "location" : "https://github.com/sparkle-project/Sparkle.git", + "state" : { + "revision" : "0ca3004e98712ea2b39dd881d28448630cce1c99", + "version" : "2.7.0" + } + }, { "identity" : "swift-argument-parser", "kind" : "remoteSourceControl", @@ -9,6 +45,15 @@ "version" : "1.5.0" } }, + { + "identity" : "swift-log", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-log.git", + "state" : { + "revision" : "3d8596ed08bd13520157f0355e35caed215ffbfa", + "version" : "1.6.3" + } + }, { "identity" : "swift-syntax", "kind" : "remoteSourceControl", @@ -26,6 +71,15 @@ "revision" : "399f76dcd91e4c688ca2301fa24a8cc6d9927211", "version" : "0.99.0" } + }, + { + "identity" : "swiftui-introspect", + "kind" : "remoteSourceControl", + "location" : "https://github.com/siteline/SwiftUI-Introspect.git", + "state" : { + "revision" : "121c146fe591b1320238d054ae35c81ffa45f45a", + "version" : "0.12.0" + } } ], "version" : 2 diff --git a/Sources/AXorcist/AXorcist.swift b/Sources/AXorcist/AXorcist.swift index 9179daf..3decd42 100644 --- a/Sources/AXorcist/AXorcist.swift +++ b/Sources/AXorcist/AXorcist.swift @@ -15,8 +15,10 @@ public class AXorcist { internal var recursiveCallDebugLogs: [String] = [] // Added for recursive logging // Default values for collection and search if not provided by the command - public static let defaultMaxDepthSearch = 10 // Example, adjust as needed - public static let defaultMaxDepthCollectAll = 5 + public static let defaultMaxDepthSearch = 10 // Default for general locator-based searches + public static let defaultMaxDepthCollectAll = 7 // Default for collectAll recursive operations + public static let defaultMaxDepthPathResolution = 15 // Max depth for resolving path hints + public static let defaultMaxDepthDescribe = 5 // ADDED: Default for description recursion public static let defaultTimeoutPerElementCollectAll = 0.5 // seconds // Default attributes to fetch if none are specified by the command. @@ -114,10 +116,14 @@ public class AXorcist { currentDebugLogs: inout [String] ) -> Element? { let pathHintString = pathHint.joined(separator: ", ") + // Log with the actual isDebugLoggingEnabled value currentDebugLogs.append(AXorcist.formatDebugLogMessage("navigateToElement: Entered. isDebugLoggingEnabled: \(isDebugLoggingEnabled). pathHint: [\(pathHintString)]", applicationName: nil, commandID: nil, file: #file, function: #function, line: #line)) func dLog(_ message: String, file: String = #file, function: String = #function, line: Int = #line) { - currentDebugLogs.append(AXorcist.formatDebugLogMessage(message, applicationName: nil, commandID: nil, file: file, function: function, line: line)) + // Use the passed-in isDebugLoggingEnabled + if isDebugLoggingEnabled { + currentDebugLogs.append(AXorcist.formatDebugLogMessage(message, applicationName: nil, commandID: nil, file: file, function: function, line: line)) + } } var currentElement = startElement @@ -143,7 +149,7 @@ public class AXorcist { for child in childrenFromElementDotChildren { let childBriefDescForLog = child.briefDescription(option: .default, isDebugLoggingEnabled: isDebugLoggingEnabled, currentDebugLogs: ¤tDebugLogs) if let actualValue = child.attribute(Attribute(attributeName), isDebugLoggingEnabled: isDebugLoggingEnabled, currentDebugLogs: ¤tDebugLogs) { - // dLog("Child (from Element.children) \(childBriefDescForLog) has '\(attributeName)': [\(actualValue)] (Expected: [\(expectedValue)])") + dLog(" [Nav Child Check 1] Child: \(childBriefDescForLog), Attribute '\(attributeName)': [\(actualValue)] (Expected: [\(expectedValue)])") if actualValue == expectedValue { dLog("Matched child (from Element.children): \(childBriefDescForLog) for '\(attributeName):\(expectedValue)'") newElementForNextStep = child @@ -175,8 +181,9 @@ public class AXorcist { currentDebugLogs.append(AXorcist.formatDebugLogMessage("navigateToElement: Direct kAXChildrenAttribute fallback found \(directAxElements.count) raw children for \(currentElementDescForFallbackLog).", applicationName: nil, commandID: nil, file: #file, function: #function, line: #line)) for axChild in directAxElements { let childElement = Element(axChild) - // let childBriefDescForLog = childElement.briefDescription(option: .default, isDebugLoggingEnabled: isDebugLoggingEnabled, currentDebugLogs: ¤tDebugLogs) // Avoid for now inside loop if too verbose or risky + let childBriefDescForLogFallback = childElement.briefDescription(option: .default, isDebugLoggingEnabled: isDebugLoggingEnabled, currentDebugLogs: ¤tDebugLogs) if let actualValue = childElement.attribute(Attribute(attributeName), isDebugLoggingEnabled: isDebugLoggingEnabled, currentDebugLogs: ¤tDebugLogs) { + dLog(" [Nav Child Check 2-Fallback] Child: \(childBriefDescForLogFallback), Attribute '\(attributeName)': [\(actualValue)] (Expected: [\(expectedValue)])") if actualValue == expectedValue { currentDebugLogs.append(AXorcist.formatDebugLogMessage("navigateToElement: Matched child (from direct fallback) for '\(attributeName):\(expectedValue)' on \(currentElementDescForFallbackLog). Child: \(childElement.briefDescription(option: .default, isDebugLoggingEnabled: isDebugLoggingEnabled, currentDebugLogs: ¤tDebugLogs))", applicationName: nil, commandID: nil, file: #file, function: #function, line: #line)) newElementForNextStep = childElement diff --git a/Sources/AXorcist/Core/AccessibilityConstants.swift b/Sources/AXorcist/Core/AccessibilityConstants.swift index 4acc7ce..8d47de2 100644 --- a/Sources/AXorcist/Core/AccessibilityConstants.swift +++ b/Sources/AXorcist/Core/AccessibilityConstants.swift @@ -120,6 +120,7 @@ public let kAXMenuItemRole = "AXMenuItem" // New public let kAXSplitGroupRole = "AXSplitGroup" // New public let kAXSplitterRole = "AXSplitter" // New public let kAXColorWellRole = "AXColorWell" // New +public let kAXLinkRole = "AXLink" // New public let kAXUnknownRole = "AXUnknown" // New // Attributes for web content and tables/lists diff --git a/Sources/AXorcist/Core/Element.swift b/Sources/AXorcist/Core/Element.swift index a35fa41..3a4fec2 100644 --- a/Sources/AXorcist/Core/Element.swift +++ b/Sources/AXorcist/Core/Element.swift @@ -77,11 +77,14 @@ public struct Element: Equatable, Hashable { } } else if T.self == Bool.self { if CFGetTypeID(unwrappedValue) == CFBooleanGetTypeID() { - return CFBooleanGetValue(unwrappedValue as! CFBoolean) as? T + // Reverted to as! based on new compiler note + let swiftBool = CFBooleanGetValue(unwrappedValue as! CFBoolean) + return swiftBool as? T } } else if T.self == Int.self { if CFGetTypeID(unwrappedValue) == CFNumberGetTypeID() { var intValue: Int = 0 + // Reverted to as! based on new compiler note if CFNumberGetValue(unwrappedValue as! CFNumber, .sInt64Type, &intValue) { return intValue as? T } diff --git a/Sources/AXorcist/Core/Models.swift b/Sources/AXorcist/Core/Models.swift index 69eb62d..0746020 100644 --- a/Sources/AXorcist/Core/Models.swift +++ b/Sources/AXorcist/Core/Models.swift @@ -25,7 +25,7 @@ public enum CommandType: String, Codable { } // For encoding/decoding 'Any' type in JSON, especially for element attributes. -public struct AnyCodable: Codable { +public struct AnyCodable: Codable, @unchecked Sendable { public let value: Any public init(_ value: T?) { @@ -162,7 +162,7 @@ public struct CommandEnvelope: Codable { } // Locator for finding elements -public struct Locator: Codable { +public struct Locator: Codable, Sendable { public var match_all: Bool? public var criteria: [String: String] public var root_element_path_hint: [String]? @@ -207,6 +207,18 @@ public struct QueryResponse: Codable { self.error = error self.debug_logs = debug_logs } + + // Custom init for HandlerResponse integration + public init(command_id: String, success: Bool, command: String, handlerResponse: HandlerResponse, debug_logs: [String]?) { + self.command_id = command_id + self.success = success + self.command = command + self.data = handlerResponse.data + // If HandlerResponse has attributes, map them from its data field. + self.attributes = handlerResponse.data?.attributes + self.error = handlerResponse.error + self.debug_logs = debug_logs + } } // Response for collect_all command (multiple elements) @@ -302,7 +314,7 @@ public struct SimpleSuccessResponse: Codable, Equatable { // Placeholder for any additional models if needed -public struct AXElement: Codable { +public struct AXElement: Codable, Sendable { public var attributes: ElementAttributes? public var path: [String]? @@ -324,7 +336,7 @@ extension QueryResponse { // MARK: - Handler Response Models -public struct HandlerResponse { +public struct HandlerResponse: Codable, Sendable { public var data: AXElement? public var error: String? public var debug_logs: [String]? @@ -345,3 +357,20 @@ internal struct CollectAllOutput: Encodable { let app_bundle_id: String? let debug_logs: [String]? } + +// ADDED BatchResponse struct +public struct BatchResponse: Codable { + public var command_id: String + public var success: Bool + public var results: [HandlerResponse] // Array of HandlerResponses for each sub-command + public var error: String? // For an overall batch error, if any + public var debug_logs: [String]? + + public init(command_id: String, success: Bool, results: [HandlerResponse], error: String? = nil, debug_logs: [String]? = nil) { + self.command_id = command_id + self.success = success + self.results = results + self.error = error + self.debug_logs = debug_logs + } +} diff --git a/Sources/AXorcist/Handlers/AXorcist+ActionHandlers.swift b/Sources/AXorcist/Handlers/AXorcist+ActionHandlers.swift index 52f8569..ef8710d 100644 --- a/Sources/AXorcist/Handlers/AXorcist+ActionHandlers.swift +++ b/Sources/AXorcist/Handlers/AXorcist+ActionHandlers.swift @@ -2,6 +2,7 @@ import AppKit import ApplicationServices +import Darwin import Foundation // MARK: - Action & Data Handlers Extension @@ -66,9 +67,26 @@ extension AXorcist { let targetElementForAction: Element if let actualLocator = locator { dLog("[AXorcist.handlePerformAction] Locator provided. Searching from current effectiveElement: \(effectiveElement.briefDescription(option: .default, isDebugLoggingEnabled: isDebugLoggingEnabled, currentDebugLogs: ¤tDebugLogs)) using locator criteria: \(actualLocator.criteria)") - guard let foundElement = search(element: effectiveElement, locator: actualLocator, requireAction: actualLocator.requireAction, depth: 0, maxDepth: maxDepth ?? DEFAULT_MAX_DEPTH_SEARCH, isDebugLoggingEnabled: isDebugLoggingEnabled, currentDebugLogs: ¤tDebugLogs) else { - let error = "[AXorcist.handlePerformAction] Failed to find element with locator: \(actualLocator) starting from \(effectiveElement.briefDescription(option: .default, isDebugLoggingEnabled: isDebugLoggingEnabled, currentDebugLogs: ¤tDebugLogs))" - currentDebugLogs.append(error) + + let searchResult = search( + element: effectiveElement, + locator: actualLocator, + requireAction: actualLocator.requireAction, + depth: 0, + maxDepth: maxDepth ?? DEFAULT_MAX_DEPTH_SEARCH, + isDebugLoggingEnabled: isDebugLoggingEnabled + ) + fputs("HANDLER_RAW_STDERR_BEFORE_LOG_APPEND handlePerformAction: searchResult.logs.count = \(searchResult.logs.count), currentDebugLogs count = \(currentDebugLogs.count)\n", stderr) + currentDebugLogs.append("HANDLER_DEBUG: searchResult.logs.count = \(searchResult.logs.count) before append for performAction") + currentDebugLogs.append(contentsOf: searchResult.logs) + fputs("HANDLER_RAW_STDERR_AFTER_LOG_APPEND handlePerformAction: currentDebugLogs count = \(currentDebugLogs.count)\n", stderr) + currentDebugLogs.append("POST_SEARCH_LOG_APPEND_MARKER_IN_HANDLER") + + guard let foundElement = searchResult.foundElement else { + let error = "[AXorcist.handlePerformAction] Search failed. Could not find element matching locator criteria \(actualLocator.criteria) starting from element \(effectiveElement.briefDescription(option: .default, isDebugLoggingEnabled: isDebugLoggingEnabled, currentDebugLogs: ¤tDebugLogs))." + if !currentDebugLogs.contains(error) { + currentDebugLogs.append(error) + } return HandlerResponse(data: nil, error: error, debug_logs: currentDebugLogs) } targetElementForAction = foundElement @@ -236,17 +254,26 @@ extension AXorcist { let targetElementForExtract: Element if let actualLocator = locator { dLog("[handleExtractText] Locator provided. Searching from current effectiveElement: \(effectiveElement.briefDescription(option: .default, isDebugLoggingEnabled: isDebugLoggingEnabled, currentDebugLogs: ¤tDebugLogs)) using locator criteria: \(actualLocator.criteria)") - guard let foundElement = search( + + let searchResult = search( element: effectiveElement, locator: actualLocator, requireAction: nil, depth: 0, maxDepth: DEFAULT_MAX_DEPTH_SEARCH, - isDebugLoggingEnabled: isDebugLoggingEnabled, - currentDebugLogs: ¤tDebugLogs - ) else { + isDebugLoggingEnabled: isDebugLoggingEnabled + ) + fputs("HANDLER_RAW_STDERR_BEFORE_LOG_APPEND handleExtractText: searchResult.logs.count = \(searchResult.logs.count), currentDebugLogs count = \(currentDebugLogs.count)\n", stderr) + currentDebugLogs.append("HANDLER_DEBUG: searchResult.logs.count = \(searchResult.logs.count) before append for extractText") + currentDebugLogs.append(contentsOf: searchResult.logs) + fputs("HANDLER_RAW_STDERR_AFTER_LOG_APPEND handleExtractText: currentDebugLogs count = \(currentDebugLogs.count)\n", stderr) + currentDebugLogs.append("POST_SEARCH_LOG_APPEND_MARKER_IN_EXTRACT_TEXT") + + guard let foundElement = searchResult.foundElement else { let errorMessage = "[handleExtractText] Target element not found for locator: \(actualLocator) starting from \(effectiveElement.briefDescription(option: .default, isDebugLoggingEnabled: isDebugLoggingEnabled, currentDebugLogs: ¤tDebugLogs))" - currentDebugLogs.append(errorMessage) + if !currentDebugLogs.contains(errorMessage) { + currentDebugLogs.append(errorMessage) + } return HandlerResponse(data: nil, error: errorMessage, debug_logs: currentDebugLogs) } targetElementForExtract = foundElement diff --git a/Sources/AXorcist/Handlers/AXorcist+BatchHandler.swift b/Sources/AXorcist/Handlers/AXorcist+BatchHandler.swift index aa0e210..ccc6747 100644 --- a/Sources/AXorcist/Handlers/AXorcist+BatchHandler.swift +++ b/Sources/AXorcist/Handlers/AXorcist+BatchHandler.swift @@ -173,7 +173,7 @@ extension AXorcist { // This is distinct from commands axorc itself might handle outside of AXorcist library. // @unknown default: // This would be better if Swift enums allowed it easily here for non-frozen enums from other modules. // Since CommandType is in axorc, this default captures any CommandType case not explicitly handled above. - default: + @unknown default: let errorMsg = "Unknown or unhandled command type '\(subCommandEnvelope.command)' in batch processing within AXorcist (sub-command ID: \(subCmdID))" dLog(errorMsg, subCommandID: subCmdID) diff --git a/Sources/AXorcist/Handlers/AXorcist+CollectAllHandler.swift b/Sources/AXorcist/Handlers/AXorcist+CollectAllHandler.swift index b54d609..5d7ed49 100644 --- a/Sources/AXorcist/Handlers/AXorcist+CollectAllHandler.swift +++ b/Sources/AXorcist/Handlers/AXorcist+CollectAllHandler.swift @@ -124,9 +124,20 @@ extension AXorcist { } if let loc = locator { - dLog("Locator provided. Searching for element from current startElement: \(startElement.briefDescription(option: .default, isDebugLoggingEnabled: isDebugLoggingEnabled, currentDebugLogs: &self.recursiveCallDebugLogs)) with locator criteria: \(String(describing: loc.criteria))") - if let locatedStartElement = search(element: startElement, locator: loc, requireAction: loc.requireAction, depth: 0, maxDepth: Self.defaultMaxDepthSearch, isDebugLoggingEnabled: isDebugLoggingEnabled, currentDebugLogs: &self.recursiveCallDebugLogs) { - dLog("Locator found element: \(locatedStartElement.briefDescription(option: .default, isDebugLoggingEnabled: isDebugLoggingEnabled, currentDebugLogs: &self.recursiveCallDebugLogs)). This will be the root for collectAll recursion.") + dLog("Locator provided. Searching for element from current startElement: \(startElement.briefDescription(option: ValueFormatOption.default, isDebugLoggingEnabled: isDebugLoggingEnabled, currentDebugLogs: &self.recursiveCallDebugLogs)) with locator criteria: \(String(describing: loc.criteria))") + + let searchResultCollectAll = search(element: startElement, + locator: loc, + requireAction: loc.requireAction, + depth: 0, + maxDepth: Self.defaultMaxDepthSearch, + isDebugLoggingEnabled: isDebugLoggingEnabled) + self.recursiveCallDebugLogs.append("HANDLER_DEBUG: searchResultCollectAll.logs.count = \(searchResultCollectAll.logs.count) before append for collectAll") + self.recursiveCallDebugLogs.append(contentsOf: searchResultCollectAll.logs) + self.recursiveCallDebugLogs.append("POST_SEARCH_LOG_APPEND_MARKER_IN_COLLECT_ALL") + + if let locatedStartElement = searchResultCollectAll.foundElement { + dLog("Locator found element: \(locatedStartElement.briefDescription(option: ValueFormatOption.default, isDebugLoggingEnabled: isDebugLoggingEnabled, currentDebugLogs: &self.recursiveCallDebugLogs)). This will be the root for collectAll recursion.") startElement = locatedStartElement } else { let errorMsg = "Failed to find element with provided locator criteria: \(String(describing: loc.criteria)). Cannot start collectAll." @@ -147,13 +158,13 @@ extension AXorcist { collectRecursively = { axUIElement, currentDepth in if currentDepth > recursionDepthLimit { dLog( - "Reached recursionDepthLimit (\(recursionDepthLimit)) at element \(Element(axUIElement).briefDescription(option: .default, isDebugLoggingEnabled: isDebugLoggingEnabled, currentDebugLogs: &self.recursiveCallDebugLogs)), stopping recursion for this branch." + "Reached recursionDepthLimit (\(recursionDepthLimit)) at element \(Element(axUIElement).briefDescription(option: ValueFormatOption.default, isDebugLoggingEnabled: isDebugLoggingEnabled, currentDebugLogs: &self.recursiveCallDebugLogs)), stopping recursion for this branch." ) return } let currentElement = Element(axUIElement) - dLog("Collecting element \(currentElement.briefDescription(option: .default, isDebugLoggingEnabled: isDebugLoggingEnabled, currentDebugLogs: &self.recursiveCallDebugLogs)) at depth \(currentDepth)") + dLog("Collecting element \(currentElement.briefDescription(option: ValueFormatOption.default, isDebugLoggingEnabled: isDebugLoggingEnabled, currentDebugLogs: &self.recursiveCallDebugLogs)) at depth \(currentDepth)") let fetchedAttrs = getElementAttributes( currentElement, @@ -182,36 +193,40 @@ extension AXorcist { if childrenResult == .success, let children = childrenRef as? [AXUIElement] { dLog( - "Element \(currentElement.briefDescription(option: .default, isDebugLoggingEnabled: isDebugLoggingEnabled, currentDebugLogs: &self.recursiveCallDebugLogs)) has \(children.count) children at depth \(currentDepth). Recursing." + "Element \(currentElement.briefDescription(option: ValueFormatOption.default, isDebugLoggingEnabled: isDebugLoggingEnabled, currentDebugLogs: &self.recursiveCallDebugLogs)) has \(children.count) children at depth \(currentDepth). Recursing." ) for childElement in children { collectRecursively(childElement, currentDepth + 1) } } else if childrenResult != .success { dLog( - "Failed to get children for element \(currentElement.briefDescription(option: .default, isDebugLoggingEnabled: isDebugLoggingEnabled, currentDebugLogs: &self.recursiveCallDebugLogs)): \(axErrorToString(childrenResult))" + "Failed to get children for element \(currentElement.briefDescription(option: ValueFormatOption.default, isDebugLoggingEnabled: isDebugLoggingEnabled, currentDebugLogs: &self.recursiveCallDebugLogs)): \(axErrorToString(childrenResult))" ) } else { dLog( - "No children found for element \(currentElement.briefDescription(option: .default, isDebugLoggingEnabled: isDebugLoggingEnabled, currentDebugLogs: &self.recursiveCallDebugLogs)) at depth \(currentDepth)" + "No children found for element \(currentElement.briefDescription(option: ValueFormatOption.default, isDebugLoggingEnabled: isDebugLoggingEnabled, currentDebugLogs: &self.recursiveCallDebugLogs)) at depth \(currentDepth)" ) } } dLog( - "Starting recursive collection from start element: \(startElement.briefDescription(option: .default, isDebugLoggingEnabled: isDebugLoggingEnabled, currentDebugLogs: &self.recursiveCallDebugLogs))" + "Starting recursive collection from start element: \(startElement.briefDescription(option: ValueFormatOption.default, isDebugLoggingEnabled: isDebugLoggingEnabled, currentDebugLogs: &self.recursiveCallDebugLogs))" ) - collectRecursively(startElement.underlyingElement, 0) - dLog("Collection complete. Found \(collectedAXElements.count) elements.") + // Start recursion from the determined startElement + if !self.recursiveCallDebugLogs.contains(where: { $0.contains("Failed to find element with provided locator criteria") && $0.contains("Cannot start collectAll") }) { + // Only start if locator search (if any) didn't critically fail and try to return early. + collectRecursively(startElement.underlyingElement, 0) + } - return encode(CollectAllOutput( + let output = CollectAllOutput( command_id: effectiveCommandId, - success: true, + success: true, // Assuming success if we reach here, errors would have returned earlier command: "collectAll", collected_elements: collectedAXElements, app_bundle_id: appIdentifier, debug_logs: self.recursiveCallDebugLogs - )) + ) + return encode(output) } } \ No newline at end of file diff --git a/Sources/AXorcist/Handlers/AXorcist+QueryHandlers.swift b/Sources/AXorcist/Handlers/AXorcist+QueryHandlers.swift index df335de..b2f7cdf 100644 --- a/Sources/AXorcist/Handlers/AXorcist+QueryHandlers.swift +++ b/Sources/AXorcist/Handlers/AXorcist+QueryHandlers.swift @@ -26,7 +26,6 @@ extension AXorcist { let appIdentifier = appIdentifierOrNil ?? self.focusedAppKeyValue dLog("Handling query for app: \(appIdentifier)") - // Pass logging parameters to applicationElement guard let appElement = applicationElement( for: appIdentifier, isDebugLoggingEnabled: isDebugLoggingEnabled, @@ -42,7 +41,6 @@ extension AXorcist { var effectiveElement = appElement if let pathHint = pathHint, !pathHint.isEmpty { dLog("Navigating with path_hint: \(pathHint.joined(separator: " -> "))") - // Pass logging parameters to navigateToElement if let navigatedElement = self.navigateToElement( from: effectiveElement, pathHint: pathHint, @@ -70,12 +68,11 @@ extension AXorcist { foundElement = effectiveElement } else { dLog("Locator contains element-specific criteria or is complex. Proceeding with search.") - var searchStartElementForLocator = appElement + var searchStartElementForLocator = effectiveElement if let rootPathHint = locator.root_element_path_hint, !rootPathHint.isEmpty { dLog( "Locator has root_element_path_hint: \(rootPathHint.joined(separator: " -> ")). Navigating from app element first." ) - // Pass logging parameters to navigateToElement guard let containerElement = self.navigateToElement( from: appElement, pathHint: rootPathHint, @@ -90,31 +87,29 @@ extension AXorcist { } searchStartElementForLocator = containerElement dLog( - "Searching with locator within container found by root_element_path_hint: \(searchStartElementForLocator.briefDescription(option: .default, isDebugLoggingEnabled: isDebugLoggingEnabled, currentDebugLogs: ¤tDebugLogs))" + "Searching with locator within container found by root_element_path_hint: \(searchStartElementForLocator.briefDescription(option: ValueFormatOption.default, isDebugLoggingEnabled: isDebugLoggingEnabled, currentDebugLogs: ¤tDebugLogs))" ) } else { - searchStartElementForLocator = effectiveElement dLog( - "Searching with locator from element (determined by main path_hint or app root): \(searchStartElementForLocator.briefDescription(option: .default, isDebugLoggingEnabled: isDebugLoggingEnabled, currentDebugLogs: ¤tDebugLogs))" + "Searching with locator from element (determined by main path_hint or app root): \(searchStartElementForLocator.briefDescription(option: ValueFormatOption.default, isDebugLoggingEnabled: isDebugLoggingEnabled, currentDebugLogs: ¤tDebugLogs))" ) } - - let finalSearchTarget = (pathHint != nil && !pathHint!.isEmpty) ? effectiveElement : - searchStartElementForLocator - - // Pass logging parameters to search - foundElement = search( - element: finalSearchTarget, + + let searchResult = search( + element: searchStartElementForLocator, locator: locator, requireAction: locator.requireAction, + depth: 0, maxDepth: maxDepth ?? AXorcist.defaultMaxDepthSearch, - isDebugLoggingEnabled: isDebugLoggingEnabled, - currentDebugLogs: ¤tDebugLogs + isDebugLoggingEnabled: isDebugLoggingEnabled ) + currentDebugLogs.append("HANDLER_DEBUG: searchResult.logs.count = \(searchResult.logs.count) before append for query") + currentDebugLogs.append(contentsOf: searchResult.logs) + currentDebugLogs.append("POST_SEARCH_LOG_APPEND_MARKER_IN_QUERY") + foundElement = searchResult.foundElement } if let elementToQuery = foundElement { - // Pass logging parameters to getElementAttributes var attributes = getElementAttributes( elementToQuery, requestedAttributes: requestedAttributes ?? [], @@ -176,7 +171,6 @@ extension AXorcist { ) } - // Find element to get attributes from var effectiveElement = appElement if let pathHint = pathHint, !pathHint.isEmpty { dLog("handleGetAttributes: Navigating with path_hint: \(pathHint.joined(separator: " -> "))") @@ -199,20 +193,24 @@ extension AXorcist { } dLog( - "handleGetAttributes: Searching for element with locator: \(locator.criteria) from root: \(effectiveElement.briefDescription(option: .default, isDebugLoggingEnabled: isDebugLoggingEnabled, currentDebugLogs: ¤tDebugLogs))" + "handleGetAttributes: Searching for element with locator: \(locator.criteria) from root: \(effectiveElement.briefDescription(option: ValueFormatOption.default, isDebugLoggingEnabled: isDebugLoggingEnabled, currentDebugLogs: ¤tDebugLogs))" ) - let foundElement = search( + let searchResult = search( element: effectiveElement, locator: locator, requireAction: locator.requireAction, + depth: 0, maxDepth: maxDepth ?? AXorcist.defaultMaxDepthSearch, - isDebugLoggingEnabled: isDebugLoggingEnabled, - currentDebugLogs: ¤tDebugLogs + isDebugLoggingEnabled: isDebugLoggingEnabled ) + currentDebugLogs.append("HANDLER_DEBUG: searchResult.logs.count = \(searchResult.logs.count) before append for getAttributes") + currentDebugLogs.append(contentsOf: searchResult.logs) + currentDebugLogs.append("POST_SEARCH_LOG_APPEND_MARKER_IN_GET_ATTRIBUTES") + let foundElement = searchResult.foundElement if let elementToQuery = foundElement { dLog( - "handleGetAttributes: Element found: \(elementToQuery.briefDescription(option: .default, isDebugLoggingEnabled: isDebugLoggingEnabled, currentDebugLogs: ¤tDebugLogs)). Fetching attributes: \(requestedAttributes ?? ["all"])..." + "handleGetAttributes: Element found: \(elementToQuery.briefDescription(option: ValueFormatOption.default, isDebugLoggingEnabled: isDebugLoggingEnabled, currentDebugLogs: ¤tDebugLogs)). Fetching attributes: \(requestedAttributes ?? ["all"])..." ) var attributes = getElementAttributes( elementToQuery, @@ -227,7 +225,7 @@ extension AXorcist { attributes = encodeAttributesToJSONStringRepresentation(attributes) } dLog( - "Successfully fetched attributes for element \(elementToQuery.briefDescription(option: .default, isDebugLoggingEnabled: isDebugLoggingEnabled, currentDebugLogs: ¤tDebugLogs))." + "Successfully fetched attributes for element \(elementToQuery.briefDescription(option: ValueFormatOption.default, isDebugLoggingEnabled: isDebugLoggingEnabled, currentDebugLogs: ¤tDebugLogs))." ) let axElement = AXElement(attributes: attributes) @@ -246,4 +244,102 @@ extension AXorcist { ) } } + + @MainActor + public func handleDescribeElement( + for appIdentifierOrNil: String?, + locator: Locator, + pathHint: [String]?, + maxDepth: Int?, + requestedAttributes: [String]?, + outputFormat: OutputFormat?, + isDebugLoggingEnabled: Bool, + currentDebugLogs: inout [String] + ) async -> HandlerResponse { + + func dLog(_ message: String) { if isDebugLoggingEnabled { currentDebugLogs.append(message) } } + + let appIdentifier = appIdentifierOrNil ?? self.focusedAppKeyValue + dLog("Handling describe_element for app: \(appIdentifier)") + + guard let appElement = applicationElement( + for: appIdentifier, + isDebugLoggingEnabled: isDebugLoggingEnabled, + currentDebugLogs: ¤tDebugLogs + ) else { + return HandlerResponse( + data: nil, + error: "Application not found: \(appIdentifier)", + debug_logs: isDebugLoggingEnabled ? currentDebugLogs : nil + ) + } + + var effectiveElement = appElement + if let pathHint = pathHint, !pathHint.isEmpty { + dLog("handleDescribeElement: Navigating with path_hint: \(pathHint.joined(separator: " -> "))") + if let navigatedElement = self.navigateToElement( + from: appElement, + pathHint: pathHint, + isDebugLoggingEnabled: isDebugLoggingEnabled, + currentDebugLogs: ¤tDebugLogs + ) { + effectiveElement = navigatedElement + } else { + let errorMessage = "Element not found via path hint for describe: \(pathHint.joined(separator: " -> "))" + dLog("handleDescribeElement: \(errorMessage)") + return HandlerResponse( + data: nil, + error: errorMessage, + debug_logs: isDebugLoggingEnabled ? currentDebugLogs : nil + ) + } + } + + dLog( + "[AXorcist.handleDescribeElement] Searching for element to describe using locator: \(locator.criteria) from effective element: \(effectiveElement.briefDescription(option: ValueFormatOption.default, isDebugLoggingEnabled: isDebugLoggingEnabled, currentDebugLogs: ¤tDebugLogs))" + ) + + let searchMaxDepth = maxDepth ?? AXorcist.defaultMaxDepthSearch + + let searchResult = search( + element: effectiveElement, + locator: locator, + requireAction: locator.requireAction, + depth: 0, + maxDepth: searchMaxDepth, + isDebugLoggingEnabled: isDebugLoggingEnabled + ) + currentDebugLogs.append("HANDLER_DEBUG: searchResult.logs.count = \(searchResult.logs.count) before append for describeElement") + currentDebugLogs.append(contentsOf: searchResult.logs) + currentDebugLogs.append("POST_SEARCH_LOG_APPEND_MARKER_IN_DESCRIBE") + guard let elementToDescribe = searchResult.foundElement else { + let error = "[AXorcist.handleDescribeElement] Element to describe not found for locator: \(locator.criteria)" + currentDebugLogs.append(error) + return HandlerResponse(data: nil, error: error, debug_logs: currentDebugLogs) + } + + dLog( + "[AXorcist.handleDescribeElement] Element found: \(elementToDescribe.briefDescription(option: ValueFormatOption.default, isDebugLoggingEnabled: isDebugLoggingEnabled, currentDebugLogs: ¤tDebugLogs)). Now describing." + ) + + var attributes = getElementAttributes( + elementToDescribe, + requestedAttributes: requestedAttributes ?? ["all"], + forMultiDefault: true, + targetRole: locator.criteria[kAXRoleAttribute], + outputFormat: outputFormat ?? .verbose, + isDebugLoggingEnabled: isDebugLoggingEnabled, + currentDebugLogs: ¤tDebugLogs + ) + if outputFormat == .json_string { + attributes = encodeAttributesToJSONStringRepresentation(attributes) + } + + let axElement = AXElement( + attributes: attributes, + path: elementToDescribe.generatePathArray(upTo: appElement, isDebugLoggingEnabled: isDebugLoggingEnabled, currentDebugLogs: ¤tDebugLogs) + ) + + return HandlerResponse(data: axElement, error: nil, debug_logs: currentDebugLogs) + } } \ No newline at end of file diff --git a/Sources/AXorcist/Handlers/QueryHandlers.swift b/Sources/AXorcist/Handlers/QueryHandlers.swift index d97c1b1..714058c 100644 --- a/Sources/AXorcist/Handlers/QueryHandlers.swift +++ b/Sources/AXorcist/Handlers/QueryHandlers.swift @@ -82,14 +82,15 @@ extension AXorcist { dLog( "[AXorcist.handleGetAttributes] Not an AXApplication-only locator or value mismatch. Searching for element with locator: \(locator.criteria) from root: \(rootElementDescription)" ) - elementToQuery = search( + let searchResultGetAttributes = search( element: effectiveElement, locator: locator, requireAction: locator.requireAction, maxDepth: maxDepth ?? DEFAULT_MAX_DEPTH_SEARCH, - isDebugLoggingEnabled: isDebugLoggingEnabled, - currentDebugLogs: ¤tDebugLogs + isDebugLoggingEnabled: isDebugLoggingEnabled ) + currentDebugLogs.append(contentsOf: searchResultGetAttributes.logs) + elementToQuery = searchResultGetAttributes.foundElement } if let actualElementToQuery = elementToQuery { @@ -243,14 +244,15 @@ extension AXorcist { let finalSearchTarget = (pathHint != nil && !pathHint!.isEmpty) ? effectiveElement : searchStartElementForLocator - foundElement = search( + let searchResultQuery = search( element: finalSearchTarget, locator: locator, requireAction: locator.requireAction, maxDepth: maxDepth ?? DEFAULT_MAX_DEPTH_SEARCH, - isDebugLoggingEnabled: isDebugLoggingEnabled, - currentDebugLogs: ¤tDebugLogs + isDebugLoggingEnabled: isDebugLoggingEnabled ) + currentDebugLogs.append(contentsOf: searchResultQuery.logs) + foundElement = searchResultQuery.foundElement } if let elementToQuery = foundElement { @@ -350,18 +352,19 @@ extension AXorcist { dLog( "[AXorcist.handleDescribeElement] Searching for element with locator: \(locator.criteria) from root: \(rootElementDescription)" ) - let foundElement = search( + let searchResultDescribe = search( element: effectiveElement, locator: locator, requireAction: locator.requireAction, maxDepth: maxDepth ?? DEFAULT_MAX_DEPTH_SEARCH, - isDebugLoggingEnabled: isDebugLoggingEnabled, - currentDebugLogs: ¤tDebugLogs + isDebugLoggingEnabled: isDebugLoggingEnabled ) + currentDebugLogs.append(contentsOf: searchResultDescribe.logs) + let foundElementForDescribe = searchResultDescribe.foundElement - if let elementToDescribe = foundElement { + if let elementToDescribe = foundElementForDescribe { let elementDescription = elementToDescribe.briefDescription( - option: .default, + option: ValueFormatOption.default, isDebugLoggingEnabled: isDebugLoggingEnabled, currentDebugLogs: ¤tDebugLogs ) @@ -374,9 +377,9 @@ extension AXorcist { var attributes = getElementAttributes( elementToDescribe, requestedAttributes: [], // Empty means 'all standard' or 'all known' - forMultiDefault: false, + forMultiDefault: true, targetRole: locator.criteria[kAXRoleAttribute], - outputFormat: .verbose, // Describe implies verbose + outputFormat: outputFormat ?? .smart, isDebugLoggingEnabled: isDebugLoggingEnabled, currentDebugLogs: ¤tDebugLogs ) @@ -393,7 +396,7 @@ extension AXorcist { let axElement = AXElement(attributes: attributes, path: elementPathArray) dLog( - "[AXorcist.handleDescribeElement] Successfully described element \(elementToDescribe.briefDescription(option: .default, isDebugLoggingEnabled: isDebugLoggingEnabled, currentDebugLogs: ¤tDebugLogs))." + "[AXorcist.handleDescribeElement] Successfully described element \(elementToDescribe.briefDescription(option: ValueFormatOption.default, isDebugLoggingEnabled: isDebugLoggingEnabled, currentDebugLogs: ¤tDebugLogs))." ) return HandlerResponse(data: axElement, error: nil, debug_logs: currentDebugLogs) } else { diff --git a/Sources/AXorcist/Search/ElementSearch.swift b/Sources/AXorcist/Search/ElementSearch.swift index eeba1b2..267134b 100644 --- a/Sources/AXorcist/Search/ElementSearch.swift +++ b/Sources/AXorcist/Search/ElementSearch.swift @@ -1,6 +1,7 @@ // ElementSearch.swift - Contains search and element collection logic import ApplicationServices +import Darwin import Foundation // Variable DEBUG_LOGGING_ENABLED is expected to be globally available from Logging.swift @@ -97,14 +98,28 @@ public func search(element: Element, requireAction: String?, depth: Int = 0, maxDepth: Int = DEFAULT_MAX_DEPTH_SEARCH, - isDebugLoggingEnabled: Bool, - currentDebugLogs: inout [String]) -> Element? { - func dLog(_ message: String) { if isDebugLoggingEnabled { currentDebugLogs.append(message) } } - var tempLogs: [String] = [] // For calls to Element methods + isDebugLoggingEnabled: Bool + /* REMOVED: currentDebugLogs: inout [String] */) -> (foundElement: Element?, logs: [String]) { // CHANGED RETURN TYPE + fputs("SEARCH_FUNCTION_RAW_PRINT_STDERR: Depth \(depth), isDebug: \(isDebugLoggingEnabled)\n", stderr) + var internalSearchLogs: [String] = [] // NEW: Internal log storage + + // DIRECT APPEND AND LOCAL LET FOR DEBUG FLAG + internalSearchLogs.append("SEARCH_ENTRY_DIRECT_APPEND: Depth \(depth), isDebugLoggingEnabledParam: \(isDebugLoggingEnabled)") + let localDebugEnabled = isDebugLoggingEnabled + internalSearchLogs.append("SEARCH_ENTRY_LOCALDEBUG: localDebugEnabled is \(localDebugEnabled)") + + func dLog(_ message: String) { if localDebugEnabled { internalSearchLogs.append(message) } } // Appends to internalSearchLogs + + var tempLogsForElementMethods: [String] = [] // For calls to Element methods that require inout logs let criteriaDesc = locator.criteria.map { "\($0.key)=\($0.value)" }.joined(separator: ", ") - let roleStr = element.role(isDebugLoggingEnabled: isDebugLoggingEnabled, currentDebugLogs: &tempLogs) ?? "nil" - let titleStr = element.title(isDebugLoggingEnabled: isDebugLoggingEnabled, currentDebugLogs: &tempLogs) ?? "N/A" + // Calls to element.role, element.title etc. will use tempLogsForElementMethods + // Their logs aren't the primary concern for *this* refactor's test, but they need a valid inout array. + let roleStr = element.role(isDebugLoggingEnabled: isDebugLoggingEnabled, currentDebugLogs: &tempLogsForElementMethods) ?? "nil" + let titleStr = element.title(isDebugLoggingEnabled: isDebugLoggingEnabled, currentDebugLogs: &tempLogsForElementMethods) ?? "N/A" + internalSearchLogs.append(contentsOf: tempLogsForElementMethods) // Append logs from element methods + tempLogsForElementMethods.removeAll() // Clear for next use + dLog( "search [D\(depth)]: Visiting. Role: \(roleStr), Title: \(titleStr). Locator Criteria: [\(criteriaDesc)], Action: \(requireAction ?? "none")" ) @@ -113,36 +128,46 @@ public func search(element: Element, let briefDesc = element.briefDescription( option: .default, isDebugLoggingEnabled: isDebugLoggingEnabled, - currentDebugLogs: &tempLogs + currentDebugLogs: &tempLogsForElementMethods ) + internalSearchLogs.append(contentsOf: tempLogsForElementMethods) + tempLogsForElementMethods.removeAll() dLog("search [D\(depth)]: Max depth \(maxDepth) reached for element \(briefDesc).") - return nil + return (nil, internalSearchLogs) // RETURN TUPLE } + // evaluateElementAgainstCriteria still uses inout, its logs will be added to internalSearchLogs + var logsFromEvaluate: [String] = [] let matchStatus = evaluateElementAgainstCriteria(element: element, locator: locator, actionToVerify: requireAction, depth: depth, isDebugLoggingEnabled: isDebugLoggingEnabled, - currentDebugLogs: ¤tDebugLogs) // Pass through logs + currentDebugLogs: &logsFromEvaluate) + internalSearchLogs.append(contentsOf: logsFromEvaluate) if matchStatus == .fullMatch { let briefDesc = element.briefDescription( option: .default, isDebugLoggingEnabled: isDebugLoggingEnabled, - currentDebugLogs: &tempLogs + currentDebugLogs: &tempLogsForElementMethods ) + internalSearchLogs.append(contentsOf: tempLogsForElementMethods) + tempLogsForElementMethods.removeAll() dLog( "search [D\(depth)]: evaluateElementAgainstCriteria returned .fullMatch for \(briefDesc). Returning element." ) - return element + return (element, internalSearchLogs) // RETURN TUPLE } let briefDesc = element.briefDescription( option: .default, isDebugLoggingEnabled: isDebugLoggingEnabled, - currentDebugLogs: &tempLogs + currentDebugLogs: &tempLogsForElementMethods ) + internalSearchLogs.append(contentsOf: tempLogsForElementMethods) + tempLogsForElementMethods.removeAll() + if matchStatus == .partialMatch_actionMissing { dLog( "search [D\(depth)]: Element \(briefDesc) matched criteria but missed action '\(requireAction ?? "")'. Continuing child search." @@ -154,25 +179,31 @@ public func search(element: Element, let childrenToSearch: [Element] = element.children( isDebugLoggingEnabled: isDebugLoggingEnabled, - currentDebugLogs: &tempLogs + currentDebugLogs: &tempLogsForElementMethods // Pass tempLogsForElementMethods here ) ?? [] + internalSearchLogs.append(contentsOf: tempLogsForElementMethods) + tempLogsForElementMethods.removeAll() + if !childrenToSearch.isEmpty { for childElement in childrenToSearch { - if let found = search( + // RECURSIVE CALL + let recursiveResult = search( // No longer passes currentDebugLogs element: childElement, locator: locator, requireAction: requireAction, depth: depth + 1, maxDepth: maxDepth, - isDebugLoggingEnabled: isDebugLoggingEnabled, - currentDebugLogs: ¤tDebugLogs - ) { - return found + isDebugLoggingEnabled: isDebugLoggingEnabled + // Removed: currentDebugLogs: ¤tDebugLogs -> now &internalSearchLogs + ) + internalSearchLogs.append(contentsOf: recursiveResult.logs) // Append logs from recursive call + if let found = recursiveResult.foundElement { + return (found, internalSearchLogs) // RETURN TUPLE } } } - return nil + return (nil, internalSearchLogs) // RETURN TUPLE } @MainActor diff --git a/Sources/axorc/Core/CommandExecutor.swift b/Sources/axorc/Core/CommandExecutor.swift index 23661b8..4792dd9 100644 --- a/Sources/axorc/Core/CommandExecutor.swift +++ b/Sources/axorc/Core/CommandExecutor.swift @@ -3,6 +3,12 @@ import AXorcistLib import Foundation +// TEMPORARY TEST STRUCT - REMOVED +// struct SimpleTestResponse: Codable { +// var message: String +// var logs: [String]? +// } + struct CommandExecutor { static func execute( @@ -12,7 +18,10 @@ struct CommandExecutor { ) async -> String { var localDebugLogs: [String] = [] - if debug { localDebugLogs.append("Executing command: \(command.command) (ID: \(command.command_id))") } + // Determine the effective debug logging state + let effectiveDebugLogging = command.debug_logging ?? debug + + if effectiveDebugLogging { localDebugLogs.append("Executing command: \(command.command) (ID: \(command.command_id)), effectiveDebug: \(effectiveDebugLogging), cliDebug: \(debug), jsonDebug: \(String(describing: command.debug_logging))") } let ax = axorcist // Use the passed-in instance @@ -36,7 +45,7 @@ struct CommandExecutor { actionName: actionName, actionValue: command.action_value, maxDepth: command.max_elements, - isDebugLoggingEnabled: debug, + isDebugLoggingEnabled: effectiveDebugLogging, currentDebugLogs: &localDebugLogs ) let queryResponse = QueryResponse( @@ -44,7 +53,7 @@ struct CommandExecutor { success: handlerResponse.error == nil, command: command.command.rawValue, handlerResponse: handlerResponse, - debug_logs: debug ? localDebugLogs : nil + debug_logs: effectiveDebugLogging ? localDebugLogs : nil ) return encodeToJson(queryResponse) ?? "{\"error\": \"Encoding performAction response failed\"}" @@ -52,7 +61,7 @@ struct CommandExecutor { let handlerResponse: HandlerResponse = await ax.handleGetFocusedElement( for: command.application, requestedAttributes: command.attributes, - isDebugLoggingEnabled: debug, + isDebugLoggingEnabled: effectiveDebugLogging, currentDebugLogs: &localDebugLogs ) let queryResponse = QueryResponse( @@ -60,7 +69,7 @@ struct CommandExecutor { success: handlerResponse.error == nil, command: command.command.rawValue, handlerResponse: handlerResponse, - debug_logs: debug ? localDebugLogs : nil + debug_logs: effectiveDebugLogging ? localDebugLogs : nil ) return encodeToJson(queryResponse) ?? "{\"error\": \"Encoding getFocusedElement response failed\"}" @@ -83,7 +92,7 @@ struct CommandExecutor { pathHint: command.path_hint, maxDepth: command.max_elements, outputFormat: command.output_format, - isDebugLoggingEnabled: debug, + isDebugLoggingEnabled: effectiveDebugLogging, currentDebugLogs: &localDebugLogs ) let queryResponse = QueryResponse( @@ -91,7 +100,7 @@ struct CommandExecutor { success: handlerResponse.error == nil, command: command.command.rawValue, handlerResponse: handlerResponse, - debug_logs: debug ? localDebugLogs : nil + debug_logs: effectiveDebugLogging ? localDebugLogs : nil ) return encodeToJson(queryResponse) ?? "{\"error\": \"Encoding getAttributes response failed\"}" @@ -114,7 +123,7 @@ struct CommandExecutor { maxDepth: command.max_elements, requestedAttributes: command.attributes, outputFormat: command.output_format, - isDebugLoggingEnabled: debug, + isDebugLoggingEnabled: effectiveDebugLogging, currentDebugLogs: &localDebugLogs ) let queryResponse = QueryResponse( @@ -122,7 +131,7 @@ struct CommandExecutor { success: handlerResponse.error == nil, command: command.command.rawValue, handlerResponse: handlerResponse, - debug_logs: debug ? localDebugLogs : nil + debug_logs: effectiveDebugLogging ? localDebugLogs : nil ) return encodeToJson(queryResponse) ?? "{\"error\": \"Encoding query response failed\"}" @@ -144,7 +153,7 @@ struct CommandExecutor { pathHint: command.path_hint, maxDepth: command.max_elements, outputFormat: command.output_format, - isDebugLoggingEnabled: debug, + isDebugLoggingEnabled: effectiveDebugLogging, currentDebugLogs: &localDebugLogs ) let queryResponse = QueryResponse( @@ -152,7 +161,7 @@ struct CommandExecutor { success: handlerResponse.error == nil, command: command.command.rawValue, handlerResponse: handlerResponse, - debug_logs: debug ? localDebugLogs : nil + debug_logs: effectiveDebugLogging ? localDebugLogs : nil ) return encodeToJson(queryResponse) ?? "{\"error\": \"Encoding describeElement response failed\"}" @@ -172,7 +181,7 @@ struct CommandExecutor { for: command.application, locator: locator, pathHint: command.path_hint, - isDebugLoggingEnabled: debug, + isDebugLoggingEnabled: effectiveDebugLogging, currentDebugLogs: &localDebugLogs ) let queryResponse = QueryResponse( @@ -180,14 +189,11 @@ struct CommandExecutor { success: handlerResponse.error == nil, command: command.command.rawValue, handlerResponse: handlerResponse, - debug_logs: debug ? localDebugLogs : nil + debug_logs: effectiveDebugLogging ? localDebugLogs : nil ) return encodeToJson(queryResponse) ?? "{\"error\": \"Encoding extractText response failed\"}" case .collectAll: - // AXorcist.handleCollectAll returns a String (JSON) directly. - // It manages its own debug logs internally via recursiveCallDebugLogs. - // The `currentDebugLogs` passed to it is for initial logs only. let jsonStringResult = await ax.handleCollectAll( for: command.application, locator: command.locator, @@ -196,64 +202,59 @@ struct CommandExecutor { requestedAttributes: command.attributes, outputFormat: command.output_format, commandId: command.command_id, - isDebugLoggingEnabled: debug, + isDebugLoggingEnabled: effectiveDebugLogging, currentDebugLogs: localDebugLogs ) return jsonStringResult case .batch: guard let subCommands = command.sub_commands else { - let error = "Missing sub_commands for batch" + let error = "Missing sub_commands for batch command" localDebugLogs.append(error) - return encodeToJson(QueryResponse( + return encodeToJson(BatchResponse( + command_id: command.command_id, success: false, - commandId: command.command_id, - command: command.command.rawValue, + results: [], error: error, - debugLogs: debug ? localDebugLogs : nil - )) ?? "{\"error\": \"Encoding error response failed\"}" + debug_logs: effectiveDebugLogging ? localDebugLogs : nil + )) ?? "{\"error\": \"Encoding batch error response failed\"}" } - let batchHandlerResponses: [HandlerResponse] = await ax.handleBatchCommands( + + var batchDebugLogs = localDebugLogs + let batchResults: [HandlerResponse] = await ax.handleBatchCommands( batchCommandID: command.command_id, subCommands: subCommands, - isDebugLoggingEnabled: debug, - currentDebugLogs: &localDebugLogs + isDebugLoggingEnabled: effectiveDebugLogging, + currentDebugLogs: &batchDebugLogs ) - // Convert [HandlerResponse] to an array of QueryResponse objects - let queryResponses = batchHandlerResponses.enumerated().map { index, hr -> QueryResponse in - let subCommandId = index < subCommands.count ? subCommands[index].command_id : "batch_sub_\(index)" - let subCommand = index < subCommands.count ? subCommands[index].command.rawValue : "unknown" - return QueryResponse( - command_id: subCommandId, - success: hr.error == nil, - command: subCommand, - handlerResponse: hr, - debug_logs: hr.debug_logs - ) - } - // Determine overall success of the batch - let overallSuccess = batchHandlerResponses.allSatisfy { $0.error == nil } - // Return BatchOperationResponse - let batchResponse = BatchOperationResponse( + + let overallSuccess = batchResults.allSatisfy { $0.error == nil } + let batchResponse = BatchResponse( command_id: command.command_id, success: overallSuccess, - results: queryResponses, - debug_logs: debug ? localDebugLogs : nil + results: batchResults, + error: nil, + debug_logs: effectiveDebugLogging ? batchDebugLogs : nil ) return encodeToJson(batchResponse) ?? "{\"error\": \"Encoding batch response failed\"}" case .ping: - let appName = command.application ?? "N/A" - let msg = "Ping received for \(appName). AXORC Version: \(AXORC_VERSION)" - localDebugLogs.append(msg) - return encodeToJson(SimpleSuccessResponse( + if effectiveDebugLogging { localDebugLogs.append("Ping command received. Responding with pong.") } + // Create an empty HandlerResponse for ping + let pingHandlerResponse = HandlerResponse( + data: nil, + error: nil, + debug_logs: nil // Ping-specific logs are already in localDebugLogs + ) + // Construct QueryResponse using the HandlerResponse initializer + let queryResponse = QueryResponse( command_id: command.command_id, - success: true, - status: "pong", - message: msg, - details: nil, - debug_logs: debug ? localDebugLogs : nil - )) ?? "{\"error\": \"Encoding ping response failed\"}" + success: true, // Ping is always a success if reached + command: command.command.rawValue, + handlerResponse: pingHandlerResponse, + debug_logs: effectiveDebugLogging ? localDebugLogs : nil + ) + return encodeToJson(queryResponse) ?? "{\"error\": \"Encoding ping response failed\"}" } } @@ -265,6 +266,9 @@ struct CommandExecutor { let data = try encoder.encode(object) return String(data: data, encoding: .utf8) } catch { + // PRINT THE ERROR TO STDERR + let errorDescription = "JSON ENCODING ERROR: \(error.localizedDescription). Details: \(error)" + FileHandle.standardError.write(errorDescription.data(using: .utf8)!) return nil } }