import CodexBarCoreimport Foundation struct OpenAIWebRefreshGateContext { let force: Bool let accountDidChange: Bool let lastError: String? let lastSnapshotAt: Date? let lastAttemptAt: Date? let now: Date let refreshInterval: TimeInterval} struct OpenAIWebRefreshPolicyContext { let accessEnabled: Bool let batterySaverEnabled: Bool let force: Bool let refreshPhase: ProviderRefreshPhase} // MARK: - OpenAI web lifecycle extension UsageStore { private struct OpenAIDashboardRefreshContext { let targetEmail: String? let allowCurrentSnapshotFallback: Bool let expectedGuard: CodexAccountScopedRefreshGuard? let refreshTaskToken: UUID let allowCodexUsageBackfill: Bool let force: Bool } private struct OpenAIDashboardCookieImportRequest { let normalizedTarget: String? let allowAnyAccount: Bool let cookieSource: ProviderCookieSource let cacheScope: CookieHeaderCache.Scope? let preferCachedCookieHeader: Bool? let force: Bool } private static let openAIWebRefreshMultiplier: TimeInterval = 5 private static let openAIWebPrimaryFetchTimeout: TimeInterval = 25 private static let openAIWebRetryFetchTimeout: TimeInterval = 8 private static let openAIWebPostImportFetchTimeout: TimeInterval = 25 static func openAIWebDashboardFetchTimeout(didImportCookies: Bool) -> TimeInterval { didImportCookies ? self.openAIWebPostImportFetchTimeout : self.openAIWebPrimaryFetchTimeout } static func openAIWebRetryDashboardFetchTimeout(afterCookieImport: Bool) -> TimeInterval { afterCookieImport ? self.openAIWebPostImportFetchTimeout : self.openAIWebRetryFetchTimeout } nonisolated static func refreshPhase( hasCompletedInitialRefresh: Bool) -> ProviderRefreshPhase { hasCompletedInitialRefresh ? .regular : .startup } nonisolated static func openAIWebRefreshPhase( providerRefreshPhase: ProviderRefreshPhase, startupConnectivityRetryAttempt: Int?) -> ProviderRefreshPhase { startupConnectivityRetryAttempt == nil ? providerRefreshPhase : .startup } private func openAIWebRefreshIntervalSeconds() -> TimeInterval { let base = max(self.settings.refreshFrequency.seconds ?? 0, 120) return base * Self.openAIWebRefreshMultiplier } func requestOpenAIDashboardRefreshIfStale(reason: String) { guard self.isEnabled(.codex), self.settings.openAIWebAccessEnabled, self.settings.codexCookieSource.isEnabled else { return } let now = Date() let refreshInterval = self.openAIWebRefreshIntervalSeconds() let dashboard = self.openAIDashboard ?? self.lastOpenAIDashboardSnapshot let lastUpdatedAt = dashboard?.updatedAt let needsMenuHistoryRefresh = dashboard?.dailyBreakdown.isEmpty == true && dashboard?.usageBreakdown.isEmpty == true if needsMenuHistoryRefresh, Self.shouldSkipOpenAIWebEmptyHistoryRetry(.init( force: false, accountDidChange: self.openAIWebAccountDidChange, lastError: self.lastOpenAIDashboardError, lastSnapshotAt: lastUpdatedAt, lastAttemptAt: self.lastOpenAIDashboardAttemptAt, now: now, refreshInterval: refreshInterval)) { return } if let lastUpdatedAt, now.timeIntervalSince(lastUpdatedAt) < refreshInterval, !needsMenuHistoryRefresh { return } let stamp = now.formatted(date: .abbreviated, time: .shortened) self.logOpenAIWeb("[\(stamp)] OpenAI web refresh request: \(reason)") let forceRefresh = Self.forceOpenAIWebRefreshForStaleRequest( batterySaverEnabled: self.settings.openAIWebBatterySaverEnabled) || needsMenuHistoryRefresh self.openAIWebLogger.info( "OpenAI web stale refresh gate", metadata: [ "reason": reason, "force": forceRefresh ? "1" : "0", "batterySaverEnabled": self.settings.openAIWebBatterySaverEnabled ? "1" : "0", "interaction": ProviderInteractionContext.current == .userInitiated ? "user" : "background", ]) let expectedGuard = self.freshCodexOpenAIWebRefreshGuard() Task { await self.refreshOpenAIDashboardIfNeeded(force: forceRefresh, expectedGuard: expectedGuard) } } func applyOpenAIDashboard( _ dash: OpenAIDashboardSnapshot, targetEmail: String?, expectedGuard: CodexAccountScopedRefreshGuard? = nil, refreshTaskToken: UUID? = nil, allowCodexUsageBackfill: Bool = true) async { guard self.shouldApplyOpenAIDashboardRefreshTask(token: refreshTaskToken) else { return } self.settings.invalidateCodexAccountReconciliationSnapshotCache() let authority = self.evaluateCodexDashboardAuthority( dashboard: dash, sourceKind: .liveWeb, routingTargetEmail: targetEmail) if let expectedGuard { let shouldApply = switch authority.decision.disposition { case .attach: self.shouldApplyOpenAIDashboardRefreshGuard( expectedGuard: expectedGuard, routingTargetEmail: targetEmail) case .displayOnly, .failClosed: self.shouldApplyOpenAIDashboardPolicyResult( expectedGuard: expectedGuard, routingTargetEmail: targetEmail) } guard shouldApply else { return } } let attachedAccountEmail = self.codexDashboardAttachmentEmail(from: authority.input) await self.applyOpenAIDashboardAuthorityDecision( authority.decision, dashboard: dash, authorityInput: authority.input, attachedAccountEmail: attachedAccountEmail, allowCodexUsageBackfill: allowCodexUsageBackfill) } func applyOpenAIDashboardFailure( message: String, expectedGuard: CodexAccountScopedRefreshGuard? = nil, refreshTaskToken: UUID? = nil, routingTargetEmail: String? = nil) async { guard self.shouldApplyOpenAIDashboardRefreshTask(token: refreshTaskToken) else { return } if let expectedGuard, !self.shouldApplyOpenAIWebNonSuccessResult( expectedGuard: expectedGuard, routingTargetEmail: routingTargetEmail) { return } if self.openAIWebManagedTargetStoreIsUnreadable() { await self.failClosedRefreshForUnreadableManagedCodexStore() return } if self.openAIWebManagedTargetIsMissing() { await self.failClosedRefreshForMissingManagedCodexTarget() return } if self.openAIWebProfileTargetEmailIsMissing() { await self.failClosedRefreshForMissingProfileCodexTarget() return } OpenAIDashboardFetcher.evictAllCachedWebViews() await MainActor.run { if let cached = self.lastOpenAIDashboardSnapshot { self.openAIDashboard = cached self.openAIDashboardAttachmentAuthorized = self.lastOpenAIDashboardAttachmentAuthorized let stamp = cached.updatedAt.formatted(date: .abbreviated, time: .shortened) self.lastOpenAIDashboardError = "Last OpenAI dashboard refresh failed: \(message). Cached values from \(stamp)." } else { self.lastOpenAIDashboardError = message self.openAIDashboard = nil self.openAIDashboardAttachmentAuthorized = false } } } func applyOpenAIDashboardLoginRequiredFailure( expectedGuard: CodexAccountScopedRefreshGuard? = nil, refreshTaskToken: UUID? = nil, routingTargetEmail: String? = nil) async { guard self.shouldApplyOpenAIDashboardRefreshTask(token: refreshTaskToken) else { return } if let expectedGuard, !self.shouldApplyOpenAIWebNonSuccessResult( expectedGuard: expectedGuard, routingTargetEmail: routingTargetEmail) { return } if self.openAIWebManagedTargetStoreIsUnreadable() { await self.failClosedRefreshForUnreadableManagedCodexStore() return } if self.openAIWebManagedTargetIsMissing() { await self.failClosedRefreshForMissingManagedCodexTarget() return } if self.openAIWebProfileTargetEmailIsMissing() { await self.failClosedRefreshForMissingProfileCodexTarget() return } OpenAIDashboardFetcher.evictAllCachedWebViews() await MainActor.run { self.lastOpenAIDashboardError = [ "OpenAI web access requires a signed-in chatgpt.com session.", "Sign in using \(self.codexBrowserCookieOrder.loginHint), " + "then update OpenAI cookies in Providers → Codex.", ].joined(separator: " ") self.openAIDashboard = self.lastOpenAIDashboardSnapshot self.openAIDashboardAttachmentAuthorized = self.lastOpenAIDashboardAttachmentAuthorized self.openAIDashboardRequiresLogin = true } } private func failClosedOpenAIDashboardSnapshot() { self.applyOpenAIDashboardCleanup(Set(CodexDashboardCleanup.allCases), preserveVisibleDashboard: false) self.openAIDashboardRequiresLogin = true } private func applyOpenAIDashboardAuthorityDecision( _ decision: CodexDashboardAuthorityDecision, dashboard: OpenAIDashboardSnapshot, authorityInput: CodexDashboardAuthorityInput, attachedAccountEmail: String?, allowCodexUsageBackfill: Bool) async { switch decision.disposition { case .attach: self.openAIDashboard = dashboard self.openAIDashboardAttachmentAuthorized = true self.lastOpenAIDashboardSnapshot = dashboard self.lastOpenAIDashboardAttachmentAuthorized = true self.lastOpenAIDashboardError = nil self.openAIDashboardRequiresLogin = false if decision.allowedEffects.contains(.usageBackfill), allowCodexUsageBackfill, self.snapshots[.codex] == nil, let usage = dashboard.toUsageSnapshot(provider: .codex, accountEmail: attachedAccountEmail) { self.snapshots[.codex] = usage self.errors[.codex] = nil self.failureGates[.codex]?.recordSuccess() self.lastSourceLabels[.codex] = "openai-web" } if decision.allowedEffects.contains(.creditsAttachment), self.credits == nil, let credits = dashboard.toCreditsSnapshot() { self.credits = credits self.lastCreditsSnapshot = credits self.lastCreditsSnapshotAccountKey = Self.normalizeCodexAccountScopedKey(attachedAccountEmail) self.lastCreditsSource = .dashboardWeb self.lastCreditsError = nil self.creditsFailureStreak = 0 } if decision.allowedEffects.contains(.refreshGuardSeed) { self.seedCodexAccountScopedRefreshGuard(accountEmail: attachedAccountEmail) } if let attachedAccountEmail, !attachedAccountEmail.isEmpty { OpenAIDashboardCacheStore.save(OpenAIDashboardCache( accountEmail: attachedAccountEmail, snapshot: dashboard)) } if decision.allowedEffects.contains(.historicalBackfill) { self.backfillCodexHistoricalFromDashboardIfNeeded( dashboard, authorityDecision: decision, attachedAccountEmail: attachedAccountEmail) } case .displayOnly: self.applyOpenAIDashboardCleanup(decision.cleanup, preserveVisibleDashboard: true) self.openAIDashboard = dashboard self.openAIDashboardAttachmentAuthorized = false self.lastOpenAIDashboardSnapshot = dashboard self.lastOpenAIDashboardAttachmentAuthorized = false self.lastOpenAIDashboardError = nil self.openAIDashboardRequiresLogin = false case .failClosed: self.applyOpenAIDashboardCleanup(decision.cleanup, preserveVisibleDashboard: false) self.lastOpenAIDashboardError = self.openAIDashboardPolicyFailureMessage( for: decision, authorityInput: authorityInput) self.openAIDashboardRequiresLogin = true } } private func applyOpenAIDashboardCleanup( _ cleanup: Set<CodexDashboardCleanup>, preserveVisibleDashboard: Bool) { if cleanup.contains(.dashboardDerivedUsage) { self.clearDashboardDerivedCodexUsageIfNeeded() } if cleanup.contains(.dashboardDerivedCredits) { self.clearDashboardDerivedCreditsIfNeeded() } if cleanup.contains(.dashboardRefreshGuardSeed) { self.clearDashboardRefreshGuardSeedIfNeeded() } if cleanup.contains(.dashboardCache) { OpenAIDashboardCacheStore.clear() } if cleanup.contains(.dashboardSnapshot), !preserveVisibleDashboard { self.openAIDashboard = nil self.openAIDashboardAttachmentAuthorized = false self.lastOpenAIDashboardSnapshot = nil self.lastOpenAIDashboardAttachmentAuthorized = false } } private func clearDashboardDerivedCodexUsageIfNeeded() { guard self.lastSourceLabels[.codex] == "openai-web" else { return } self.snapshots.removeValue(forKey: .codex) self.errors[.codex] = nil self.lastSourceLabels.removeValue(forKey: .codex) self.lastFetchAttempts.removeValue(forKey: .codex) self.accountSnapshots.removeValue(forKey: .codex) self.codexAccountSnapshots = [] self.failureGates[.codex]?.reset() self.lastKnownSessionRemaining.removeValue(forKey: .codex) self.lastKnownSessionWindowSource.removeValue(forKey: .codex) } private func clearDashboardDerivedCreditsIfNeeded() { guard self.lastCreditsSource == .dashboardWeb else { return } self.credits = nil self.lastCreditsError = nil self.lastCreditsSnapshot = nil self.lastCreditsSnapshotAccountKey = nil self.lastCreditsSource = .none self.creditsFailureStreak = 0 } private func clearDashboardRefreshGuardSeedIfNeeded() { self.lastCodexAccountScopedRefreshGuard = self.currentCodexAccountScopedRefreshGuard( preferCurrentSnapshot: false, allowLastKnownLiveFallback: false) } private func openAIDashboardPolicyFailureMessage( for decision: CodexDashboardAuthorityDecision, authorityInput: CodexDashboardAuthorityInput) -> String { switch decision.reason { case let .wrongEmail(expected, actual): [ "OpenAI dashboard signed in as \(actual ?? "unknown"), but Codex uses \(expected ?? "unknown").", "Switch accounts in your browser and update OpenAI cookies in Providers → Codex.", ].joined(separator: " ") case let .sameEmailAmbiguity(email): "OpenAI dashboard ownership is ambiguous for \(email); Codex will not attach dashboard data." case .missingDashboardSignedInEmail: "OpenAI dashboard did not report a signed-in account. Refresh OpenAI cookies and try again." case .unresolvedWithoutTrustedEvidence: "OpenAI dashboard ownership could not be verified for the active Codex account." case .providerAccountMissingScopedEmail: "Codex account ownership could not be verified because the scoped email is unavailable." case .providerAccountLacksExactOwnershipProof: [ "OpenAI dashboard ownership could not be matched to the active Codex account.", "Refresh Codex account data, then retry OpenAI web access.", ].joined(separator: " ") case .exactProviderAccountMatch, .trustedEmailMatchNoCompetingOwner, .trustedContinuityNoCompetingOwner: "OpenAI dashboard ownership policy blocked this dashboard." } } func refreshOpenAIDashboardIfNeeded( force: Bool = false, expectedGuard: CodexAccountScopedRefreshGuard? = nil, bypassCoalescing: Bool = false, allowCodexUsageBackfill: Bool = true) async { self.syncOpenAIWebState() guard self.isEnabled(.codex), self.settings.openAIWebAccessEnabled, self.settings.codexCookieSource.isEnabled else { return } if self.openAIWebManagedTargetStoreIsUnreadable() { await self.failClosedRefreshForUnreadableManagedCodexStore() return } if self.openAIWebManagedTargetIsMissing() { await self.failClosedRefreshForMissingManagedCodexTarget() return } if self.openAIWebProfileTargetEmailIsMissing() { await self.failClosedRefreshForMissingProfileCodexTarget() return } let allowCurrentSnapshotFallback = expectedGuard?.source == .liveSystem && expectedGuard? .identity == .unresolved let targetEmail = self.currentCodexOpenAIWebTargetEmail( allowCurrentSnapshotFallback: allowCurrentSnapshotFallback, allowLastKnownLiveFallback: expectedGuard?.identity != .unresolved) let refreshKey = self.openAIDashboardRefreshKey(targetEmail: targetEmail, expectedGuard: expectedGuard) if !bypassCoalescing, let task = self.openAIDashboardRefreshTask, self.openAIDashboardRefreshTaskKey == refreshKey { await task.value return } self.handleOpenAIWebTargetEmailChangeIfNeeded( targetEmail: targetEmail, targetScope: self.codexCookieCacheScopeForOpenAIWeb()) let now = Date() let minInterval = self.openAIWebRefreshIntervalSeconds() let refreshGate = OpenAIWebRefreshGateContext( force: force, accountDidChange: self.openAIWebAccountDidChange, lastError: self.lastOpenAIDashboardError, lastSnapshotAt: self.lastOpenAIDashboardSnapshot?.updatedAt, lastAttemptAt: self.lastOpenAIDashboardAttemptAt, now: now, refreshInterval: minInterval) if Self.shouldSkipOpenAIWebRefresh(refreshGate) { return } self.lastOpenAIDashboardAttemptAt = now let taskToken = UUID() let context = OpenAIDashboardRefreshContext( targetEmail: targetEmail, allowCurrentSnapshotFallback: allowCurrentSnapshotFallback, expectedGuard: expectedGuard, refreshTaskToken: taskToken, allowCodexUsageBackfill: allowCodexUsageBackfill, force: force) let task = Task { [weak self] in guard let self else { return } await self.performOpenAIDashboardRefreshIfNeeded(context) } self.openAIDashboardRefreshTask = task self.openAIDashboardRefreshTaskKey = refreshKey self.openAIDashboardRefreshTaskToken = taskToken await task.value if self.openAIDashboardRefreshTaskToken == taskToken { self.openAIDashboardRefreshTask = nil self.openAIDashboardRefreshTaskKey = nil self.openAIDashboardRefreshTaskToken = nil } } func scheduleOpenAIDashboardRefreshIfNeeded(expectedGuard: CodexAccountScopedRefreshGuard? = nil) { self.syncOpenAIWebState() let allowCurrentSnapshotFallback = expectedGuard?.source == .liveSystem && expectedGuard? .identity == .unresolved let targetEmail = self.currentCodexOpenAIWebTargetEmail( allowCurrentSnapshotFallback: allowCurrentSnapshotFallback, allowLastKnownLiveFallback: expectedGuard?.identity != .unresolved) let refreshKey = self.openAIDashboardRefreshKey(targetEmail: targetEmail, expectedGuard: expectedGuard) if let task = self.openAIDashboardBackgroundRefreshTask, !task.isCancelled, self.openAIDashboardBackgroundRefreshTaskKey == refreshKey { return } if self.openAIDashboardBackgroundRefreshTaskKey != nil, self.openAIDashboardBackgroundRefreshTaskKey != refreshKey { self.invalidateOpenAIDashboardRefreshTask() } self.openAIDashboardBackgroundRefreshTask?.cancel() self.openAIDashboardBackgroundRefreshTaskKey = refreshKey self.openAIDashboardBackgroundRefreshTask = Task(priority: .utility) { @MainActor [weak self] in guard let self else { return } defer { if self.openAIDashboardBackgroundRefreshTaskKey == refreshKey {Lines 1–500 of 1539. Load more
import CodexBarCore import Foundation struct OpenAIWebRefreshGateContext { let force: Bool let accountDidChange: Bool let lastError: String? let lastSnapshotAt: Date? let lastAttemptAt: Date? let now: Date let refreshInterval: TimeInterval } struct OpenAIWebRefreshPolicyContext { let accessEnabled: Bool let batterySaverEnabled: Bool let force: Bool let refreshPhase: ProviderRefreshPhase } // MARK: - OpenAI web lifecycle extension UsageStore { private struct OpenAIDashboardRefreshContext { let targetEmail: String? let allowCurrentSnapshotFallback: Bool let expectedGuard: CodexAccountScopedRefreshGuard? let refreshTaskToken: UUID let allowCodexUsageBackfill: Bool let force: Bool } private struct OpenAIDashboardCookieImportRequest { let normalizedTarget: String? let allowAnyAccount: Bool let cookieSource: ProviderCookieSource let cacheScope: CookieHeaderCache.Scope? let preferCachedCookieHeader: Bool? let force: Bool } private static let openAIWebRefreshMultiplier: TimeInterval = 5 private static let openAIWebPrimaryFetchTimeout: TimeInterval = 25 private static let openAIWebRetryFetchTimeout: TimeInterval = 8 private static let openAIWebPostImportFetchTimeout: TimeInterval = 25 static func openAIWebDashboardFetchTimeout(didImportCookies: Bool) -> TimeInterval { didImportCookies ? self.openAIWebPostImportFetchTimeout : self.openAIWebPrimaryFetchTimeout } static func openAIWebRetryDashboardFetchTimeout(afterCookieImport: Bool) -> TimeInterval { afterCookieImport ? self.openAIWebPostImportFetchTimeout : self.openAIWebRetryFetchTimeout } nonisolated static func refreshPhase( hasCompletedInitialRefresh: Bool) -> ProviderRefreshPhase { hasCompletedInitialRefresh ? .regular : .startup } nonisolated static func openAIWebRefreshPhase( providerRefreshPhase: ProviderRefreshPhase, startupConnectivityRetryAttempt: Int?) -> ProviderRefreshPhase { startupConnectivityRetryAttempt == nil ? providerRefreshPhase : .startup } private func openAIWebRefreshIntervalSeconds() -> TimeInterval { let base = max(self.settings.refreshFrequency.seconds ?? 0, 120) return base * Self.openAIWebRefreshMultiplier } func requestOpenAIDashboardRefreshIfStale(reason: String) { guard self.isEnabled(.codex), self.settings.openAIWebAccessEnabled, self.settings.codexCookieSource.isEnabled else { return } let now = Date() let refreshInterval = self.openAIWebRefreshIntervalSeconds() let dashboard = self.openAIDashboard ?? self.lastOpenAIDashboardSnapshot let lastUpdatedAt = dashboard?.updatedAt let needsMenuHistoryRefresh = dashboard?.dailyBreakdown.isEmpty == true && dashboard?.usageBreakdown.isEmpty == true if needsMenuHistoryRefresh, Self.shouldSkipOpenAIWebEmptyHistoryRetry(.init( force: false, accountDidChange: self.openAIWebAccountDidChange, lastError: self.lastOpenAIDashboardError, lastSnapshotAt: lastUpdatedAt, lastAttemptAt: self.lastOpenAIDashboardAttemptAt, now: now, refreshInterval: refreshInterval)) { return } if let lastUpdatedAt, now.timeIntervalSince(lastUpdatedAt) < refreshInterval, !needsMenuHistoryRefresh { return } let stamp = now.formatted(date: .abbreviated, time: .shortened) self.logOpenAIWeb("[\(stamp)] OpenAI web refresh request: \(reason)") let forceRefresh = Self.forceOpenAIWebRefreshForStaleRequest( batterySaverEnabled: self.settings.openAIWebBatterySaverEnabled) || needsMenuHistoryRefresh self.openAIWebLogger.info( "OpenAI web stale refresh gate", metadata: [ "reason": reason, "force": forceRefresh ? "1" : "0", "batterySaverEnabled": self.settings.openAIWebBatterySaverEnabled ? "1" : "0", "interaction": ProviderInteractionContext.current == .userInitiated ? "user" : "background", ]) let expectedGuard = self.freshCodexOpenAIWebRefreshGuard() Task { await self.refreshOpenAIDashboardIfNeeded(force: forceRefresh, expectedGuard: expectedGuard) } } func applyOpenAIDashboard( _ dash: OpenAIDashboardSnapshot, targetEmail: String?, expectedGuard: CodexAccountScopedRefreshGuard? = nil, refreshTaskToken: UUID? = nil, allowCodexUsageBackfill: Bool = true) async { guard self.shouldApplyOpenAIDashboardRefreshTask(token: refreshTaskToken) else { return } self.settings.invalidateCodexAccountReconciliationSnapshotCache() let authority = self.evaluateCodexDashboardAuthority( dashboard: dash, sourceKind: .liveWeb, routingTargetEmail: targetEmail) if let expectedGuard { let shouldApply = switch authority.decision.disposition { case .attach: self.shouldApplyOpenAIDashboardRefreshGuard( expectedGuard: expectedGuard, routingTargetEmail: targetEmail) case .displayOnly, .failClosed: self.shouldApplyOpenAIDashboardPolicyResult( expectedGuard: expectedGuard, routingTargetEmail: targetEmail) } guard shouldApply else { return } } let attachedAccountEmail = self.codexDashboardAttachmentEmail(from: authority.input) await self.applyOpenAIDashboardAuthorityDecision( authority.decision, dashboard: dash, authorityInput: authority.input, attachedAccountEmail: attachedAccountEmail, allowCodexUsageBackfill: allowCodexUsageBackfill) } func applyOpenAIDashboardFailure( message: String, expectedGuard: CodexAccountScopedRefreshGuard? = nil, refreshTaskToken: UUID? = nil, routingTargetEmail: String? = nil) async { guard self.shouldApplyOpenAIDashboardRefreshTask(token: refreshTaskToken) else { return } if let expectedGuard, !self.shouldApplyOpenAIWebNonSuccessResult( expectedGuard: expectedGuard, routingTargetEmail: routingTargetEmail) { return } if self.openAIWebManagedTargetStoreIsUnreadable() { await self.failClosedRefreshForUnreadableManagedCodexStore() return } if self.openAIWebManagedTargetIsMissing() { await self.failClosedRefreshForMissingManagedCodexTarget() return } if self.openAIWebProfileTargetEmailIsMissing() { await self.failClosedRefreshForMissingProfileCodexTarget() return } OpenAIDashboardFetcher.evictAllCachedWebViews() await MainActor.run { if let cached = self.lastOpenAIDashboardSnapshot { self.openAIDashboard = cached self.openAIDashboardAttachmentAuthorized = self.lastOpenAIDashboardAttachmentAuthorized let stamp = cached.updatedAt.formatted(date: .abbreviated, time: .shortened) self.lastOpenAIDashboardError = "Last OpenAI dashboard refresh failed: \(message). Cached values from \(stamp)." } else { self.lastOpenAIDashboardError = message self.openAIDashboard = nil self.openAIDashboardAttachmentAuthorized = false } } } func applyOpenAIDashboardLoginRequiredFailure( expectedGuard: CodexAccountScopedRefreshGuard? = nil, refreshTaskToken: UUID? = nil, routingTargetEmail: String? = nil) async { guard self.shouldApplyOpenAIDashboardRefreshTask(token: refreshTaskToken) else { return } if let expectedGuard, !self.shouldApplyOpenAIWebNonSuccessResult( expectedGuard: expectedGuard, routingTargetEmail: routingTargetEmail) { return } if self.openAIWebManagedTargetStoreIsUnreadable() { await self.failClosedRefreshForUnreadableManagedCodexStore() return } if self.openAIWebManagedTargetIsMissing() { await self.failClosedRefreshForMissingManagedCodexTarget() return } if self.openAIWebProfileTargetEmailIsMissing() { await self.failClosedRefreshForMissingProfileCodexTarget() return } OpenAIDashboardFetcher.evictAllCachedWebViews() await MainActor.run { self.lastOpenAIDashboardError = [ "OpenAI web access requires a signed-in chatgpt.com session.", "Sign in using \(self.codexBrowserCookieOrder.loginHint), " + "then update OpenAI cookies in Providers → Codex.", ].joined(separator: " ") self.openAIDashboard = self.lastOpenAIDashboardSnapshot self.openAIDashboardAttachmentAuthorized = self.lastOpenAIDashboardAttachmentAuthorized self.openAIDashboardRequiresLogin = true } } private func failClosedOpenAIDashboardSnapshot() { self.applyOpenAIDashboardCleanup(Set(CodexDashboardCleanup.allCases), preserveVisibleDashboard: false) self.openAIDashboardRequiresLogin = true } private func applyOpenAIDashboardAuthorityDecision( _ decision: CodexDashboardAuthorityDecision, dashboard: OpenAIDashboardSnapshot, authorityInput: CodexDashboardAuthorityInput, attachedAccountEmail: String?, allowCodexUsageBackfill: Bool) async { switch decision.disposition { case .attach: self.openAIDashboard = dashboard self.openAIDashboardAttachmentAuthorized = true self.lastOpenAIDashboardSnapshot = dashboard self.lastOpenAIDashboardAttachmentAuthorized = true self.lastOpenAIDashboardError = nil self.openAIDashboardRequiresLogin = false if decision.allowedEffects.contains(.usageBackfill), allowCodexUsageBackfill, self.snapshots[.codex] == nil, let usage = dashboard.toUsageSnapshot(provider: .codex, accountEmail: attachedAccountEmail) { self.snapshots[.codex] = usage self.errors[.codex] = nil self.failureGates[.codex]?.recordSuccess() self.lastSourceLabels[.codex] = "openai-web" } if decision.allowedEffects.contains(.creditsAttachment), self.credits == nil, let credits = dashboard.toCreditsSnapshot() { self.credits = credits self.lastCreditsSnapshot = credits self.lastCreditsSnapshotAccountKey = Self.normalizeCodexAccountScopedKey(attachedAccountEmail) self.lastCreditsSource = .dashboardWeb self.lastCreditsError = nil self.creditsFailureStreak = 0 } if decision.allowedEffects.contains(.refreshGuardSeed) { self.seedCodexAccountScopedRefreshGuard(accountEmail: attachedAccountEmail) } if let attachedAccountEmail, !attachedAccountEmail.isEmpty { OpenAIDashboardCacheStore.save(OpenAIDashboardCache( accountEmail: attachedAccountEmail, snapshot: dashboard)) } if decision.allowedEffects.contains(.historicalBackfill) { self.backfillCodexHistoricalFromDashboardIfNeeded( dashboard, authorityDecision: decision, attachedAccountEmail: attachedAccountEmail) } case .displayOnly: self.applyOpenAIDashboardCleanup(decision.cleanup, preserveVisibleDashboard: true) self.openAIDashboard = dashboard self.openAIDashboardAttachmentAuthorized = false self.lastOpenAIDashboardSnapshot = dashboard self.lastOpenAIDashboardAttachmentAuthorized = false self.lastOpenAIDashboardError = nil self.openAIDashboardRequiresLogin = false case .failClosed: self.applyOpenAIDashboardCleanup(decision.cleanup, preserveVisibleDashboard: false) self.lastOpenAIDashboardError = self.openAIDashboardPolicyFailureMessage( for: decision, authorityInput: authorityInput) self.openAIDashboardRequiresLogin = true } } private func applyOpenAIDashboardCleanup( _ cleanup: Set<CodexDashboardCleanup>, preserveVisibleDashboard: Bool) { if cleanup.contains(.dashboardDerivedUsage) { self.clearDashboardDerivedCodexUsageIfNeeded() } if cleanup.contains(.dashboardDerivedCredits) { self.clearDashboardDerivedCreditsIfNeeded() } if cleanup.contains(.dashboardRefreshGuardSeed) { self.clearDashboardRefreshGuardSeedIfNeeded() } if cleanup.contains(.dashboardCache) { OpenAIDashboardCacheStore.clear() } if cleanup.contains(.dashboardSnapshot), !preserveVisibleDashboard { self.openAIDashboard = nil self.openAIDashboardAttachmentAuthorized = false self.lastOpenAIDashboardSnapshot = nil self.lastOpenAIDashboardAttachmentAuthorized = false } } private func clearDashboardDerivedCodexUsageIfNeeded() { guard self.lastSourceLabels[.codex] == "openai-web" else { return } self.snapshots.removeValue(forKey: .codex) self.errors[.codex] = nil self.lastSourceLabels.removeValue(forKey: .codex) self.lastFetchAttempts.removeValue(forKey: .codex) self.accountSnapshots.removeValue(forKey: .codex) self.codexAccountSnapshots = [] self.failureGates[.codex]?.reset() self.lastKnownSessionRemaining.removeValue(forKey: .codex) self.lastKnownSessionWindowSource.removeValue(forKey: .codex) } private func clearDashboardDerivedCreditsIfNeeded() { guard self.lastCreditsSource == .dashboardWeb else { return } self.credits = nil self.lastCreditsError = nil self.lastCreditsSnapshot = nil self.lastCreditsSnapshotAccountKey = nil self.lastCreditsSource = .none self.creditsFailureStreak = 0 } private func clearDashboardRefreshGuardSeedIfNeeded() { self.lastCodexAccountScopedRefreshGuard = self.currentCodexAccountScopedRefreshGuard( preferCurrentSnapshot: false, allowLastKnownLiveFallback: false) } private func openAIDashboardPolicyFailureMessage( for decision: CodexDashboardAuthorityDecision, authorityInput: CodexDashboardAuthorityInput) -> String { switch decision.reason { case let .wrongEmail(expected, actual): [ "OpenAI dashboard signed in as \(actual ?? "unknown"), but Codex uses \(expected ?? "unknown").", "Switch accounts in your browser and update OpenAI cookies in Providers → Codex.", ].joined(separator: " ") case let .sameEmailAmbiguity(email): "OpenAI dashboard ownership is ambiguous for \(email); Codex will not attach dashboard data." case .missingDashboardSignedInEmail: "OpenAI dashboard did not report a signed-in account. Refresh OpenAI cookies and try again." case .unresolvedWithoutTrustedEvidence: "OpenAI dashboard ownership could not be verified for the active Codex account." case .providerAccountMissingScopedEmail: "Codex account ownership could not be verified because the scoped email is unavailable." case .providerAccountLacksExactOwnershipProof: [ "OpenAI dashboard ownership could not be matched to the active Codex account.", "Refresh Codex account data, then retry OpenAI web access.", ].joined(separator: " ") case .exactProviderAccountMatch, .trustedEmailMatchNoCompetingOwner, .trustedContinuityNoCompetingOwner: "OpenAI dashboard ownership policy blocked this dashboard." } } func refreshOpenAIDashboardIfNeeded( force: Bool = false, expectedGuard: CodexAccountScopedRefreshGuard? = nil, bypassCoalescing: Bool = false, allowCodexUsageBackfill: Bool = true) async { self.syncOpenAIWebState() guard self.isEnabled(.codex), self.settings.openAIWebAccessEnabled, self.settings.codexCookieSource.isEnabled else { return } if self.openAIWebManagedTargetStoreIsUnreadable() { await self.failClosedRefreshForUnreadableManagedCodexStore() return } if self.openAIWebManagedTargetIsMissing() { await self.failClosedRefreshForMissingManagedCodexTarget() return } if self.openAIWebProfileTargetEmailIsMissing() { await self.failClosedRefreshForMissingProfileCodexTarget() return } let allowCurrentSnapshotFallback = expectedGuard?.source == .liveSystem && expectedGuard? .identity == .unresolved let targetEmail = self.currentCodexOpenAIWebTargetEmail( allowCurrentSnapshotFallback: allowCurrentSnapshotFallback, allowLastKnownLiveFallback: expectedGuard?.identity != .unresolved) let refreshKey = self.openAIDashboardRefreshKey(targetEmail: targetEmail, expectedGuard: expectedGuard) if !bypassCoalescing, let task = self.openAIDashboardRefreshTask, self.openAIDashboardRefreshTaskKey == refreshKey { await task.value return } self.handleOpenAIWebTargetEmailChangeIfNeeded( targetEmail: targetEmail, targetScope: self.codexCookieCacheScopeForOpenAIWeb()) let now = Date() let minInterval = self.openAIWebRefreshIntervalSeconds() let refreshGate = OpenAIWebRefreshGateContext( force: force, accountDidChange: self.openAIWebAccountDidChange, lastError: self.lastOpenAIDashboardError, lastSnapshotAt: self.lastOpenAIDashboardSnapshot?.updatedAt, lastAttemptAt: self.lastOpenAIDashboardAttemptAt, now: now, refreshInterval: minInterval) if Self.shouldSkipOpenAIWebRefresh(refreshGate) { return } self.lastOpenAIDashboardAttemptAt = now let taskToken = UUID() let context = OpenAIDashboardRefreshContext( targetEmail: targetEmail, allowCurrentSnapshotFallback: allowCurrentSnapshotFallback, expectedGuard: expectedGuard, refreshTaskToken: taskToken, allowCodexUsageBackfill: allowCodexUsageBackfill, force: force) let task = Task { [weak self] in guard let self else { return } await self.performOpenAIDashboardRefreshIfNeeded(context) } self.openAIDashboardRefreshTask = task self.openAIDashboardRefreshTaskKey = refreshKey self.openAIDashboardRefreshTaskToken = taskToken await task.value if self.openAIDashboardRefreshTaskToken == taskToken { self.openAIDashboardRefreshTask = nil self.openAIDashboardRefreshTaskKey = nil self.openAIDashboardRefreshTaskToken = nil } } func scheduleOpenAIDashboardRefreshIfNeeded(expectedGuard: CodexAccountScopedRefreshGuard? = nil) { self.syncOpenAIWebState() let allowCurrentSnapshotFallback = expectedGuard?.source == .liveSystem && expectedGuard? .identity == .unresolved let targetEmail = self.currentCodexOpenAIWebTargetEmail( allowCurrentSnapshotFallback: allowCurrentSnapshotFallback, allowLastKnownLiveFallback: expectedGuard?.identity != .unresolved) let refreshKey = self.openAIDashboardRefreshKey(targetEmail: targetEmail, expectedGuard: expectedGuard) if let task = self.openAIDashboardBackgroundRefreshTask, !task.isCancelled, self.openAIDashboardBackgroundRefreshTaskKey == refreshKey { return } if self.openAIDashboardBackgroundRefreshTaskKey != nil, self.openAIDashboardBackgroundRefreshTaskKey != refreshKey { self.invalidateOpenAIDashboardRefreshTask() } self.openAIDashboardBackgroundRefreshTask?.cancel() self.openAIDashboardBackgroundRefreshTaskKey = refreshKey self.openAIDashboardBackgroundRefreshTask = Task(priority: .utility) { @MainActor [weak self] in guard let self else { return } defer { if self.openAIDashboardBackgroundRefreshTaskKey == refreshKey {