Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 34 additions & 3 deletions clients/shared/App/Auth/AuthService.swift
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,13 @@ import os

private let log = Logger(subsystem: Bundle.main.bundleIdentifier ?? "com.vellum.vellum-assistant", category: "AuthService")

// MARK: - Thread-safe configuredBaseURL storage (module-private)
// These live outside the @MainActor class so they are nonisolated by default.
// GatewayHTTPClient (nonisolated) reads via AuthService.currentConfiguredBaseURL;
// SettingsStore (@MainActor) writes via AuthService.shared.configuredBaseURL.
private let _configuredBaseURLLock = NSLock()
private var _configuredBaseURLValue: String = ""

@MainActor
public final class AuthService {
public static let shared = AuthService()
Expand All @@ -20,7 +27,29 @@ public final class AuthService {
/// Platform base URL from daemon config. Set by SettingsStore when the
/// `platform_config_response` arrives. When non-empty, takes precedence
/// over persisted defaults, but an explicit per-launch env override still wins.
public var configuredBaseURL: String = ""
///
/// Backed by a lock-protected static so that `GatewayHTTPClient` (nonisolated)
/// can read the value without crossing into `@MainActor` isolation.
public var configuredBaseURL: String {
get {
_configuredBaseURLLock.lock()
defer { _configuredBaseURLLock.unlock() }
return _configuredBaseURLValue
}
set {
_configuredBaseURLLock.lock()
defer { _configuredBaseURLLock.unlock() }
_configuredBaseURLValue = newValue
}
}

/// Read the current configured base URL from any isolation context.
/// Uses lock-based synchronization — safe to call from nonisolated code.
nonisolated static var currentConfiguredBaseURL: String {
_configuredBaseURLLock.lock()
defer { _configuredBaseURLLock.unlock() }
return _configuredBaseURLValue
}

public var baseURL: String {
Self.resolveBaseURL(
Expand All @@ -32,7 +61,9 @@ public final class AuthService {

private init() {}

static func resolveBaseURL(
/// Pure URL resolution logic — safe to call from any isolation context.
/// All inputs are value types; no mutable shared state is accessed.
nonisolated static func resolveBaseURL(
configuredBaseURL: String,
environment: [String: String],
userDefaults: UserDefaults
Expand All @@ -52,7 +83,7 @@ public final class AuthService {
return defaultBaseURL
}

private static func normalizedBaseURL(_ raw: String?) -> String? {
nonisolated private static func normalizedBaseURL(_ raw: String?) -> String? {
let trimmed = raw?.trimmingCharacters(in: .whitespacesAndNewlines) ?? ""
let normalized = trimmed.replacingOccurrences(of: "/+$", with: "", options: .regularExpression)
return normalized.isEmpty ? nil : normalized
Expand Down
2 changes: 0 additions & 2 deletions clients/shared/Network/AppsClient.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ private let log = Logger(subsystem: Bundle.main.bundleIdentifier ?? "com.vellum.
///
/// Covers listing, opening, deleting, previewing, bundling, sharing, and
/// version history for both local and shared apps.
@MainActor
public protocol AppsClientProtocol {
func fetchAppsList() async -> AppsListResponse?
func openApp(id: String) async -> AppOpenResult?
Expand Down Expand Up @@ -36,7 +35,6 @@ public struct AppOpenResult: Sendable {
}

/// Gateway-backed implementation of ``AppsClientProtocol``.
@MainActor
public struct AppsClient: AppsClientProtocol {
nonisolated public init() {}

Expand Down
2 changes: 0 additions & 2 deletions clients/shared/Network/BtwClient.swift
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,11 @@ import os
private let log = Logger(subsystem: Bundle.main.bundleIdentifier ?? "com.vellum.vellum-assistant", category: "BtwClient")

/// Focused client for BTW side-chain messages routed through the gateway.
@MainActor
public protocol BtwClientProtocol {
func sendMessage(content: String, conversationKey: String) -> AsyncThrowingStream<String, Error>
}

/// Gateway-backed implementation of ``BtwClientProtocol``.
@MainActor
public struct BtwClient: BtwClientProtocol {
nonisolated public init() {}

Expand Down
2 changes: 0 additions & 2 deletions clients/shared/Network/ChannelClient.swift
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import os
private let log = Logger(subsystem: Bundle.main.bundleIdentifier ?? "com.vellum.vellum-assistant", category: "ChannelClient")

/// Focused client for channel readiness operations routed through the gateway.
@MainActor
public protocol ChannelClientProtocol {
func fetchChannelReadiness() async -> [String: ChannelReadinessInfo]
}
Expand Down Expand Up @@ -45,7 +44,6 @@ public struct ReadinessCheck: Sendable {
}

/// Gateway-backed implementation of ``ChannelClientProtocol``.
@MainActor
public struct ChannelClient: ChannelClientProtocol {
nonisolated public init() {}

Expand Down
2 changes: 0 additions & 2 deletions clients/shared/Network/ComputerUseClient.swift
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,12 @@ import os
private let log = Logger(subsystem: Bundle.main.bundleIdentifier ?? "com.vellum.vellum-assistant", category: "ComputerUseClient")

/// Focused client for watch observation and recording lifecycle operations via the gateway.
@MainActor
public protocol ComputerUseClientProtocol {
func sendWatchObservation(_ msg: WatchObservationMessage) async -> Bool
func sendRecordingStatus(_ msg: RecordingStatus) async -> Bool
}

/// Gateway-backed implementation of ``ComputerUseClientProtocol``.
@MainActor
public struct ComputerUseClient: ComputerUseClientProtocol {
nonisolated public init() {}

Expand Down
2 changes: 0 additions & 2 deletions clients/shared/Network/ContactClient.swift
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import os
private let log = Logger(subsystem: Bundle.main.bundleIdentifier ?? "com.vellum.vellum-assistant", category: "ContactClient")

/// Focused client for contact management operations routed through the gateway.
@MainActor
public protocol ContactClientProtocol {
func updateContact(
contactId: String,
Expand Down Expand Up @@ -51,7 +50,6 @@ public struct NewContactChannel: Codable {
}

/// Gateway-backed implementation of ``ContactClientProtocol``.
@MainActor
public struct ContactClient: ContactClientProtocol {
nonisolated public init() {}

Expand Down
2 changes: 0 additions & 2 deletions clients/shared/Network/ConversationClient.swift
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,11 @@ import os
private let log = Logger(subsystem: Bundle.main.bundleIdentifier ?? "com.vellum.vellum-assistant", category: "ConversationClient")

/// Focused client for conversation-related operations routed through the gateway.
@MainActor
public protocol ConversationClientProtocol {
func fetchMessageContent(conversationId: String, messageId: String) async -> MessageContentResponse?
}

/// Gateway-backed implementation of ``ConversationClientProtocol``.
@MainActor
public struct ConversationClient: ConversationClientProtocol {
nonisolated public init() {}

Expand Down
2 changes: 0 additions & 2 deletions clients/shared/Network/ConversationDetailClient.swift
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,11 @@ import os
private let log = Logger(subsystem: Bundle.main.bundleIdentifier ?? "com.vellum.vellum-assistant", category: "ConversationDetailClient")

/// Focused client for fetching a single conversation summary through the gateway.
@MainActor
public protocol ConversationDetailClientProtocol {
func fetchConversation(conversationId: String) async -> ConversationListResponseItem?
}

/// Gateway-backed implementation of ``ConversationDetailClientProtocol``.
@MainActor
public struct ConversationDetailClient: ConversationDetailClientProtocol {
nonisolated public init() {}

Expand Down
2 changes: 0 additions & 2 deletions clients/shared/Network/ConversationForkClient.swift
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,11 @@ import os
private let log = Logger(subsystem: Bundle.main.bundleIdentifier ?? "com.vellum.vellum-assistant", category: "ConversationForkClient")

/// Focused client for creating conversation forks through the gateway.
@MainActor
public protocol ConversationForkClientProtocol {
func forkConversation(conversationId: String, throughMessageId: String?) async -> ConversationListResponseItem?
}

/// Gateway-backed implementation of ``ConversationForkClientProtocol``.
@MainActor
public struct ConversationForkClient: ConversationForkClientProtocol {
nonisolated public init() {}

Expand Down
2 changes: 0 additions & 2 deletions clients/shared/Network/ConversationHistoryClient.swift
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,11 @@ private let log = Logger(subsystem: Bundle.main.bundleIdentifier ?? "com.vellum.
private let perfLog = OSLog(subsystem: Bundle.main.bundleIdentifier ?? "com.vellum.vellum-assistant", category: .pointsOfInterest)

/// Focused client for conversation history operations routed through the gateway.
@MainActor
public protocol ConversationHistoryClientProtocol {
func fetchHistory(conversationId: String, limit: Int?, beforeTimestamp: Double?, mode: String?, maxTextChars: Int?, maxToolResultChars: Int?) async -> HistoryResponse?
}

/// Gateway-backed implementation of ``ConversationHistoryClientProtocol``.
@MainActor
public struct ConversationHistoryClient: ConversationHistoryClientProtocol {
nonisolated public init() {}

Expand Down
2 changes: 0 additions & 2 deletions clients/shared/Network/ConversationListClient.swift
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import os
private let log = Logger(subsystem: Bundle.main.bundleIdentifier ?? "com.vellum.vellum-assistant", category: "ConversationListClient")

/// Focused client for conversation list and management operations via the gateway.
@MainActor
public protocol ConversationListClientProtocol {
func fetchConversationList(offset: Int, limit: Int, conversationType: String?) async -> ConversationListResponse?
func switchConversation(conversationId: String) async -> Bool
Expand All @@ -18,7 +17,6 @@ public protocol ConversationListClientProtocol {
}

/// Gateway-backed implementation of ``ConversationListClientProtocol``.
@MainActor
public struct ConversationListClient: ConversationListClientProtocol {
nonisolated public init() {}

Expand Down
2 changes: 0 additions & 2 deletions clients/shared/Network/ConversationQueueClient.swift
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,11 @@ import os
private let log = Logger(subsystem: Bundle.main.bundleIdentifier ?? "com.vellum.vellum-assistant", category: "ConversationQueueClient")

/// Focused client for message queue operations routed through the gateway.
@MainActor
public protocol ConversationQueueClientProtocol {
func deleteQueuedMessage(conversationId: String, requestId: String) async -> Bool
}

/// Gateway-backed implementation of ``ConversationQueueClientProtocol``.
@MainActor
public struct ConversationQueueClient: ConversationQueueClientProtocol {
nonisolated public init() {}

Expand Down
2 changes: 0 additions & 2 deletions clients/shared/Network/ConversationUnreadClient.swift
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,11 @@ public enum ConversationUnreadError: LocalizedError {
}

/// Focused client for marking conversations as unread through the gateway.
@MainActor
public protocol ConversationUnreadClientProtocol {
func sendConversationUnread(_ signal: ConversationUnreadSignal) async throws
}

/// Gateway-backed implementation of ``ConversationUnreadClientProtocol``.
@MainActor
public struct ConversationUnreadClient: ConversationUnreadClientProtocol {
nonisolated public init() {}

Expand Down
2 changes: 0 additions & 2 deletions clients/shared/Network/DiagnosticsClient.swift
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,11 @@ import os
private let log = Logger(subsystem: Bundle.main.bundleIdentifier ?? "com.vellum.vellum-assistant", category: "DiagnosticsClient")

/// Focused client for diagnostics operations routed through the gateway.
@MainActor
public protocol DiagnosticsClientProtocol {
func fetchEnvVars() async -> EnvVarsResponseMessage?
}

/// Gateway-backed implementation of ``DiagnosticsClientProtocol``.
@MainActor
public struct DiagnosticsClient: DiagnosticsClientProtocol {
nonisolated public init() {}

Expand Down
2 changes: 0 additions & 2 deletions clients/shared/Network/DictationClient.swift
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,11 @@ import os
private let log = Logger(subsystem: Bundle.main.bundleIdentifier ?? "com.vellum.vellum-assistant", category: "DictationClient")

/// Focused client for dictation requests routed through the gateway.
@MainActor
public protocol DictationClientProtocol {
func process(_ request: DictationRequest) async -> DictationResponseMessage
}

/// Gateway-backed implementation of ``DictationClientProtocol``.
@MainActor
public struct DictationClient: DictationClientProtocol {
nonisolated public init() {}

Expand Down
2 changes: 0 additions & 2 deletions clients/shared/Network/DocumentClient.swift
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,13 @@ import os
private let log = Logger(subsystem: Bundle.main.bundleIdentifier ?? "com.vellum.vellum-assistant", category: "DocumentClient")

/// Focused client for document persistence operations routed through the gateway.
@MainActor
public protocol DocumentClientProtocol {
func fetchList(conversationId: String?) async -> DocumentListResponse?
func fetchDocument(surfaceId: String) async -> DocumentLoadResponse?
func saveDocument(surfaceId: String, conversationId: String, title: String, content: String, wordCount: Int) async -> DocumentSaveResponse?
}

/// Gateway-backed implementation of ``DocumentClientProtocol``.
@MainActor
public struct DocumentClient: DocumentClientProtocol {
nonisolated public init() {}

Expand Down
2 changes: 0 additions & 2 deletions clients/shared/Network/FeatureFlagClient.swift
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import os
private let log = Logger(subsystem: Bundle.main.bundleIdentifier ?? "com.vellum.vellum-assistant", category: "FeatureFlagClient")

/// Focused client for feature-flag and privacy-config operations routed through the gateway.
@MainActor
public protocol FeatureFlagClientProtocol {
func getFeatureFlags() async throws -> [AssistantFeatureFlag]
func setFeatureFlag(key: String, enabled: Bool) async throws
Expand Down Expand Up @@ -69,7 +68,6 @@ public enum FeatureFlagError: Error, LocalizedError {
// MARK: - Gateway-Backed Implementation

/// Gateway-backed implementation of ``FeatureFlagClientProtocol``.
@MainActor
public struct FeatureFlagClient: FeatureFlagClientProtocol {
nonisolated public init() {}

Expand Down
16 changes: 14 additions & 2 deletions clients/shared/Network/GatewayHTTPClient.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ private let log = Logger(subsystem: Bundle.main.bundleIdentifier ?? "com.vellum.
///
/// let response = try await GatewayHTTPClient.get(path: "health")
/// let response = try await GatewayHTTPClient.post(path: "assistants/upgrade")
@MainActor
public enum GatewayHTTPClient {

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🚩 This is the second attempt at this change — the first was reverted

Commit 4164c83 explicitly reverted the previous attempt (PR #21695, "Move network layer off @mainactor isolation") just hours before this PR. The revert also touched GatewayConnectionManager.swift, HostToolExecutor.swift, and InteractionClient.swift — files not modified in this PR. This suggests the current PR may be an incomplete re-application of the original change, or those files were intentionally excluded. Worth verifying that the omission of changes to GatewayConnectionManager, HostToolExecutor, and InteractionClient is intentional and doesn't leave an inconsistent isolation state.

Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.


/// Response from a gateway HTTP request.
Expand Down Expand Up @@ -383,7 +382,20 @@ public enum GatewayHTTPClient {
guard let token = SessionTokenManager.getToken(), !token.isEmpty else {
throw ClientError.notAuthenticated
}
let baseURL = assistant.runtimeUrl ?? AuthService.shared.baseURL
let baseURL: String
if let runtimeUrl = assistant.runtimeUrl {
baseURL = runtimeUrl
} else {
// Call the nonisolated pure function directly to avoid
// crossing into @MainActor isolation. The instance property
// `AuthService.shared.baseURL` is @MainActor-isolated and
// cannot be read from a nonisolated synchronous context.
baseURL = AuthService.resolveBaseURL(
configuredBaseURL: AuthService.currentConfiguredBaseURL,
environment: ProcessInfo.processInfo.environment,
userDefaults: .standard
)
}
Comment thread
devin-ai-integration[bot] marked this conversation as resolved.
return ConnectionInfo(baseURL: baseURL, authHeader: ("X-Session-Token", token), assistantId: assistant.assistantId, isManaged: true)
} else {
let token = ActorTokenManager.getToken()
Expand Down
2 changes: 0 additions & 2 deletions clients/shared/Network/GuardianClient.swift
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,13 @@ import os
private let log = Logger(subsystem: Bundle.main.bundleIdentifier ?? "com.vellum.vellum-assistant", category: "GuardianClient")

/// Focused client for guardian operations routed through the gateway.
@MainActor
public protocol GuardianClientProtocol {
func fetchPendingActions(conversationId: String) async -> GuardianActionsPendingResponseMessage?
func submitDecision(requestId: String, action: String, conversationId: String?) async -> GuardianActionDecisionResponseMessage?
func bootstrapActorToken(platform: String, deviceId: String) async -> Bool
}

/// Gateway-backed implementation of ``GuardianClientProtocol``.
@MainActor
public struct GuardianClient: GuardianClientProtocol {
nonisolated public init() {}

Expand Down
1 change: 0 additions & 1 deletion clients/shared/Network/HealthCheckClient.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ private let log = Logger(subsystem: Bundle.main.bundleIdentifier ?? "com.vellum.
/// Local assistants are checked by hitting their own gateway's `/readyz` endpoint
/// directly (unauthenticated). Remote/managed assistants route through
/// `GatewayHTTPClient` which handles URL resolution, authentication, and 401 retry.
@MainActor
public enum HealthCheckClient {

/// Check whether the currently connected assistant is reachable.
Expand Down
2 changes: 0 additions & 2 deletions clients/shared/Network/HeartbeatClient.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ private let log = Logger(subsystem: Bundle.main.bundleIdentifier ?? "com.vellum.
///
/// Covers heartbeat configuration, run history, on-demand runs, and checklist
/// management.
@MainActor
public protocol HeartbeatClientProtocol {
func fetchConfig() async -> HeartbeatConfigResponse?
func updateConfig(enabled: Bool?, intervalMs: Double?, activeHoursStart: Double?, activeHoursEnd: Double?) async -> HeartbeatConfigResponse?
Expand All @@ -18,7 +17,6 @@ public protocol HeartbeatClientProtocol {
}

/// Gateway-backed implementation of ``HeartbeatClientProtocol``.
@MainActor
public struct HeartbeatClient: HeartbeatClientProtocol {
nonisolated public init() {}

Expand Down
2 changes: 0 additions & 2 deletions clients/shared/Network/HostProxyClient.swift
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,13 @@ import os
private let log = Logger(subsystem: Bundle.main.bundleIdentifier ?? "com.vellum.vellum-assistant", category: "HostProxyClient")

/// Focused client for posting host proxy execution results back to the gateway.
@MainActor
public protocol HostProxyClientProtocol {
func postBashResult(_ result: HostBashResultPayload) async -> Bool
func postFileResult(_ result: HostFileResultPayload) async -> Bool
func postCuResult(_ result: HostCuResultPayload) async -> Bool
}

/// Gateway-backed implementation of ``HostProxyClientProtocol``.
@MainActor
public struct HostProxyClient: HostProxyClientProtocol {
nonisolated public init() {}

Expand Down
Loading