import * as fs from 'fs'; import { warn } from '../utils/ui'; import InstanceManager from '../management/instance-manager'; import ProfileRegistry from './profile-registry'; import { isAccountContextMetadata, resolveAccountContextPolicy } from './account-context'; import type { ProfileType } from '../types/profile'; import { getProfileLookupCandidates, resolveAliasToCanonical } from '../utils/profile-compat'; import { getConfigJsonPath, getContinuityInheritanceMap, isUnifiedMode, } from '../config/config-loader-facade'; export interface ProfileContinuityInheritanceInput { profileName: string; profileType: ProfileType; target: string; } export interface ProfileContinuityInheritanceResult { sourceAccount?: string; claudeConfigDir?: string; } function loadLegacyContinuityInheritanceMap(): Record { const configJsonPath = getConfigJsonPath(); if (!fs.existsSync(configJsonPath)) { return {}; } try { const raw = fs.readFileSync(configJsonPath, 'utf8'); const parsed = JSON.parse(raw) as { continuity_inherit_from_account?: unknown }; if ( typeof parsed.continuity_inherit_from_account !== 'object' || parsed.continuity_inherit_from_account === null || Array.isArray(parsed.continuity_inherit_from_account) ) { return {}; } const normalized: Record = {}; for (const [profileName, accountName] of Object.entries( parsed.continuity_inherit_from_account as Record )) { if (typeof accountName !== 'string') continue; const normalizedProfile = profileName.trim(); const normalizedAccount = accountName.trim(); if (!normalizedProfile || !normalizedAccount) continue; normalized[normalizedProfile] = normalizedAccount; } return normalized; } catch (error) { console.error( warn( `Failed to parse legacy continuity mapping at "${configJsonPath}": ${(error as Error).message}` ) ); return {}; } } function resolveMappedAccount( profileName: string, profileType: ProfileType, inheritFromAccount: Record ): string | undefined { const candidates = new Set(getProfileLookupCandidates(profileName)); if (profileType === 'settings') { candidates.add(resolveAliasToCanonical(profileName)); } for (const candidate of candidates) { const mapped = inheritFromAccount[candidate]; if (typeof mapped !== 'string') { continue; } const normalized = mapped.trim(); if (normalized.length > 0) { return normalized; } } const normalizedCandidates = new Set( [...candidates].map((candidate) => candidate.trim().toLowerCase()).filter(Boolean) ); for (const [mappedProfileName, mappedAccountName] of Object.entries(inheritFromAccount)) { if (!normalizedCandidates.has(mappedProfileName.trim().toLowerCase())) { continue; } const normalized = mappedAccountName.trim(); if (normalized.length > 0) { return normalized; } } return undefined; } export function resolveConfiguredContinuitySourceAccount( profileName: string, profileType: ProfileType ): string | undefined { const inheritFromAccount = getContinuityInheritanceMap(); return ( resolveMappedAccount(profileName, profileType, inheritFromAccount) ?? (!isUnifiedMode() ? resolveMappedAccount(profileName, profileType, loadLegacyContinuityInheritanceMap()) : undefined) ); } /** * Resolve optional cross-profile continuity inheritance. * * Rules: * - Claude target only. * - Never applies to account profiles. * - Mapping source must be an account profile. */ export async function resolveProfileContinuityInheritance( input: ProfileContinuityInheritanceInput ): Promise { if (input.target !== 'claude' || input.profileType === 'account') { return {}; } const sourceAccount = resolveConfiguredContinuitySourceAccount( input.profileName, input.profileType ); if (!sourceAccount) { return {}; } try { const registry = new ProfileRegistry(); const profiles = registry.getAllProfilesMerged(); const mappedProfile = profiles[sourceAccount]; if (!mappedProfile || mappedProfile.type !== 'account') { console.error( warn( `Continuity inheritance skipped for "${input.profileName}": source "${sourceAccount}" not found or not an account profile` ) ); return {}; } const contextPolicy = resolveAccountContextPolicy( isAccountContextMetadata(mappedProfile) ? mappedProfile : undefined ); const instanceMgr = new InstanceManager(); const instancePath = await instanceMgr.ensureInstance(sourceAccount, contextPolicy, { bare: mappedProfile.bare === true, }); return { sourceAccount, claudeConfigDir: instancePath, }; } catch (error) { const message = error instanceof Error ? error.message : String(error); console.error( warn( `Continuity inheritance skipped for "${input.profileName}": failed to initialize source "${sourceAccount}" (${message})` ) ); return {}; } }