agent-ecosystem/src/renderer/services/createTeamPreferences.ts
777genius d018002c3e feat(docs): restructure VitePress IA, improve onboarding/troubleshooting docs
- Restructure sidebar: Start → Guide → Operations → Developers → Reference
- Fix EN/RU sidebar order (Installation before Quickstart)
- Expand troubleshooting with diagnostics commands and task-log triage
- Improve quickstart with prerequisites, pitfalls, and contributor links
- Expand installation docs with verification commands
- Add cyberpunk hero theme to landing page
- Add atomicFile utility with tests and stage-runtime script
- Harden team provisioning with better error handling and progress output
- Add cross-team communication, kanban, and workSync improvements
2026-05-15 23:34:06 +03:00

361 lines
11 KiB
TypeScript

import { normalizeCreateLaunchProviderForUi } from '@renderer/utils/geminiUiFreeze';
import { normalizeExplicitTeamModelForUi } from '@renderer/utils/teamModelAvailability';
import { isTeamEffortLevel } from '@shared/utils/effortLevels';
import { normalizeOptionalTeamProviderId } from '@shared/utils/teamProvider';
import type { EffortLevel, TeamFastMode, TeamProviderId } from '@shared/types';
const CREATE_TEAM_PREFIX = 'createTeam:';
const LEGACY_TEAM_PREFIX = 'team:';
const CREATE_TEAM_PROVIDER_KEY = `${CREATE_TEAM_PREFIX}lastSelectedProvider`;
const CREATE_TEAM_FAST_MODE_KEY = `${CREATE_TEAM_PREFIX}lastSelectedFastMode`;
const CREATE_TEAM_LIMIT_CONTEXT_KEY = `${CREATE_TEAM_PREFIX}lastLimitContext`;
const CREATE_TEAM_SKIP_PERMISSIONS_KEY = `${CREATE_TEAM_PREFIX}lastSkipPermissions`;
const CREATE_TEAM_EFFORT_KEY = `${CREATE_TEAM_PREFIX}lastSelectedEffort`;
const CREATE_TEAM_SYNC_MODELS_KEY = `${CREATE_TEAM_PREFIX}lastSyncModelsWithLead`;
const CREATE_TEAM_MEMBER_RUNTIME_PREFERENCES_KEY = `${CREATE_TEAM_PREFIX}lastMemberRuntimePreferences`;
const CREATE_TEAM_MEMBER_RUNTIME_PREFERENCES_VERSION = 1;
export interface CreateTeamMemberRuntimePreference {
name: string;
providerId?: TeamProviderId;
model?: string;
effort?: EffortLevel;
}
interface StoredCreateTeamMemberRuntimePreferences {
version: number;
members: CreateTeamMemberRuntimePreference[];
}
function readStorageItem(key: string): string | null {
try {
return globalThis.localStorage?.getItem(key) ?? null;
} catch {
return null;
}
}
function writeStorageItem(key: string, value: string): void {
try {
globalThis.localStorage?.setItem(key, value);
} catch {
// Ignore storage write failures in renderer helpers.
}
}
function removeStorageItem(key: string): void {
try {
globalThis.localStorage?.removeItem(key);
} catch {
// Ignore storage delete failures in renderer helpers.
}
}
function readCreateTeamPreference(key: string, legacyKey?: string): string | null {
const nextValue = readStorageItem(key);
if (nextValue != null) {
return nextValue;
}
return legacyKey ? readStorageItem(legacyKey) : null;
}
function isValidCreateTeamMemberRuntimePreference(
value: unknown
): value is CreateTeamMemberRuntimePreference {
if (typeof value !== 'object' || value === null) {
return false;
}
const entry = value as Record<string, unknown>;
return (
typeof entry.name === 'string' &&
(entry.providerId === undefined || normalizeOptionalTeamProviderId(entry.providerId) != null) &&
(entry.model === undefined || typeof entry.model === 'string') &&
(entry.effort === undefined || isTeamEffortLevel(entry.effort))
);
}
function parseStoredCreateTeamMemberRuntimePreferences(
value: string | null
): CreateTeamMemberRuntimePreference[] {
if (!value) {
return [];
}
try {
const parsed = JSON.parse(value) as StoredCreateTeamMemberRuntimePreferences;
if (
parsed.version !== CREATE_TEAM_MEMBER_RUNTIME_PREFERENCES_VERSION ||
!Array.isArray(parsed.members)
) {
return [];
}
return parsed.members.filter(isValidCreateTeamMemberRuntimePreference).map((entry) => {
const providerId = normalizeOptionalTeamProviderId(entry.providerId);
const normalizedModel = normalizeExplicitTeamModelForUi(providerId, entry.model ?? '');
return {
name: entry.name.trim(),
providerId,
model: normalizedModel || undefined,
effort: entry.effort,
};
});
} catch {
return [];
}
}
function normalizeCreateTeamMemberRuntimePreferences(
members: readonly {
name: string;
providerId?: TeamProviderId;
model?: string;
effort?: EffortLevel;
removedAt?: number | string | null;
}[]
): CreateTeamMemberRuntimePreference[] {
const normalizedMembers: CreateTeamMemberRuntimePreference[] = [];
const seenNames = new Set<string>();
for (const member of members) {
if (member.removedAt) {
continue;
}
const name = member.name.trim();
const normalizedName = name.toLowerCase();
if (!name || seenNames.has(normalizedName)) {
continue;
}
seenNames.add(normalizedName);
const providerId = normalizeOptionalTeamProviderId(member.providerId);
const model = normalizeExplicitTeamModelForUi(providerId, member.model ?? '');
normalizedMembers.push({
name,
providerId,
model: model || undefined,
effort: isTeamEffortLevel(member.effort) ? member.effort : undefined,
});
}
return normalizedMembers;
}
function getCreateTeamModelKey(providerId: TeamProviderId): string {
return `${CREATE_TEAM_PREFIX}lastSelectedModel:${providerId}`;
}
function getLegacyTeamModelKey(providerId: TeamProviderId): string {
return `${LEGACY_TEAM_PREFIX}lastSelectedModel:${providerId}`;
}
function copyLegacyPreferenceIfMissing(key: string, legacyKey: string): void {
if (readStorageItem(key) != null) {
return;
}
const legacyValue = readStorageItem(legacyKey);
if (legacyValue != null) {
writeStorageItem(key, legacyValue);
}
}
export function migrateLegacyCreateTeamPreferences(): void {
copyLegacyPreferenceIfMissing(
CREATE_TEAM_PROVIDER_KEY,
`${LEGACY_TEAM_PREFIX}lastSelectedProvider`
);
copyLegacyPreferenceIfMissing(
CREATE_TEAM_FAST_MODE_KEY,
`${LEGACY_TEAM_PREFIX}lastSelectedFastMode`
);
copyLegacyPreferenceIfMissing(
CREATE_TEAM_LIMIT_CONTEXT_KEY,
`${LEGACY_TEAM_PREFIX}lastLimitContext`
);
copyLegacyPreferenceIfMissing(
CREATE_TEAM_SKIP_PERMISSIONS_KEY,
`${LEGACY_TEAM_PREFIX}lastSkipPermissions`
);
copyLegacyPreferenceIfMissing(CREATE_TEAM_EFFORT_KEY, `${LEGACY_TEAM_PREFIX}lastSelectedEffort`);
for (const providerId of ['anthropic', 'codex', 'gemini', 'opencode'] as const) {
copyLegacyPreferenceIfMissing(
getCreateTeamModelKey(providerId),
getLegacyTeamModelKey(providerId)
);
}
const legacyTeamModel = readStorageItem(`${LEGACY_TEAM_PREFIX}lastSelectedModel`);
if (legacyTeamModel != null && readStorageItem(getCreateTeamModelKey('anthropic')) == null) {
writeStorageItem(getCreateTeamModelKey('anthropic'), legacyTeamModel);
}
removeStorageItem(`${LEGACY_TEAM_PREFIX}lastSelectedModel`);
}
export function getStoredCreateTeamProvider(): TeamProviderId {
const stored = readCreateTeamPreference(
CREATE_TEAM_PROVIDER_KEY,
`${LEGACY_TEAM_PREFIX}lastSelectedProvider`
);
return normalizeCreateLaunchProviderForUi(normalizeOptionalTeamProviderId(stored), true);
}
export function setStoredCreateTeamProvider(providerId: TeamProviderId): void {
writeStorageItem(CREATE_TEAM_PROVIDER_KEY, providerId);
}
export function getStoredCreateTeamModel(providerId: TeamProviderId): string {
const stored = readCreateTeamPreference(
getCreateTeamModelKey(providerId),
getLegacyTeamModelKey(providerId)
);
if (stored === null) {
return providerId === 'anthropic' ? 'opus' : '';
}
return normalizeExplicitTeamModelForUi(providerId, stored === '__default__' ? '' : stored);
}
export function setStoredCreateTeamModel(providerId: TeamProviderId, model: string): void {
writeStorageItem(getCreateTeamModelKey(providerId), model);
}
export function getStoredCreateTeamFastMode(): TeamFastMode {
const stored = readCreateTeamPreference(
CREATE_TEAM_FAST_MODE_KEY,
`${LEGACY_TEAM_PREFIX}lastSelectedFastMode`
);
return stored === 'on' || stored === 'off' || stored === 'inherit' ? stored : 'inherit';
}
export function setStoredCreateTeamFastMode(value: TeamFastMode): void {
writeStorageItem(CREATE_TEAM_FAST_MODE_KEY, value);
}
export function getStoredCreateTeamLimitContext(): boolean {
return (
readCreateTeamPreference(
CREATE_TEAM_LIMIT_CONTEXT_KEY,
`${LEGACY_TEAM_PREFIX}lastLimitContext`
) === 'true'
);
}
export function setStoredCreateTeamLimitContext(value: boolean): void {
writeStorageItem(CREATE_TEAM_LIMIT_CONTEXT_KEY, String(value));
}
export function getStoredCreateTeamSkipPermissions(): boolean {
return (
readCreateTeamPreference(
CREATE_TEAM_SKIP_PERMISSIONS_KEY,
`${LEGACY_TEAM_PREFIX}lastSkipPermissions`
) !== 'false'
);
}
export function setStoredCreateTeamSkipPermissions(value: boolean): void {
writeStorageItem(CREATE_TEAM_SKIP_PERMISSIONS_KEY, String(value));
}
export function getStoredCreateTeamEffort(): string {
return (
readCreateTeamPreference(CREATE_TEAM_EFFORT_KEY, `${LEGACY_TEAM_PREFIX}lastSelectedEffort`) ??
''
);
}
export function setStoredCreateTeamEffort(value: string): void {
writeStorageItem(CREATE_TEAM_EFFORT_KEY, value);
}
export function getStoredCreateTeamSyncModelsWithLead(): boolean {
return readStorageItem(CREATE_TEAM_SYNC_MODELS_KEY) !== 'false';
}
export function setStoredCreateTeamSyncModelsWithLead(value: boolean): void {
writeStorageItem(CREATE_TEAM_SYNC_MODELS_KEY, String(value));
}
export function getStoredCreateTeamMemberRuntimePreferences(): CreateTeamMemberRuntimePreference[] {
return parseStoredCreateTeamMemberRuntimePreferences(
readStorageItem(CREATE_TEAM_MEMBER_RUNTIME_PREFERENCES_KEY)
);
}
export function setStoredCreateTeamMemberRuntimePreferences(
members: readonly {
name: string;
providerId?: TeamProviderId;
model?: string;
effort?: EffortLevel;
removedAt?: number | string | null;
}[]
): void {
const existingMembers = getStoredCreateTeamMemberRuntimePreferences();
const nextMembers = normalizeCreateTeamMemberRuntimePreferences(members);
const mergedMembers = [...existingMembers];
const mergedIndexByName = new Map(
mergedMembers.map((member, index) => [member.name.trim().toLowerCase(), index] as const)
);
for (const member of nextMembers) {
const normalizedName = member.name.trim().toLowerCase();
const existingIndex = mergedIndexByName.get(normalizedName);
if (existingIndex == null) {
mergedIndexByName.set(normalizedName, mergedMembers.length);
mergedMembers.push(member);
continue;
}
mergedMembers[existingIndex] = member;
}
writeStorageItem(
CREATE_TEAM_MEMBER_RUNTIME_PREFERENCES_KEY,
JSON.stringify({
version: CREATE_TEAM_MEMBER_RUNTIME_PREFERENCES_VERSION,
members: mergedMembers,
} satisfies StoredCreateTeamMemberRuntimePreferences)
);
}
export function applyStoredCreateTeamMemberRuntimePreferences<
T extends {
name: string;
providerId?: TeamProviderId;
model?: string;
effort?: EffortLevel;
removedAt?: number | string | null;
},
>(members: readonly T[]): T[] {
const storedPreferences = getStoredCreateTeamMemberRuntimePreferences();
if (storedPreferences.length === 0) {
return [...members];
}
const preferenceByName = new Map(
storedPreferences
.map((entry) => [entry.name.trim().toLowerCase(), entry] as const)
.filter(([name]) => name.length > 0)
);
return members.map((member) => {
if (member.removedAt) {
return member;
}
const preference = preferenceByName.get(member.name.trim().toLowerCase());
if (!preference) {
return member;
}
return {
...member,
providerId: preference.providerId,
model: preference.model ?? '',
effort: preference.effort,
};
});
}