Skip to content
14 changes: 13 additions & 1 deletion Sources/OmniFocusAutomation/BridgeClient.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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))"
)
}

Expand Down Expand Up @@ -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))
}
Expand Down
27 changes: 27 additions & 0 deletions Tests/OmniFocusIntegrationTests/BridgeClientTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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")
}
Loading