diff --git a/Sources/OmniFocusAutomation/BridgeClient.swift b/Sources/OmniFocusAutomation/BridgeClient.swift index 3e3721a..1cb89e2 100644 --- a/Sources/OmniFocusAutomation/BridgeClient.swift +++ b/Sources/OmniFocusAutomation/BridgeClient.swift @@ -500,8 +500,13 @@ final class BridgeClient: @unchecked Sendable { let timeoutSeconds = String(format: "%.1f", timeout) let lastReadDetail = lastReadError.map { String(describing: $0) } ?? "none" + let pickupState = bridgePickupState( + requestExists: requestExists, + responseExists: responseExists, + lockExists: lockExists + ) throw AutomationError.executionFailed( - "Bridge response timed out after \(timeoutSeconds)s (requestId=\(requestId), requestExists=\(requestExists), responseExists=\(responseExists), lockExists=\(lockExists), lastReadError=\(lastReadDetail))" + "Bridge response timed out after \(timeoutSeconds)s (requestId=\(requestId), pickupState=\(pickupState), requestExists=\(requestExists), responseExists=\(responseExists), lockExists=\(lockExists), strandedRedispatched=\(hasRedispatchedStrandedRequest), lastReadError=\(lastReadDetail))" ) } @@ -611,6 +616,13 @@ func strandedRedispatchDelay(timeout: TimeInterval) -> TimeInterval { min(2.0, max(0.5, timeout * 0.1)) } +func bridgePickupState(requestExists: Bool, responseExists: Bool, lockExists: Bool) -> String { + if responseExists { return "response_written" } + if lockExists { return "bridge_processing" } + if requestExists { return "stranded_not_picked_up" } + return "request_missing" +} + func lateStrandedRecoveryGrace(timeout: TimeInterval) -> TimeInterval { min(10.0, max(3.0, timeout * 0.2)) } diff --git a/Tests/OmniFocusIntegrationTests/BridgeClientTests.swift b/Tests/OmniFocusIntegrationTests/BridgeClientTests.swift index a28c314..e142043 100644 --- a/Tests/OmniFocusIntegrationTests/BridgeClientTests.swift +++ b/Tests/OmniFocusIntegrationTests/BridgeClientTests.swift @@ -106,3 +106,30 @@ func lateStrandedRecoveryOnlyAppliesToURLTransportWithoutLock() { lockExists: false )) } + +@Test +func bridgePickupStateNamesTimeoutCause() { + #expect(bridgePickupState( + requestExists: true, + responseExists: false, + lockExists: false + ) == "stranded_not_picked_up") + + #expect(bridgePickupState( + requestExists: true, + responseExists: false, + lockExists: true + ) == "bridge_processing") + + #expect(bridgePickupState( + requestExists: true, + responseExists: true, + lockExists: false + ) == "response_written") + + #expect(bridgePickupState( + requestExists: false, + responseExists: false, + lockExists: false + ) == "request_missing") +}