import Foundation public struct CodexResolvedActiveSource: Equatable, Sendable { public let persistedSource: CodexActiveSource public let resolvedSource: CodexActiveSource public init(persistedSource: CodexActiveSource, resolvedSource: CodexActiveSource) { self.persistedSource = persistedSource self.resolvedSource = resolvedSource } public var requiresPersistenceCorrection: Bool { self.persistedSource != self.resolvedSource }} public enum CodexActiveSourceResolver { public static func resolve(from snapshot: CodexAccountReconciliationSnapshot) -> CodexResolvedActiveSource { let persistedSource = snapshot.activeSource let resolvedSource: CodexActiveSource = switch persistedSource { case .liveSystem: .liveSystem case let .managedAccount(id): if let activeStoredAccount = snapshot.activeStoredAccount { self.resolvedSource(for: activeStoredAccount, snapshot: snapshot) } else { snapshot.liveSystemAccount != nil ? .liveSystem : .managedAccount(id: id) } case let .profileHome(path): if let normalizedPath = snapshot.configuredProfileHomePath(path: path) { self.resolvedProfileSource(path: normalizedPath, snapshot: snapshot) } else { .liveSystem } } return CodexResolvedActiveSource( persistedSource: persistedSource, resolvedSource: resolvedSource) } private static func resolvedProfileSource( path: String, snapshot: CodexAccountReconciliationSnapshot) -> CodexActiveSource { if let livePath = snapshot.liveSystemAccount.flatMap({ CodexHomeScope.normalizedHomePath($0.codexHomePath) }), livePath == path { return .liveSystem } if let storedAccount = snapshot.storedAccounts.first(where: { CodexHomeScope.normalizedHomePath($0.managedHomePath) == path }) { return self.resolvedSource(for: storedAccount, snapshot: snapshot) } return .profileHome(path: path) } private static func resolvedSource( for storedAccount: ManagedCodexAccount, snapshot: CodexAccountReconciliationSnapshot) -> CodexActiveSource { self.matchesLiveSystemAccount( storedAccount: storedAccount, snapshot: snapshot, liveSystemAccount: snapshot.liveSystemAccount) ? .liveSystem : .managedAccount(id: storedAccount.id) } private static func matchesLiveSystemAccount( storedAccount: ManagedCodexAccount, snapshot: CodexAccountReconciliationSnapshot, liveSystemAccount: ObservedSystemCodexAccount?) -> Bool { guard let liveSystemAccount else { return false } if let storedFingerprint = storedAccount.authFingerprint, let liveFingerprint = liveSystemAccount.authFingerprint, storedFingerprint == liveFingerprint { return true } return CodexIdentityMatcher.matches( snapshot.runtimeIdentity(for: storedAccount), lhsEmail: snapshot.runtimeEmail(for: storedAccount), snapshot.runtimeIdentity(for: liveSystemAccount), rhsEmail: liveSystemAccount.email) }} public struct CodexAccountReconciliationSnapshot: Equatable, Sendable { public let storedAccounts: [ManagedCodexAccount] public let activeStoredAccount: ManagedCodexAccount? public let liveSystemAccount: ObservedSystemCodexAccount? public let profileHomeAccounts: [ObservedSystemCodexAccount] public let profileHomePaths: [String] public let matchingStoredAccountForLiveSystemAccount: ManagedCodexAccount? public let activeSource: CodexActiveSource public let hasUnreadableAddedAccountStore: Bool public let storedAccountRuntimeIdentities: [UUID: CodexIdentity] public let storedAccountRuntimeEmails: [UUID: String] public init( storedAccounts: [ManagedCodexAccount], activeStoredAccount: ManagedCodexAccount?, liveSystemAccount: ObservedSystemCodexAccount?, profileHomeAccounts: [ObservedSystemCodexAccount] = [], profileHomePaths: [String]? = nil, matchingStoredAccountForLiveSystemAccount: ManagedCodexAccount?, activeSource: CodexActiveSource, hasUnreadableAddedAccountStore: Bool, storedAccountRuntimeIdentities: [UUID: CodexIdentity] = [:], storedAccountRuntimeEmails: [UUID: String] = [:]) { self.storedAccounts = storedAccounts self.activeStoredAccount = activeStoredAccount self.liveSystemAccount = liveSystemAccount self.profileHomeAccounts = Self.uniqueProfileHomeAccounts(profileHomeAccounts) self.profileHomePaths = Self.uniqueNormalizedPaths( profileHomePaths ?? profileHomeAccounts.map(\.codexHomePath)) self.matchingStoredAccountForLiveSystemAccount = matchingStoredAccountForLiveSystemAccount self.activeSource = activeSource self.hasUnreadableAddedAccountStore = hasUnreadableAddedAccountStore self.storedAccountRuntimeIdentities = storedAccountRuntimeIdentities self.storedAccountRuntimeEmails = storedAccountRuntimeEmails } public static func == (lhs: CodexAccountReconciliationSnapshot, rhs: CodexAccountReconciliationSnapshot) -> Bool { lhs.storedAccounts.map(AccountIdentity.init) == rhs.storedAccounts.map(AccountIdentity.init) && lhs.activeStoredAccount.map(AccountIdentity.init) == rhs.activeStoredAccount.map(AccountIdentity.init) && lhs.liveSystemAccount == rhs.liveSystemAccount && lhs.profileHomeAccounts == rhs.profileHomeAccounts && lhs.profileHomePaths == rhs.profileHomePaths && lhs.matchingStoredAccountForLiveSystemAccount.map(AccountIdentity.init) == rhs.matchingStoredAccountForLiveSystemAccount.map(AccountIdentity.init) && lhs.activeSource == rhs.activeSource && lhs.hasUnreadableAddedAccountStore == rhs.hasUnreadableAddedAccountStore && lhs.storedAccountRuntimeIdentities == rhs.storedAccountRuntimeIdentities && lhs.storedAccountRuntimeEmails == rhs.storedAccountRuntimeEmails } public func runtimeIdentity(for storedAccount: ManagedCodexAccount) -> CodexIdentity { self.storedAccountRuntimeIdentities[storedAccount.id] ?? CodexIdentityResolver.resolve(accountId: nil, email: storedAccount.email) } public func runtimeEmail(for storedAccount: ManagedCodexAccount) -> String { self.storedAccountRuntimeEmails[storedAccount.id] ?? Self.normalizeEmail(storedAccount.email) } public func runtimeIdentity(for liveSystemAccount: ObservedSystemCodexAccount) -> CodexIdentity { CodexIdentityMatcher.normalized( liveSystemAccount.identity, fallbackEmail: liveSystemAccount.email) } public func profileHomeAccount(path: String) -> ObservedSystemCodexAccount? { guard let normalizedPath = CodexHomeScope.normalizedHomePath(path) else { return nil } return self.profileHomeAccounts.first { CodexHomeScope.normalizedHomePath($0.codexHomePath) == normalizedPath } } public func configuredProfileHomePath(path: String) -> String? { guard let normalizedPath = CodexHomeScope.normalizedHomePath(path), self.profileHomePaths.contains(normalizedPath) else { return nil } return normalizedPath } private static func normalizeEmail(_ email: String) -> String { email.trimmingCharacters(in: .whitespacesAndNewlines).lowercased() } private static func uniqueProfileHomeAccounts( _ accounts: [ObservedSystemCodexAccount]) -> [ObservedSystemCodexAccount] { var seenPaths: Set<String> = [] var result: [ObservedSystemCodexAccount] = [] for account in accounts { let path = CodexHomeScope.normalizedHomePath(account.codexHomePath) ?? account.codexHomePath guard seenPaths.insert(path).inserted else { continue } result.append(account) } return result } private static func uniqueNormalizedPaths(_ paths: [String]) -> [String] { var seen: Set<String> = [] return paths.compactMap { path in guard let normalizedPath = CodexHomeScope.normalizedHomePath(path), seen.insert(normalizedPath).inserted else { return nil } return normalizedPath } }} public struct DefaultCodexAccountReconciler: Sendable { public let storeLoader: @Sendable () throws -> ManagedCodexAccountSet public let systemObserver: any CodexSystemAccountObserving public let activeSource: CodexActiveSource public let baseEnvironment: [String: String] public let profileHomePaths: [String] public let managedEnvironmentBuilder: @Sendable ([String: String], ManagedCodexAccount) -> [String: String] public init( storeLoader: @escaping @Sendable () throws -> ManagedCodexAccountSet = { try FileManagedCodexAccountStore().loadAccounts() }, systemObserver: any CodexSystemAccountObserving = DefaultCodexSystemAccountObserver(), activeSource: CodexActiveSource = .liveSystem, baseEnvironment: [String: String], profileHomePaths: [String] = [], managedEnvironmentBuilder: @escaping @Sendable ([String: String], ManagedCodexAccount) -> [String: String] = { baseEnvironment, account in CodexHomeScope.scopedEnvironment(base: baseEnvironment, codexHome: account.managedHomePath) }) { self.storeLoader = storeLoader self.systemObserver = systemObserver self.activeSource = activeSource self.baseEnvironment = baseEnvironment self.profileHomePaths = Self.uniqueNormalizedPaths(profileHomePaths) self.managedEnvironmentBuilder = managedEnvironmentBuilder } public func loadSnapshot() -> CodexAccountReconciliationSnapshot { let liveSystemAccount = self.loadLiveSystemAccount() let profileHomeAccounts = self.loadProfileHomeAccounts(liveSystemAccount: liveSystemAccount) do { let accounts = try self.storeLoader() let runtimeAccounts = Dictionary(uniqueKeysWithValues: accounts.accounts.map { account in let runtimeAccount = self.loadRuntimeAccount(for: account) return (account.id, runtimeAccount) }) let activeStoredAccount: ManagedCodexAccount? = switch self.activeSource { case let .managedAccount(id): accounts.account(id: id) case .liveSystem, .profileHome: nil } let matchingStoredAccountForLiveSystemAccount = liveSystemAccount.flatMap { liveAccount in if let liveFingerprint = liveAccount.authFingerprint, let exactFingerprintMatch = accounts.accounts.first(where: { $0.authFingerprint == liveFingerprint }) { return exactFingerprintMatch } return accounts.accounts.first { account in guard let runtimeAccount = runtimeAccounts[account.id] else { return false } return CodexIdentityMatcher.matches( runtimeAccount.identity, lhsEmail: runtimeAccount.email, self.runtimeIdentity(for: liveAccount), rhsEmail: liveAccount.email) } } return CodexAccountReconciliationSnapshot( storedAccounts: accounts.accounts, activeStoredAccount: activeStoredAccount, liveSystemAccount: liveSystemAccount, profileHomeAccounts: self.profileHomeAccounts( profileHomeAccounts, excludingManagedAccounts: accounts.accounts), profileHomePaths: self.profileHomePaths, matchingStoredAccountForLiveSystemAccount: matchingStoredAccountForLiveSystemAccount, activeSource: self.activeSource, hasUnreadableAddedAccountStore: false, storedAccountRuntimeIdentities: runtimeAccounts.mapValues(\.identity), storedAccountRuntimeEmails: runtimeAccounts.mapValues(\.email)) } catch { return CodexAccountReconciliationSnapshot( storedAccounts: [], activeStoredAccount: nil, liveSystemAccount: liveSystemAccount, profileHomeAccounts: profileHomeAccounts, profileHomePaths: self.profileHomePaths, matchingStoredAccountForLiveSystemAccount: nil, activeSource: self.activeSource, hasUnreadableAddedAccountStore: true) } } private func loadProfileHomeAccounts( liveSystemAccount: ObservedSystemCodexAccount?) -> [ObservedSystemCodexAccount] { let livePath = liveSystemAccount.flatMap { CodexHomeScope.normalizedHomePath($0.codexHomePath) } return self.profileHomePaths.compactMap { path in guard path != livePath else { return nil } return self.loadProfileHomeAccount(homePath: path) } } private func loadProfileHomeAccount(homePath: String) -> ObservedSystemCodexAccount? { let environment = CodexHomeScope.scopedEnvironment(base: self.baseEnvironment, codexHome: homePath) let account = UsageFetcher(environment: environment).loadAuthBackedCodexAccount() guard let rawEmail = account.email?.trimmingCharacters(in: .whitespacesAndNewlines), !rawEmail.isEmpty else { return nil } let providerAccountID: String? = switch account.identity { case let .providerAccount(id): ManagedCodexAccount.normalizeProviderAccountID(id) case .emailOnly, .unresolved: nil } return ObservedSystemCodexAccount( email: rawEmail.lowercased(), workspaceAccountID: providerAccountID, authFingerprint: CodexAuthFingerprint.fingerprint(homePath: homePath), codexHomePath: homePath, observedAt: Date(), identity: account.identity) } private func loadLiveSystemAccount() -> ObservedSystemCodexAccount? { do { guard let account = try self.systemObserver.loadSystemAccount(environment: self.baseEnvironment) else { return nil } let normalizedEmail = Self.normalizeEmail(account.email) guard !normalizedEmail.isEmpty else { return nil } return ObservedSystemCodexAccount( email: normalizedEmail, workspaceLabel: account.workspaceLabel, workspaceAccountID: account.workspaceAccountID, authFingerprint: account.authFingerprint, codexHomePath: account.codexHomePath, observedAt: account.observedAt, identity: self.runtimeIdentity(for: account)) } catch { return nil } } private static func normalizeEmail(_ email: String) -> String { email.trimmingCharacters(in: .whitespacesAndNewlines).lowercased() } private func loadRuntimeAccount(for account: ManagedCodexAccount) -> RuntimeManagedCodexAccount { let scopedEnvironment = self.managedEnvironmentBuilder(self.baseEnvironment, account) let authBackedAccount = UsageFetcher(environment: scopedEnvironment).loadAuthBackedCodexAccount() let email = Self.normalizeEmail(authBackedAccount.email ?? account.email) let identity = CodexIdentityMatcher.normalized(authBackedAccount.identity, fallbackEmail: email) return RuntimeManagedCodexAccount( email: email, identity: identity) } private func profileHomeAccounts( _ profileHomeAccounts: [ObservedSystemCodexAccount], excludingManagedAccounts managedAccounts: [ManagedCodexAccount]) -> [ObservedSystemCodexAccount] { let managedPaths = Set(managedAccounts.compactMap { CodexHomeScope.normalizedHomePath($0.managedHomePath) }) return profileHomeAccounts.filter { account in guard let path = CodexHomeScope.normalizedHomePath(account.codexHomePath) else { return false } return !managedPaths.contains(path) } } private func runtimeIdentity(for liveSystemAccount: ObservedSystemCodexAccount) -> CodexIdentity { CodexIdentityMatcher.normalized( liveSystemAccount.identity, fallbackEmail: liveSystemAccount.email) } private static func uniqueNormalizedPaths(_ paths: [String]) -> [String] { var seen: Set<String> = [] var result: [String] = [] for path in paths.compactMap({ CodexHomeScope.normalizedHomePath($0) }) { guard seen.insert(path).inserted else { continue } result.append(path) } return result }} public enum CodexIdentityMatcher { public static func matches(_ lhs: CodexIdentity, _ rhs: CodexIdentity) -> Bool { switch (lhs, rhs) { case let (.providerAccount(leftID), .providerAccount(rightID)): leftID == rightID case let (.emailOnly(leftEmail), .emailOnly(rightEmail)): leftEmail == rightEmail default: false } } public static func matches( _ lhs: CodexIdentity, lhsEmail: String?, _ rhs: CodexIdentity, rhsEmail: String?) -> Bool { guard self.matches(lhs, rhs) else { return false } guard case .providerAccount = lhs, case .providerAccount = rhs else { return true } guard let normalizedLeftEmail = CodexIdentityResolver.normalizeEmail(lhsEmail), let normalizedRightEmail = CodexIdentityResolver.normalizeEmail(rhsEmail) else { return true } return normalizedLeftEmail == normalizedRightEmail } public static func normalized(_ identity: CodexIdentity, fallbackEmail: String) -> CodexIdentity { switch identity { case .providerAccount: identity case let .emailOnly(normalizedEmail): CodexIdentityResolver.resolve(accountId: nil, email: normalizedEmail) case .unresolved: CodexIdentityResolver.resolve(accountId: nil, email: fallbackEmail) } } public static func selectionKey(for identity: CodexIdentity, fallbackEmail: String) -> String { switch self.normalized(identity, fallbackEmail: fallbackEmail) { case let .providerAccount(id): "provider:\(id)" case let .emailOnly(normalizedEmail): "email:\(normalizedEmail)" case .unresolved: "unresolved:\(fallbackEmail)" } }} private struct RuntimeManagedCodexAccount { let email: String let identity: CodexIdentity} private struct AccountIdentity: Equatable { let id: UUID let email: String let providerAccountID: String? let workspaceLabel: String? let workspaceAccountID: String? let managedHomePath: String let createdAt: TimeInterval let updatedAt: TimeInterval let lastAuthenticatedAt: TimeInterval? let authFingerprint: String? init(_ account: ManagedCodexAccount) { self.id = account.id self.email = account.email self.providerAccountID = account.providerAccountID self.workspaceLabel = account.workspaceLabel self.workspaceAccountID = account.workspaceAccountID self.managedHomePath = account.managedHomePath self.createdAt = account.createdAt self.updatedAt = account.updatedAt self.lastAuthenticatedAt = account.lastAuthenticatedAt self.authFingerprint = account.authFingerprint }}
import Foundation
public struct CodexResolvedActiveSource: Equatable, Sendable {
public let persistedSource: CodexActiveSource
public let resolvedSource: CodexActiveSource
public init(persistedSource: CodexActiveSource, resolvedSource: CodexActiveSource) {
self.persistedSource = persistedSource
self.resolvedSource = resolvedSource
}
public var requiresPersistenceCorrection: Bool {
self.persistedSource != self.resolvedSource
}
}
public enum CodexActiveSourceResolver {
public static func resolve(from snapshot: CodexAccountReconciliationSnapshot) -> CodexResolvedActiveSource {
let persistedSource = snapshot.activeSource
let resolvedSource: CodexActiveSource = switch persistedSource {
case .liveSystem:
.liveSystem
case let .managedAccount(id):
if let activeStoredAccount = snapshot.activeStoredAccount {
self.resolvedSource(for: activeStoredAccount, snapshot: snapshot)
} else {
snapshot.liveSystemAccount != nil ? .liveSystem : .managedAccount(id: id)
}
case let .profileHome(path):
if let normalizedPath = snapshot.configuredProfileHomePath(path: path) {
self.resolvedProfileSource(path: normalizedPath, snapshot: snapshot)
} else {
.liveSystem
}
}
return CodexResolvedActiveSource(
persistedSource: persistedSource,
resolvedSource: resolvedSource)
}
private static func resolvedProfileSource(
path: String,
snapshot: CodexAccountReconciliationSnapshot) -> CodexActiveSource
{
if let livePath = snapshot.liveSystemAccount.flatMap({
CodexHomeScope.normalizedHomePath($0.codexHomePath)
}), livePath == path {
return .liveSystem
}
if let storedAccount = snapshot.storedAccounts.first(where: {
CodexHomeScope.normalizedHomePath($0.managedHomePath) == path
}) {
return self.resolvedSource(for: storedAccount, snapshot: snapshot)
}
return .profileHome(path: path)
}
private static func resolvedSource(
for storedAccount: ManagedCodexAccount,
snapshot: CodexAccountReconciliationSnapshot) -> CodexActiveSource
{
self.matchesLiveSystemAccount(
storedAccount: storedAccount,
snapshot: snapshot,
liveSystemAccount: snapshot.liveSystemAccount) ? .liveSystem : .managedAccount(id: storedAccount.id)
}
private static func matchesLiveSystemAccount(
storedAccount: ManagedCodexAccount,
snapshot: CodexAccountReconciliationSnapshot,
liveSystemAccount: ObservedSystemCodexAccount?) -> Bool
{
guard let liveSystemAccount else { return false }
if let storedFingerprint = storedAccount.authFingerprint,
let liveFingerprint = liveSystemAccount.authFingerprint,
storedFingerprint == liveFingerprint
{
return true
}
return CodexIdentityMatcher.matches(
snapshot.runtimeIdentity(for: storedAccount),
lhsEmail: snapshot.runtimeEmail(for: storedAccount),
snapshot.runtimeIdentity(for: liveSystemAccount),
rhsEmail: liveSystemAccount.email)
}
}
public struct CodexAccountReconciliationSnapshot: Equatable, Sendable {
public let storedAccounts: [ManagedCodexAccount]
public let activeStoredAccount: ManagedCodexAccount?
public let liveSystemAccount: ObservedSystemCodexAccount?
public let profileHomeAccounts: [ObservedSystemCodexAccount]
public let profileHomePaths: [String]
public let matchingStoredAccountForLiveSystemAccount: ManagedCodexAccount?
public let activeSource: CodexActiveSource
public let hasUnreadableAddedAccountStore: Bool
public let storedAccountRuntimeIdentities: [UUID: CodexIdentity]
public let storedAccountRuntimeEmails: [UUID: String]
public init(
storedAccounts: [ManagedCodexAccount],
activeStoredAccount: ManagedCodexAccount?,
liveSystemAccount: ObservedSystemCodexAccount?,
profileHomeAccounts: [ObservedSystemCodexAccount] = [],
profileHomePaths: [String]? = nil,
matchingStoredAccountForLiveSystemAccount: ManagedCodexAccount?,
activeSource: CodexActiveSource,
hasUnreadableAddedAccountStore: Bool,
storedAccountRuntimeIdentities: [UUID: CodexIdentity] = [:],
storedAccountRuntimeEmails: [UUID: String] = [:])
{
self.storedAccounts = storedAccounts
self.activeStoredAccount = activeStoredAccount
self.liveSystemAccount = liveSystemAccount
self.profileHomeAccounts = Self.uniqueProfileHomeAccounts(profileHomeAccounts)
self.profileHomePaths = Self.uniqueNormalizedPaths(
profileHomePaths ?? profileHomeAccounts.map(\.codexHomePath))
self.matchingStoredAccountForLiveSystemAccount = matchingStoredAccountForLiveSystemAccount
self.activeSource = activeSource
self.hasUnreadableAddedAccountStore = hasUnreadableAddedAccountStore
self.storedAccountRuntimeIdentities = storedAccountRuntimeIdentities
self.storedAccountRuntimeEmails = storedAccountRuntimeEmails
}
public static func == (lhs: CodexAccountReconciliationSnapshot, rhs: CodexAccountReconciliationSnapshot) -> Bool {
lhs.storedAccounts.map(AccountIdentity.init) == rhs.storedAccounts.map(AccountIdentity.init)
&& lhs.activeStoredAccount.map(AccountIdentity.init) == rhs.activeStoredAccount.map(AccountIdentity.init)
&& lhs.liveSystemAccount == rhs.liveSystemAccount
&& lhs.profileHomeAccounts == rhs.profileHomeAccounts
&& lhs.profileHomePaths == rhs.profileHomePaths
&& lhs.matchingStoredAccountForLiveSystemAccount.map(AccountIdentity.init)
== rhs.matchingStoredAccountForLiveSystemAccount.map(AccountIdentity.init)
&& lhs.activeSource == rhs.activeSource
&& lhs.hasUnreadableAddedAccountStore == rhs.hasUnreadableAddedAccountStore
&& lhs.storedAccountRuntimeIdentities == rhs.storedAccountRuntimeIdentities
&& lhs.storedAccountRuntimeEmails == rhs.storedAccountRuntimeEmails
}
public func runtimeIdentity(for storedAccount: ManagedCodexAccount) -> CodexIdentity {
self.storedAccountRuntimeIdentities[storedAccount.id]
?? CodexIdentityResolver.resolve(accountId: nil, email: storedAccount.email)
}
public func runtimeEmail(for storedAccount: ManagedCodexAccount) -> String {
self.storedAccountRuntimeEmails[storedAccount.id]
?? Self.normalizeEmail(storedAccount.email)
}
public func runtimeIdentity(for liveSystemAccount: ObservedSystemCodexAccount) -> CodexIdentity {
CodexIdentityMatcher.normalized(
liveSystemAccount.identity,
fallbackEmail: liveSystemAccount.email)
}
public func profileHomeAccount(path: String) -> ObservedSystemCodexAccount? {
guard let normalizedPath = CodexHomeScope.normalizedHomePath(path) else { return nil }
return self.profileHomeAccounts.first {
CodexHomeScope.normalizedHomePath($0.codexHomePath) == normalizedPath
}
}
public func configuredProfileHomePath(path: String) -> String? {
guard let normalizedPath = CodexHomeScope.normalizedHomePath(path),
self.profileHomePaths.contains(normalizedPath)
else {
return nil
}
return normalizedPath
}
private static func normalizeEmail(_ email: String) -> String {
email.trimmingCharacters(in: .whitespacesAndNewlines).lowercased()
}
private static func uniqueProfileHomeAccounts(
_ accounts: [ObservedSystemCodexAccount]) -> [ObservedSystemCodexAccount]
{
var seenPaths: Set<String> = []
var result: [ObservedSystemCodexAccount] = []
for account in accounts {
let path = CodexHomeScope.normalizedHomePath(account.codexHomePath) ?? account.codexHomePath
guard seenPaths.insert(path).inserted else { continue }
result.append(account)
}
return result
}
private static func uniqueNormalizedPaths(_ paths: [String]) -> [String] {
var seen: Set<String> = []
return paths.compactMap { path in
guard let normalizedPath = CodexHomeScope.normalizedHomePath(path),
seen.insert(normalizedPath).inserted
else {
return nil
}
return normalizedPath
}
}
}
public struct DefaultCodexAccountReconciler: Sendable {
public let storeLoader: @Sendable () throws -> ManagedCodexAccountSet
public let systemObserver: any CodexSystemAccountObserving
public let activeSource: CodexActiveSource
public let baseEnvironment: [String: String]
public let profileHomePaths: [String]
public let managedEnvironmentBuilder: @Sendable ([String: String], ManagedCodexAccount) -> [String: String]
public init(
storeLoader: @escaping @Sendable () throws -> ManagedCodexAccountSet = {
try FileManagedCodexAccountStore().loadAccounts()
},
systemObserver: any CodexSystemAccountObserving = DefaultCodexSystemAccountObserver(),
activeSource: CodexActiveSource = .liveSystem,
baseEnvironment: [String: String],
profileHomePaths: [String] = [],
managedEnvironmentBuilder: @escaping @Sendable ([String: String], ManagedCodexAccount)
-> [String: String] = { baseEnvironment, account in
CodexHomeScope.scopedEnvironment(base: baseEnvironment, codexHome: account.managedHomePath)
})
{
self.storeLoader = storeLoader
self.systemObserver = systemObserver
self.activeSource = activeSource
self.baseEnvironment = baseEnvironment
self.profileHomePaths = Self.uniqueNormalizedPaths(profileHomePaths)
self.managedEnvironmentBuilder = managedEnvironmentBuilder
}
public func loadSnapshot() -> CodexAccountReconciliationSnapshot {
let liveSystemAccount = self.loadLiveSystemAccount()
let profileHomeAccounts = self.loadProfileHomeAccounts(liveSystemAccount: liveSystemAccount)
do {
let accounts = try self.storeLoader()
let runtimeAccounts = Dictionary(uniqueKeysWithValues: accounts.accounts.map { account in
let runtimeAccount = self.loadRuntimeAccount(for: account)
return (account.id, runtimeAccount)
})
let activeStoredAccount: ManagedCodexAccount? = switch self.activeSource {
case let .managedAccount(id):
accounts.account(id: id)
case .liveSystem, .profileHome:
nil
}
let matchingStoredAccountForLiveSystemAccount = liveSystemAccount.flatMap { liveAccount in
if let liveFingerprint = liveAccount.authFingerprint,
let exactFingerprintMatch = accounts.accounts.first(where: {
$0.authFingerprint == liveFingerprint
})
{
return exactFingerprintMatch
}
return accounts.accounts.first { account in
guard let runtimeAccount = runtimeAccounts[account.id] else { return false }
return CodexIdentityMatcher.matches(
runtimeAccount.identity,
lhsEmail: runtimeAccount.email,
self.runtimeIdentity(for: liveAccount),
rhsEmail: liveAccount.email)
}
}
return CodexAccountReconciliationSnapshot(
storedAccounts: accounts.accounts,
activeStoredAccount: activeStoredAccount,
liveSystemAccount: liveSystemAccount,
profileHomeAccounts: self.profileHomeAccounts(
profileHomeAccounts,
excludingManagedAccounts: accounts.accounts),
profileHomePaths: self.profileHomePaths,
matchingStoredAccountForLiveSystemAccount: matchingStoredAccountForLiveSystemAccount,
activeSource: self.activeSource,
hasUnreadableAddedAccountStore: false,
storedAccountRuntimeIdentities: runtimeAccounts.mapValues(\.identity),
storedAccountRuntimeEmails: runtimeAccounts.mapValues(\.email))
} catch {
return CodexAccountReconciliationSnapshot(
storedAccounts: [],
activeStoredAccount: nil,
liveSystemAccount: liveSystemAccount,
profileHomeAccounts: profileHomeAccounts,
profileHomePaths: self.profileHomePaths,
matchingStoredAccountForLiveSystemAccount: nil,
activeSource: self.activeSource,
hasUnreadableAddedAccountStore: true)
}
}
private func loadProfileHomeAccounts(
liveSystemAccount: ObservedSystemCodexAccount?) -> [ObservedSystemCodexAccount]
{
let livePath = liveSystemAccount.flatMap { CodexHomeScope.normalizedHomePath($0.codexHomePath) }
return self.profileHomePaths.compactMap { path in
guard path != livePath else { return nil }
return self.loadProfileHomeAccount(homePath: path)
}
}
private func loadProfileHomeAccount(homePath: String) -> ObservedSystemCodexAccount? {
let environment = CodexHomeScope.scopedEnvironment(base: self.baseEnvironment, codexHome: homePath)
let account = UsageFetcher(environment: environment).loadAuthBackedCodexAccount()
guard let rawEmail = account.email?.trimmingCharacters(in: .whitespacesAndNewlines),
!rawEmail.isEmpty
else {
return nil
}
let providerAccountID: String? = switch account.identity {
case let .providerAccount(id):
ManagedCodexAccount.normalizeProviderAccountID(id)
case .emailOnly, .unresolved:
nil
}
return ObservedSystemCodexAccount(
email: rawEmail.lowercased(),
workspaceAccountID: providerAccountID,
authFingerprint: CodexAuthFingerprint.fingerprint(homePath: homePath),
codexHomePath: homePath,
observedAt: Date(),
identity: account.identity)
}
private func loadLiveSystemAccount() -> ObservedSystemCodexAccount? {
do {
guard let account = try self.systemObserver.loadSystemAccount(environment: self.baseEnvironment) else {
return nil
}
let normalizedEmail = Self.normalizeEmail(account.email)
guard !normalizedEmail.isEmpty else {
return nil
}
return ObservedSystemCodexAccount(
email: normalizedEmail,
workspaceLabel: account.workspaceLabel,
workspaceAccountID: account.workspaceAccountID,
authFingerprint: account.authFingerprint,
codexHomePath: account.codexHomePath,
observedAt: account.observedAt,
identity: self.runtimeIdentity(for: account))
} catch {
return nil
}
}
private static func normalizeEmail(_ email: String) -> String {
email.trimmingCharacters(in: .whitespacesAndNewlines).lowercased()
}
private func loadRuntimeAccount(for account: ManagedCodexAccount) -> RuntimeManagedCodexAccount {
let scopedEnvironment = self.managedEnvironmentBuilder(self.baseEnvironment, account)
let authBackedAccount = UsageFetcher(environment: scopedEnvironment).loadAuthBackedCodexAccount()
let email = Self.normalizeEmail(authBackedAccount.email ?? account.email)
let identity = CodexIdentityMatcher.normalized(authBackedAccount.identity, fallbackEmail: email)
return RuntimeManagedCodexAccount(
email: email,
identity: identity)
}
private func profileHomeAccounts(
_ profileHomeAccounts: [ObservedSystemCodexAccount],
excludingManagedAccounts managedAccounts: [ManagedCodexAccount]) -> [ObservedSystemCodexAccount]
{
let managedPaths = Set(managedAccounts.compactMap { CodexHomeScope.normalizedHomePath($0.managedHomePath) })
return profileHomeAccounts.filter { account in
guard let path = CodexHomeScope.normalizedHomePath(account.codexHomePath) else { return false }
return !managedPaths.contains(path)
}
}
private func runtimeIdentity(for liveSystemAccount: ObservedSystemCodexAccount) -> CodexIdentity {
CodexIdentityMatcher.normalized(
liveSystemAccount.identity,
fallbackEmail: liveSystemAccount.email)
}
private static func uniqueNormalizedPaths(_ paths: [String]) -> [String] {
var seen: Set<String> = []
var result: [String] = []
for path in paths.compactMap({ CodexHomeScope.normalizedHomePath($0) }) {
guard seen.insert(path).inserted else { continue }
result.append(path)
}
return result
}
}
public enum CodexIdentityMatcher {
public static func matches(_ lhs: CodexIdentity, _ rhs: CodexIdentity) -> Bool {
switch (lhs, rhs) {
case let (.providerAccount(leftID), .providerAccount(rightID)):
leftID == rightID
case let (.emailOnly(leftEmail), .emailOnly(rightEmail)):
leftEmail == rightEmail
default:
false
}
}
public static func matches(
_ lhs: CodexIdentity,
lhsEmail: String?,
_ rhs: CodexIdentity,
rhsEmail: String?) -> Bool
{
guard self.matches(lhs, rhs) else { return false }
guard case .providerAccount = lhs, case .providerAccount = rhs else { return true }
guard let normalizedLeftEmail = CodexIdentityResolver.normalizeEmail(lhsEmail),
let normalizedRightEmail = CodexIdentityResolver.normalizeEmail(rhsEmail)
else {
return true
}
return normalizedLeftEmail == normalizedRightEmail
}
public static func normalized(_ identity: CodexIdentity, fallbackEmail: String) -> CodexIdentity {
switch identity {
case .providerAccount:
identity
case let .emailOnly(normalizedEmail):
CodexIdentityResolver.resolve(accountId: nil, email: normalizedEmail)
case .unresolved:
CodexIdentityResolver.resolve(accountId: nil, email: fallbackEmail)
}
}
public static func selectionKey(for identity: CodexIdentity, fallbackEmail: String) -> String {
switch self.normalized(identity, fallbackEmail: fallbackEmail) {
case let .providerAccount(id):
"provider:\(id)"
case let .emailOnly(normalizedEmail):
"email:\(normalizedEmail)"
case .unresolved:
"unresolved:\(fallbackEmail)"
}
}
}
private struct RuntimeManagedCodexAccount {
let email: String
let identity: CodexIdentity
}
private struct AccountIdentity: Equatable {
let id: UUID
let email: String
let providerAccountID: String?
let workspaceLabel: String?
let workspaceAccountID: String?
let managedHomePath: String
let createdAt: TimeInterval
let updatedAt: TimeInterval
let lastAuthenticatedAt: TimeInterval?
let authFingerprint: String?
init(_ account: ManagedCodexAccount) {
self.id = account.id
self.email = account.email
self.providerAccountID = account.providerAccountID
self.workspaceLabel = account.workspaceLabel
self.workspaceAccountID = account.workspaceAccountID
self.managedHomePath = account.managedHomePath
self.createdAt = account.createdAt
self.updatedAt = account.updatedAt
self.lastAuthenticatedAt = account.lastAuthenticatedAt
self.authFingerprint = account.authFingerprint
}
}