mirror of
https://github.com/tiennm99/ccs.git
synced 2026-07-17 10:17:05 +00:00
176 lines
5.2 KiB
TypeScript
176 lines
5.2 KiB
TypeScript
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 { isProfileLocalSharedResourceMode } from './shared-resource-policy';
|
|
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<string, string> {
|
|
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<string, string> = {};
|
|
for (const [profileName, accountName] of Object.entries(
|
|
parsed.continuity_inherit_from_account as Record<string, unknown>
|
|
)) {
|
|
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, string>
|
|
): string | undefined {
|
|
const candidates = new Set<string>(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<ProfileContinuityInheritanceResult> {
|
|
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: isProfileLocalSharedResourceMode(mappedProfile),
|
|
});
|
|
|
|
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 {};
|
|
}
|
|
}
|