import CodexBarCoreimport Foundation extension SettingsStore { private var codexPersistedActiveSource: CodexActiveSource { self.providerConfig(for: .codex)?.codexActiveSource ?? .liveSystem } private enum ManagedCodexAccountStoreState { case none case selected(ManagedCodexAccount) case unreadable } private static func failClosedManagedCodexHomePath(fileManager: FileManager = .default) -> String { ManagedCodexHomeFactory.defaultRootURL(fileManager: fileManager) .appendingPathComponent("managed-store-unreadable", isDirectory: true) .path } private func loadManagedCodexAccounts() throws -> ManagedCodexAccountSet { #if DEBUG if CodexManagedRemoteHomeTestingOverride.isUnreadable(for: self) { throw CodexManagedRemoteHomeTestingOverrideError.unreadableManagedStore } if let override = CodexManagedRemoteHomeTestingOverride.account(for: self) { return ManagedCodexAccountSet( version: FileManagedCodexAccountStore.currentVersion, accounts: [override]) } let store = if let storeURL = CodexManagedRemoteHomeTestingOverride.managedStoreURL(for: self) { FileManagedCodexAccountStore(fileURL: storeURL) } else { FileManagedCodexAccountStore() } #else let store = FileManagedCodexAccountStore() #endif return try store.loadAccounts() } private func managedCodexAccountStoreState( activeSource: CodexActiveSource? = nil) -> ManagedCodexAccountStoreState { let source = activeSource ?? self.codexResolvedActiveSource guard case let .managedAccount(id) = source else { return .none } do { let accounts = try self.loadManagedCodexAccounts() guard let account = accounts.account(id: id) else { return .none } return .selected(account) } catch { return .unreadable } } var activeManagedCodexAccount: ManagedCodexAccount? { guard case let .selected(account) = self.managedCodexAccountStoreState() else { return nil } return account } var activeManagedCodexRemoteHomePath: String? { self.managedCodexRemoteHomePath(forActiveSource: self.codexResolvedActiveSource) } func liveSystemCodexHomePath(forActiveSource source: CodexActiveSource) -> String? { guard source == .liveSystem else { return nil } let path = self.codexAccountReconciliationSnapshot(activeSourceOverride: source) .liveSystemAccount?.codexHomePath.trimmingCharacters(in: .whitespacesAndNewlines) guard let path, !path.isEmpty else { return nil } return path } func managedCodexRemoteHomePath(forActiveSource source: CodexActiveSource) -> String? { guard case let .managedAccount(id) = source else { return nil } #if DEBUG if let override = CodexManagedRemoteHomeTestingOverride.homePath(for: self) { return override } #endif do { let accounts = try self.loadManagedCodexAccounts() // A selected managed source must never fall back to ambient ~/.codex. return accounts.account(id: id)?.managedHomePath ?? Self.failClosedManagedCodexHomePath() } catch { return Self.failClosedManagedCodexHomePath() } } var activeManagedCodexCookieCacheScope: CookieHeaderCache.Scope? { switch self.managedCodexAccountStoreState() { case let .selected(account): .managedAccount(account.id) case .unreadable: .managedStoreUnreadable case .none: nil } } var hasUnreadableManagedCodexAccountStore: Bool { self.codexAccountReconciliationSnapshot.hasUnreadableAddedAccountStore } var codexUsageDataSource: CodexUsageDataSource { get { let source = self.configSnapshot.providerConfig(for: .codex)?.source return Self.codexUsageDataSource(from: source) } set { let source: ProviderSourceMode? = switch newValue { case .auto: .auto case .oauth: .oauth case .cli: .cli } self.updateProviderConfig(provider: .codex) { entry in entry.source = source } self.logProviderModeChange(provider: .codex, field: "usageSource", value: newValue.rawValue) } } var codexActiveSource: CodexActiveSource { get { self.codexPersistedActiveSource } set { self.invalidateCodexAccountReconciliationSnapshotCache() self.cachedCodexAccountMenuProjection = nil self.updateProviderConfig(provider: .codex) { entry in entry.codexActiveSource = newValue } } } var codexResolvedActiveSource: CodexActiveSource { self.codexResolvedActiveSourceState.resolvedSource } var codexResolvedActiveSourceState: CodexResolvedActiveSource { CodexActiveSourceResolver.resolve(from: self.codexAccountReconciliationSnapshot) } @discardableResult func persistResolvedCodexActiveSourceCorrectionIfNeeded() -> Bool { let resolution = self.codexResolvedActiveSourceState guard resolution.requiresPersistenceCorrection else { return false } self.codexActiveSource = resolution.resolvedSource return true } @discardableResult func refreshCodexAccountReconciliationAfterManagedAccountsDidChange() -> Bool { self.invalidateCodexAccountReconciliationSnapshotCache() self.cachedCodexAccountMenuProjection = nil return self.persistResolvedCodexActiveSourceCorrectionIfNeeded() } var codexCookieHeader: String { get { self.configSnapshot.providerConfig(for: .codex)?.sanitizedCookieHeader ?? "" } set { // This is intentionally provider-scoped today. A per-managed-account manual cookie override would need // its own storage and UI semantics so editing one account's header does not silently rewrite another's. self.updateProviderConfig(provider: .codex) { entry in entry.cookieHeader = self.normalizedConfigValue(newValue) } self.logSecretUpdate(provider: .codex, field: "cookieHeader", value: newValue) } } var codexCookieSource: ProviderCookieSource { get { let resolved = self.resolvedCookieSource(provider: .codex, fallback: .auto) return self.openAIWebAccessEnabled ? resolved : .off } set { self.updateProviderConfig(provider: .codex) { entry in entry.cookieSource = newValue } self.logProviderModeChange(provider: .codex, field: "cookieSource", value: newValue.rawValue) self.openAIWebAccessEnabled = newValue.isEnabled } } func ensureCodexCookieLoaded() {}} extension SettingsStore { private static var codexAccountReconciliationSnapshotCacheInterval: TimeInterval { #if DEBUG if let codexAccountReconciliationSnapshotCacheIntervalOverrideForTesting { return codexAccountReconciliationSnapshotCacheIntervalOverrideForTesting } #endif return self.isRunningTests ? 0 : self.productionCodexAccountReconciliationSnapshotCacheInterval } func invalidateCodexAccountReconciliationSnapshotCache() { self.cachedCodexAccountReconciliationSnapshot = nil self.codexAccountReconciliationGeneration &+= 1 } var codexAccountReconciliationSnapshot: CodexAccountReconciliationSnapshot { self.codexAccountReconciliationSnapshot(activeSourceOverride: nil) } func codexAccountReconciliationSnapshot( activeSourceOverride: CodexActiveSource?) -> CodexAccountReconciliationSnapshot { let activeSource = activeSourceOverride ?? self.codexPersistedActiveSource let cacheInterval = Self.codexAccountReconciliationSnapshotCacheInterval let now = Date() if cacheInterval > 0, let cached = self.cachedCodexAccountReconciliationSnapshot, cached.activeSource == activeSource, now.timeIntervalSince(cached.loadedAt) < cacheInterval { return cached.snapshot } let snapshot = self.codexAccountSnapshotLoader(activeSource: activeSource)() let loadedAt = Date() if cacheInterval > 0 { self.cachedCodexAccountReconciliationSnapshot = CachedCodexAccountReconciliationSnapshot( activeSource: activeSource, loadedAt: loadedAt, snapshot: snapshot) } if activeSource == self.codexPersistedActiveSource { self.cachedCodexAccountMenuProjection = CachedCodexAccountMenuProjection( activeSource: activeSource, loadedAt: loadedAt, projection: CodexVisibleAccountProjection.make(from: snapshot)) } return snapshot } /// Menu rendering must stay side-effect free: no `auth.json` reads, JWT parsing, or fingerprint hashing. var codexVisibleAccountProjectionForMenuDisplay: CodexVisibleAccountProjection? { let activeSource = self.codexPersistedActiveSource guard let cached = self.cachedCodexAccountMenuProjection, cached.activeSource == activeSource else { return nil } return cached.projection } var codexAccountMenuProjectionNeedsRevalidation: Bool { let activeSource = self.codexPersistedActiveSource guard let cached = self.cachedCodexAccountMenuProjection, cached.activeSource == activeSource else { return true } return Date().timeIntervalSince(cached.loadedAt) >= Self.codexAccountReconciliationSnapshotCacheInterval } func revalidateCodexAccountMenuProjection() async -> CodexAccountMenuProjectionRevalidationResult { guard self.codexAccountMenuProjectionNeedsRevalidation else { return .skipped } let activeSource = self.codexPersistedActiveSource let generation = self.codexAccountReconciliationGeneration let loader = self.codexAccountSnapshotLoader(activeSource: activeSource) let snapshot = await Self.loadCodexAccountSnapshot(loader) guard generation == self.codexAccountReconciliationGeneration, activeSource == self.codexPersistedActiveSource else { return .discarded } let now = Date() let projection = CodexVisibleAccountProjection.make(from: snapshot) let previousProjection = self.cachedCodexAccountMenuProjection.flatMap { cached in cached.activeSource == activeSource ? cached.projection : nil } self.cachedCodexAccountMenuProjection = CachedCodexAccountMenuProjection( activeSource: activeSource, loadedAt: now, projection: projection) if Self.codexAccountReconciliationSnapshotCacheInterval > 0 { self.cachedCodexAccountReconciliationSnapshot = CachedCodexAccountReconciliationSnapshot( activeSource: activeSource, loadedAt: now, snapshot: snapshot) } return previousProjection == projection ? .unchanged : .updated } @concurrent private nonisolated static func loadCodexAccountSnapshot( _ loader: @escaping @Sendable () -> CodexAccountReconciliationSnapshot) async -> CodexAccountReconciliationSnapshot { loader() } var codexVisibleAccountProjection: CodexVisibleAccountProjection { CodexVisibleAccountProjection.make(from: self.codexAccountReconciliationSnapshot) } var codexVisibleAccounts: [CodexVisibleAccount] { self.codexVisibleAccountProjection.visibleAccounts } @discardableResult func selectCodexVisibleAccount(id: String) -> Bool { guard let source = self.codexSource(forVisibleAccountID: id) else { return false } self.invalidateCodexAccountReconciliationSnapshotCache() self.codexActiveSource = source return true } func selectDisplayedCodexVisibleAccount(_ account: CodexVisibleAccount) { // The row already carries the exact source it represented. Re-resolving its ID would synchronously // reload auth state from the menu click callback and can also fail after a stale snapshot is rendered. self.codexActiveSource = account.selectionSource } func selectAuthenticatedManagedCodexAccount(_ account: ManagedCodexAccount) { if let visibleAccountID = self.codexVisibleAccountProjection.visibleAccounts .first(where: { $0.storedAccountID == account.id })? .id, self.selectCodexVisibleAccount(id: visibleAccountID) { return } self.invalidateCodexAccountReconciliationSnapshotCache() self.codexActiveSource = .managedAccount(id: account.id) _ = self.persistResolvedCodexActiveSourceCorrectionIfNeeded() } func codexSource(forVisibleAccountID id: String) -> CodexActiveSource? { self.codexVisibleAccountProjection.source(forVisibleAccountID: id) } private func codexAccountSnapshotLoader( activeSource: CodexActiveSource) -> @Sendable () -> CodexAccountReconciliationSnapshot { #if DEBUG if let loader = self._test_codexAccountSnapshotLoader { return { loader(activeSource) } } #endif let reconciler = self.codexAccountReconciler(activeSource: activeSource) return { reconciler.loadSnapshot() } } private func codexAccountReconciler(activeSource: CodexActiveSource) -> DefaultCodexAccountReconciler { let baseEnvironment = self.codexReconciliationEnvironment() #if DEBUG let liveSystemAccountOverride = CodexManagedRemoteHomeTestingOverride.liveSystemAccount(for: self) let reconciliationEnvironmentOverride = CodexManagedRemoteHomeTestingOverride .reconciliationEnvironment(for: self) let managedAccountOverride = CodexManagedRemoteHomeTestingOverride.account(for: self) let managedStoreURLOverride = CodexManagedRemoteHomeTestingOverride.managedStoreURL(for: self) let unreadableStoreOverride = CodexManagedRemoteHomeTestingOverride.isUnreadable(for: self) guard CodexManagedRemoteHomeTestingOverride.hasAnyOverride(for: self) else { return DefaultCodexAccountReconciler( activeSource: activeSource, baseEnvironment: baseEnvironment, managedEnvironmentBuilder: { environment, account in CodexHomeScope.scopedEnvironment(base: environment, codexHome: account.managedHomePath) }) } let storeLoader: @Sendable () throws -> ManagedCodexAccountSet if unreadableStoreOverride { storeLoader = { throw CodexManagedRemoteHomeTestingOverrideError.unreadableManagedStore } } else if let managedAccountOverride { let accounts = ManagedCodexAccountSet( version: FileManagedCodexAccountStore.currentVersion, accounts: [managedAccountOverride]) storeLoader = { accounts } } else if let managedStoreURLOverride { let store = FileManagedCodexAccountStore(fileURL: managedStoreURLOverride) storeLoader = { try store.loadAccounts() } } else { let accounts = ManagedCodexAccountSet( version: FileManagedCodexAccountStore.currentVersion, accounts: []) storeLoader = { accounts } } return DefaultCodexAccountReconciler( storeLoader: storeLoader, systemObserver: CodexManagedRemoteHomeTestingSystemObserver( overrideAccount: liveSystemAccountOverride, usesInjectedEnvironment: reconciliationEnvironmentOverride != nil), activeSource: activeSource, baseEnvironment: baseEnvironment, managedEnvironmentBuilder: { environment, account in CodexHomeScope.scopedEnvironment(base: environment, codexHome: account.managedHomePath) }) #else return DefaultCodexAccountReconciler( activeSource: activeSource, baseEnvironment: baseEnvironment, managedEnvironmentBuilder: { environment, account in CodexHomeScope.scopedEnvironment(base: environment, codexHome: account.managedHomePath) }) #endif } private func codexReconciliationEnvironment() -> [String: String] { #if DEBUG if let override = CodexManagedRemoteHomeTestingOverride.reconciliationEnvironment(for: self) { return override } #endif return ProcessInfo.processInfo.environment }} #if DEBUGprivate enum CodexManagedRemoteHomeTestingOverride { private struct Override { var account: ManagedCodexAccount? var homePath: String? var unreadableStore: Bool = false var managedStoreURL: URL? var liveSystemAccount: ObservedSystemCodexAccount? var reconciliationEnvironment: [String: String]? var isEmpty: Bool { self.account == nil && self.homePath == nil && self.unreadableStore == false && self .managedStoreURL == nil && self.liveSystemAccount == nil && self .reconciliationEnvironment == nil } } private final class Entry { weak var settings: SettingsStore? var overrideValue: Override init(settings: SettingsStore, overrideValue: Override) { self.settings = settings self.overrideValue = overrideValue } } @MainActor private static var values: [ObjectIdentifier: Entry] = [:] @MainActor private static func entry(for settings: SettingsStore) -> Entry? { let key = ObjectIdentifier(settings) guard let entry = self.values[key] else { return nil } guard let storedSettings = entry.settings, storedSettings === settings else { self.values.removeValue(forKey: key) return nil } return entry } @MainActor static func account(for settings: SettingsStore) -> ManagedCodexAccount? { self.entry(for: settings)?.overrideValue.account } @MainActor static func setAccount(_ account: ManagedCodexAccount?, for settings: SettingsStore) { let key = ObjectIdentifier(settings) var override = self.entry(for: settings)?.overrideValue ?? Override() override.account = account if override.isEmpty { self.values.removeValue(forKey: key) } else { self.values[key] = Entry(settings: settings, overrideValue: override) } } @MainActor static func homePath(for settings: SettingsStore) -> String? { self.entry(for: settings)?.overrideValue.homePath } @MainActor static func setHomePath(_ value: String?, for settings: SettingsStore) { let key = ObjectIdentifier(settings) var override = self.entry(for: settings)?.overrideValue ?? Override() override.homePath = value if override.isEmpty {Lines 1–500 of 711. Load more
import CodexBarCore import Foundation extension SettingsStore { private var codexPersistedActiveSource: CodexActiveSource { self.providerConfig(for: .codex)?.codexActiveSource ?? .liveSystem } private enum ManagedCodexAccountStoreState { case none case selected(ManagedCodexAccount) case unreadable } private static func failClosedManagedCodexHomePath(fileManager: FileManager = .default) -> String { ManagedCodexHomeFactory.defaultRootURL(fileManager: fileManager) .appendingPathComponent("managed-store-unreadable", isDirectory: true) .path } private func loadManagedCodexAccounts() throws -> ManagedCodexAccountSet { #if DEBUG if CodexManagedRemoteHomeTestingOverride.isUnreadable(for: self) { throw CodexManagedRemoteHomeTestingOverrideError.unreadableManagedStore } if let override = CodexManagedRemoteHomeTestingOverride.account(for: self) { return ManagedCodexAccountSet( version: FileManagedCodexAccountStore.currentVersion, accounts: [override]) } let store = if let storeURL = CodexManagedRemoteHomeTestingOverride.managedStoreURL(for: self) { FileManagedCodexAccountStore(fileURL: storeURL) } else { FileManagedCodexAccountStore() } #else let store = FileManagedCodexAccountStore() #endif return try store.loadAccounts() } private func managedCodexAccountStoreState( activeSource: CodexActiveSource? = nil) -> ManagedCodexAccountStoreState { let source = activeSource ?? self.codexResolvedActiveSource guard case let .managedAccount(id) = source else { return .none } do { let accounts = try self.loadManagedCodexAccounts() guard let account = accounts.account(id: id) else { return .none } return .selected(account) } catch { return .unreadable } } var activeManagedCodexAccount: ManagedCodexAccount? { guard case let .selected(account) = self.managedCodexAccountStoreState() else { return nil } return account } var activeManagedCodexRemoteHomePath: String? { self.managedCodexRemoteHomePath(forActiveSource: self.codexResolvedActiveSource) } func liveSystemCodexHomePath(forActiveSource source: CodexActiveSource) -> String? { guard source == .liveSystem else { return nil } let path = self.codexAccountReconciliationSnapshot(activeSourceOverride: source) .liveSystemAccount?.codexHomePath.trimmingCharacters(in: .whitespacesAndNewlines) guard let path, !path.isEmpty else { return nil } return path } func managedCodexRemoteHomePath(forActiveSource source: CodexActiveSource) -> String? { guard case let .managedAccount(id) = source else { return nil } #if DEBUG if let override = CodexManagedRemoteHomeTestingOverride.homePath(for: self) { return override } #endif do { let accounts = try self.loadManagedCodexAccounts() // A selected managed source must never fall back to ambient ~/.codex. return accounts.account(id: id)?.managedHomePath ?? Self.failClosedManagedCodexHomePath() } catch { return Self.failClosedManagedCodexHomePath() } } var activeManagedCodexCookieCacheScope: CookieHeaderCache.Scope? { switch self.managedCodexAccountStoreState() { case let .selected(account): .managedAccount(account.id) case .unreadable: .managedStoreUnreadable case .none: nil } } var hasUnreadableManagedCodexAccountStore: Bool { self.codexAccountReconciliationSnapshot.hasUnreadableAddedAccountStore } var codexUsageDataSource: CodexUsageDataSource { get { let source = self.configSnapshot.providerConfig(for: .codex)?.source return Self.codexUsageDataSource(from: source) } set { let source: ProviderSourceMode? = switch newValue { case .auto: .auto case .oauth: .oauth case .cli: .cli } self.updateProviderConfig(provider: .codex) { entry in entry.source = source } self.logProviderModeChange(provider: .codex, field: "usageSource", value: newValue.rawValue) } } var codexActiveSource: CodexActiveSource { get { self.codexPersistedActiveSource } set { self.invalidateCodexAccountReconciliationSnapshotCache() self.cachedCodexAccountMenuProjection = nil self.updateProviderConfig(provider: .codex) { entry in entry.codexActiveSource = newValue } } } var codexResolvedActiveSource: CodexActiveSource { self.codexResolvedActiveSourceState.resolvedSource } var codexResolvedActiveSourceState: CodexResolvedActiveSource { CodexActiveSourceResolver.resolve(from: self.codexAccountReconciliationSnapshot) } @discardableResult func persistResolvedCodexActiveSourceCorrectionIfNeeded() -> Bool { let resolution = self.codexResolvedActiveSourceState guard resolution.requiresPersistenceCorrection else { return false } self.codexActiveSource = resolution.resolvedSource return true } @discardableResult func refreshCodexAccountReconciliationAfterManagedAccountsDidChange() -> Bool { self.invalidateCodexAccountReconciliationSnapshotCache() self.cachedCodexAccountMenuProjection = nil return self.persistResolvedCodexActiveSourceCorrectionIfNeeded() } var codexCookieHeader: String { get { self.configSnapshot.providerConfig(for: .codex)?.sanitizedCookieHeader ?? "" } set { // This is intentionally provider-scoped today. A per-managed-account manual cookie override would need // its own storage and UI semantics so editing one account's header does not silently rewrite another's. self.updateProviderConfig(provider: .codex) { entry in entry.cookieHeader = self.normalizedConfigValue(newValue) } self.logSecretUpdate(provider: .codex, field: "cookieHeader", value: newValue) } } var codexCookieSource: ProviderCookieSource { get { let resolved = self.resolvedCookieSource(provider: .codex, fallback: .auto) return self.openAIWebAccessEnabled ? resolved : .off } set { self.updateProviderConfig(provider: .codex) { entry in entry.cookieSource = newValue } self.logProviderModeChange(provider: .codex, field: "cookieSource", value: newValue.rawValue) self.openAIWebAccessEnabled = newValue.isEnabled } } func ensureCodexCookieLoaded() {} } extension SettingsStore { private static var codexAccountReconciliationSnapshotCacheInterval: TimeInterval { #if DEBUG if let codexAccountReconciliationSnapshotCacheIntervalOverrideForTesting { return codexAccountReconciliationSnapshotCacheIntervalOverrideForTesting } #endif return self.isRunningTests ? 0 : self.productionCodexAccountReconciliationSnapshotCacheInterval } func invalidateCodexAccountReconciliationSnapshotCache() { self.cachedCodexAccountReconciliationSnapshot = nil self.codexAccountReconciliationGeneration &+= 1 } var codexAccountReconciliationSnapshot: CodexAccountReconciliationSnapshot { self.codexAccountReconciliationSnapshot(activeSourceOverride: nil) } func codexAccountReconciliationSnapshot( activeSourceOverride: CodexActiveSource?) -> CodexAccountReconciliationSnapshot { let activeSource = activeSourceOverride ?? self.codexPersistedActiveSource let cacheInterval = Self.codexAccountReconciliationSnapshotCacheInterval let now = Date() if cacheInterval > 0, let cached = self.cachedCodexAccountReconciliationSnapshot, cached.activeSource == activeSource, now.timeIntervalSince(cached.loadedAt) < cacheInterval { return cached.snapshot } let snapshot = self.codexAccountSnapshotLoader(activeSource: activeSource)() let loadedAt = Date() if cacheInterval > 0 { self.cachedCodexAccountReconciliationSnapshot = CachedCodexAccountReconciliationSnapshot( activeSource: activeSource, loadedAt: loadedAt, snapshot: snapshot) } if activeSource == self.codexPersistedActiveSource { self.cachedCodexAccountMenuProjection = CachedCodexAccountMenuProjection( activeSource: activeSource, loadedAt: loadedAt, projection: CodexVisibleAccountProjection.make(from: snapshot)) } return snapshot } /// Menu rendering must stay side-effect free: no `auth.json` reads, JWT parsing, or fingerprint hashing. var codexVisibleAccountProjectionForMenuDisplay: CodexVisibleAccountProjection? { let activeSource = self.codexPersistedActiveSource guard let cached = self.cachedCodexAccountMenuProjection, cached.activeSource == activeSource else { return nil } return cached.projection } var codexAccountMenuProjectionNeedsRevalidation: Bool { let activeSource = self.codexPersistedActiveSource guard let cached = self.cachedCodexAccountMenuProjection, cached.activeSource == activeSource else { return true } return Date().timeIntervalSince(cached.loadedAt) >= Self.codexAccountReconciliationSnapshotCacheInterval } func revalidateCodexAccountMenuProjection() async -> CodexAccountMenuProjectionRevalidationResult { guard self.codexAccountMenuProjectionNeedsRevalidation else { return .skipped } let activeSource = self.codexPersistedActiveSource let generation = self.codexAccountReconciliationGeneration let loader = self.codexAccountSnapshotLoader(activeSource: activeSource) let snapshot = await Self.loadCodexAccountSnapshot(loader) guard generation == self.codexAccountReconciliationGeneration, activeSource == self.codexPersistedActiveSource else { return .discarded } let now = Date() let projection = CodexVisibleAccountProjection.make(from: snapshot) let previousProjection = self.cachedCodexAccountMenuProjection.flatMap { cached in cached.activeSource == activeSource ? cached.projection : nil } self.cachedCodexAccountMenuProjection = CachedCodexAccountMenuProjection( activeSource: activeSource, loadedAt: now, projection: projection) if Self.codexAccountReconciliationSnapshotCacheInterval > 0 { self.cachedCodexAccountReconciliationSnapshot = CachedCodexAccountReconciliationSnapshot( activeSource: activeSource, loadedAt: now, snapshot: snapshot) } return previousProjection == projection ? .unchanged : .updated } @concurrent private nonisolated static func loadCodexAccountSnapshot( _ loader: @escaping @Sendable () -> CodexAccountReconciliationSnapshot) async -> CodexAccountReconciliationSnapshot { loader() } var codexVisibleAccountProjection: CodexVisibleAccountProjection { CodexVisibleAccountProjection.make(from: self.codexAccountReconciliationSnapshot) } var codexVisibleAccounts: [CodexVisibleAccount] { self.codexVisibleAccountProjection.visibleAccounts } @discardableResult func selectCodexVisibleAccount(id: String) -> Bool { guard let source = self.codexSource(forVisibleAccountID: id) else { return false } self.invalidateCodexAccountReconciliationSnapshotCache() self.codexActiveSource = source return true } func selectDisplayedCodexVisibleAccount(_ account: CodexVisibleAccount) { // The row already carries the exact source it represented. Re-resolving its ID would synchronously // reload auth state from the menu click callback and can also fail after a stale snapshot is rendered. self.codexActiveSource = account.selectionSource } func selectAuthenticatedManagedCodexAccount(_ account: ManagedCodexAccount) { if let visibleAccountID = self.codexVisibleAccountProjection.visibleAccounts .first(where: { $0.storedAccountID == account.id })? .id, self.selectCodexVisibleAccount(id: visibleAccountID) { return } self.invalidateCodexAccountReconciliationSnapshotCache() self.codexActiveSource = .managedAccount(id: account.id) _ = self.persistResolvedCodexActiveSourceCorrectionIfNeeded() } func codexSource(forVisibleAccountID id: String) -> CodexActiveSource? { self.codexVisibleAccountProjection.source(forVisibleAccountID: id) } private func codexAccountSnapshotLoader( activeSource: CodexActiveSource) -> @Sendable () -> CodexAccountReconciliationSnapshot { #if DEBUG if let loader = self._test_codexAccountSnapshotLoader { return { loader(activeSource) } } #endif let reconciler = self.codexAccountReconciler(activeSource: activeSource) return { reconciler.loadSnapshot() } } private func codexAccountReconciler(activeSource: CodexActiveSource) -> DefaultCodexAccountReconciler { let baseEnvironment = self.codexReconciliationEnvironment() #if DEBUG let liveSystemAccountOverride = CodexManagedRemoteHomeTestingOverride.liveSystemAccount(for: self) let reconciliationEnvironmentOverride = CodexManagedRemoteHomeTestingOverride .reconciliationEnvironment(for: self) let managedAccountOverride = CodexManagedRemoteHomeTestingOverride.account(for: self) let managedStoreURLOverride = CodexManagedRemoteHomeTestingOverride.managedStoreURL(for: self) let unreadableStoreOverride = CodexManagedRemoteHomeTestingOverride.isUnreadable(for: self) guard CodexManagedRemoteHomeTestingOverride.hasAnyOverride(for: self) else { return DefaultCodexAccountReconciler( activeSource: activeSource, baseEnvironment: baseEnvironment, managedEnvironmentBuilder: { environment, account in CodexHomeScope.scopedEnvironment(base: environment, codexHome: account.managedHomePath) }) } let storeLoader: @Sendable () throws -> ManagedCodexAccountSet if unreadableStoreOverride { storeLoader = { throw CodexManagedRemoteHomeTestingOverrideError.unreadableManagedStore } } else if let managedAccountOverride { let accounts = ManagedCodexAccountSet( version: FileManagedCodexAccountStore.currentVersion, accounts: [managedAccountOverride]) storeLoader = { accounts } } else if let managedStoreURLOverride { let store = FileManagedCodexAccountStore(fileURL: managedStoreURLOverride) storeLoader = { try store.loadAccounts() } } else { let accounts = ManagedCodexAccountSet( version: FileManagedCodexAccountStore.currentVersion, accounts: []) storeLoader = { accounts } } return DefaultCodexAccountReconciler( storeLoader: storeLoader, systemObserver: CodexManagedRemoteHomeTestingSystemObserver( overrideAccount: liveSystemAccountOverride, usesInjectedEnvironment: reconciliationEnvironmentOverride != nil), activeSource: activeSource, baseEnvironment: baseEnvironment, managedEnvironmentBuilder: { environment, account in CodexHomeScope.scopedEnvironment(base: environment, codexHome: account.managedHomePath) }) #else return DefaultCodexAccountReconciler( activeSource: activeSource, baseEnvironment: baseEnvironment, managedEnvironmentBuilder: { environment, account in CodexHomeScope.scopedEnvironment(base: environment, codexHome: account.managedHomePath) }) #endif } private func codexReconciliationEnvironment() -> [String: String] { #if DEBUG if let override = CodexManagedRemoteHomeTestingOverride.reconciliationEnvironment(for: self) { return override } #endif return ProcessInfo.processInfo.environment } } #if DEBUG private enum CodexManagedRemoteHomeTestingOverride { private struct Override { var account: ManagedCodexAccount? var homePath: String? var unreadableStore: Bool = false var managedStoreURL: URL? var liveSystemAccount: ObservedSystemCodexAccount? var reconciliationEnvironment: [String: String]? var isEmpty: Bool { self.account == nil && self.homePath == nil && self.unreadableStore == false && self .managedStoreURL == nil && self.liveSystemAccount == nil && self .reconciliationEnvironment == nil } } private final class Entry { weak var settings: SettingsStore? var overrideValue: Override init(settings: SettingsStore, overrideValue: Override) { self.settings = settings self.overrideValue = overrideValue } } @MainActor private static var values: [ObjectIdentifier: Entry] = [:] @MainActor private static func entry(for settings: SettingsStore) -> Entry? { let key = ObjectIdentifier(settings) guard let entry = self.values[key] else { return nil } guard let storedSettings = entry.settings, storedSettings === settings else { self.values.removeValue(forKey: key) return nil } return entry } @MainActor static func account(for settings: SettingsStore) -> ManagedCodexAccount? { self.entry(for: settings)?.overrideValue.account } @MainActor static func setAccount(_ account: ManagedCodexAccount?, for settings: SettingsStore) { let key = ObjectIdentifier(settings) var override = self.entry(for: settings)?.overrideValue ?? Override() override.account = account if override.isEmpty { self.values.removeValue(forKey: key) } else { self.values[key] = Entry(settings: settings, overrideValue: override) } } @MainActor static func homePath(for settings: SettingsStore) -> String? { self.entry(for: settings)?.overrideValue.homePath } @MainActor static func setHomePath(_ value: String?, for settings: SettingsStore) { let key = ObjectIdentifier(settings) var override = self.entry(for: settings)?.overrideValue ?? Override() override.homePath = value if override.isEmpty {